├── .gitignore ├── README.md ├── architecture.jpeg ├── jdatastudio-admin-app ├── .gitignore ├── README.md ├── build │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.9c4002cf80b8311ff71860f5cb7e8faf.js │ ├── service-worker.js │ └── static │ │ ├── css │ │ └── main.d531bd2a.chunk.css │ │ └── js │ │ ├── 1.f3a2887c.chunk.js │ │ ├── main.66efbd61.chunk.js │ │ └── runtime~main.229c360f.js ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── Login.js │ ├── LoginForm.js │ ├── addUploadFeature.js │ ├── admin │ │ ├── AddPermissionButton.js │ │ ├── CheckButton.js │ │ ├── PasswordInput.js │ │ ├── Permission.js │ │ ├── Resource.js │ │ ├── Role.js │ │ └── User.js │ ├── authProvider.js │ ├── configuration │ │ └── Configuration.js │ ├── constants.js │ ├── crud │ │ ├── CRUDCreate.js │ │ ├── CRUDEdit.js │ │ ├── CRUDList.js │ │ ├── CRUDShow.js │ │ ├── index.js │ │ ├── renderField.js │ │ ├── renderFilter.js │ │ └── renderInput.js │ ├── dataProvider.js │ ├── i18n │ │ ├── ra-language-chinese.js │ │ └── zh-hans.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── serviceWorker.js │ ├── sys │ │ ├── DataSource.js │ │ ├── Entity.js │ │ ├── Field.js │ │ └── SyncButton.js │ └── util │ │ └── util.js └── yarn.lock ├── jdatastudio-admin ├── .gitignore ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── dataserver │ │ │ ├── admin │ │ │ ├── Application.java │ │ │ ├── config │ │ │ │ ├── Constants.java │ │ │ │ ├── HttpsConfiguration.java │ │ │ │ ├── MvcConfig.java │ │ │ │ └── WebSecurityConfig.java │ │ │ ├── exception │ │ │ │ └── RestResponseEntityExceptionHandler.java │ │ │ ├── model │ │ │ │ ├── Permission.java │ │ │ │ └── security │ │ │ │ │ ├── AuthorityName.java │ │ │ │ │ └── ListRoleConverter.java │ │ │ ├── rest │ │ │ │ ├── ParamUtil.java │ │ │ │ ├── PermissionApi.java │ │ │ │ ├── RoleApi.java │ │ │ │ ├── RoleService.java │ │ │ │ ├── UserApi.java │ │ │ │ └── UserService.java │ │ │ ├── schema │ │ │ │ ├── DataServiceProxy.java │ │ │ │ ├── DataSourceService.java │ │ │ │ ├── DatasourceApi.java │ │ │ │ ├── SchemaApi.java │ │ │ │ └── SchemaService.java │ │ │ ├── security │ │ │ │ ├── JwtAuthenticationEntryPoint.java │ │ │ │ ├── JwtAuthenticationRequest.java │ │ │ │ ├── JwtAuthorizationTokenFilter.java │ │ │ │ ├── JwtTokenUtil.java │ │ │ │ ├── JwtUser.java │ │ │ │ ├── JwtUserFactory.java │ │ │ │ ├── Role.java │ │ │ │ ├── User.java │ │ │ │ ├── controller │ │ │ │ │ ├── AuthenticationApi.java │ │ │ │ │ └── AuthenticationException.java │ │ │ │ ├── repository │ │ │ │ │ ├── MongoUserRepository.java │ │ │ │ │ └── UserRepository.java │ │ │ │ └── service │ │ │ │ │ ├── CRUDPermission.java │ │ │ │ │ ├── JwtAuthenticationResponse.java │ │ │ │ │ ├── JwtUserDetailsService.java │ │ │ │ │ ├── SecurityService.java │ │ │ │ │ └── impl │ │ │ │ │ └── SecurityServiceImpl.java │ │ │ ├── sys │ │ │ │ ├── JDataContext.java │ │ │ │ ├── MorphiaFactory.java │ │ │ │ ├── SequenceService.java │ │ │ │ ├── SysService.java │ │ │ │ ├── Tenant.java │ │ │ │ └── TenantUser.java │ │ │ └── util │ │ │ │ ├── JsonUtil.java │ │ │ │ ├── ResponseUtil.java │ │ │ │ └── SnowFlake.java │ │ │ └── dataapi │ │ │ ├── Constants.java │ │ │ ├── SwaggerConfig.java │ │ │ ├── SysConfig.java │ │ │ ├── data │ │ │ ├── AbstractRequestWrapper.java │ │ │ ├── ApiStandard.java │ │ │ ├── DataApi.java │ │ │ ├── DataServiceProxy.java │ │ │ ├── DesensitizedUtils.java │ │ │ ├── FileStorageApi.java │ │ │ ├── JdsFile.java │ │ │ ├── ResponseWrapper.java │ │ │ ├── StorageAliyunServiceImpl.java │ │ │ └── StorageJdsServiceImpl.java │ │ │ ├── jsonserver │ │ │ ├── JsonServerRequestWrapper.java │ │ │ └── JsonServerResponseWrapper.java │ │ │ └── postgrest │ │ │ ├── PostgrestRequestWrapper.java │ │ │ └── PostgrestResponseWrapper.java │ └── resources │ │ ├── 2110293_jdatastudio.com.pfx │ │ ├── application-aliyun.yml │ │ ├── application-lcloud.yml │ │ ├── application.yml │ │ └── logback.xml │ └── test │ └── java │ └── com │ └── dataserver │ └── admin │ ├── AbstractTest.java │ └── UserServiceTest.java ├── jdatastudio-api ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── dataserver │ └── api │ ├── ComplexFilter.java │ ├── Constants.java │ ├── Filter.java │ ├── Filters.java │ ├── IDataService.java │ ├── LogicEnum.java │ ├── OperatorEnum.java │ ├── OrderEnum.java │ ├── Pagination.java │ ├── QueryParams.java │ ├── Sort.java │ └── schema │ ├── ChoiceItem.java │ ├── ComponentType.java │ ├── DbColumnType.java │ ├── DbTypeEnum.java │ ├── Entity.java │ ├── Field.java │ ├── ISchemaService.java │ ├── JDataSource.java │ └── SensitiveEnum.java ├── jdatastudio-connector-elasticsearch ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── dataserver │ └── connector │ └── elasticsearch │ ├── EsDataService.java │ ├── EsDataSourceService.java │ ├── EsSchemaService.java │ └── util │ └── JsonUtil.java ├── jdatastudio-connector-mongodb ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── dataserver │ └── connector │ └── mongodb │ ├── MongoDataService.java │ ├── MongoDataSourceService.java │ └── MongoSchemaService.java ├── jdatastudio-connector-mysql ├── .gitignore ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── dataserver │ └── connector │ └── mysql │ ├── MySqlDataService.java │ ├── MySqlSchemaService.java │ └── MysqlDataSourceService.java ├── jdatastudio.mp4 └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jdatastudio 2 | 从数据库搭建restful api和中后台应用 3 | 4 | [![jdatastudio-demo](architecture.jpeg)](https://b23.tv/av87079713) 5 | 6 | ## Features 7 | * 即配即用:0编码,通过界面配置中后台系统 8 | * 多数据源支持: 支持的数据源包括 mysql、mongo、elasticsearch、jr-datacenter、elasticsearch+hbase 9 | * 多租户:数据源、权限设置物理隔离 10 | * 组件化:文本、数字、日期、选择、引用(关联) 11 | * Api Open : 后端所有数据访问接口、元数据接口、安全接口遵循RESTFUL协议 12 | * 数据安全:https + spring security + jjwt + app saltKey + rbac 13 | * 轻量:仅依赖 jdatastudio-admin.jar 和 mongodb,可独立部署 14 | * 可扩展:支持数据脱敏、多表关联、数据过滤、自定义按钮 15 | 16 | ## Guide 17 | ```sh 18 | java -jar jdatastudio-admin.jar --spring.data.mongodb.uri=mongodb://localhost:27017/jdatastudio-admin 19 | ``` 20 | 21 | ## Examples 22 | * `demo` https://www.jdatastudio.com admin admin 23 | * `api` https://www.jdatastudio.com/swagger-ui.html 24 | 25 | ## 从数据源构建通用restful api 26 | 27 | ### apis 28 | ``` 29 | GET /students 30 | GET /students/1 31 | POST /students 32 | PUT /students/1 33 | ``` 34 | ### filter 35 | 36 | ``` 37 | GET /students?name=nick&grade=5 38 | GET /students?id_in=1,2 39 | ``` 40 | ### paginate 41 | 42 | ``` 43 | GET /students?_start=0&_end=10 44 | ``` 45 | 46 | ### sort 47 | 48 | ``` 49 | GET /students?_order=ASC&_sort=score 50 | ``` 51 | 52 | ### operators 53 | 54 | ``` 55 | eq 等于 name=nick 56 | gte 大于等于 grade_gte=5 57 | gt 大于 grade_gt=5 58 | lte 小于等于 grade_lte=5 59 | lt 小于 grade_lt=5 60 | like 小于 name_like=ni 61 | in 存在于 name_in=nick,paul 62 | ``` 63 | 64 | ## 配置schema,生成前端页面 65 | 66 | ### 数据源(超级管理员维护) 67 | ``` 68 | dbType 数据源类型(mysql,mongodb,elasticsearch) 69 | url 数据源连接 jdbc:mysql://172.19.152.132:3306/student 70 | username 用户名 root 71 | password 密码 ****** 72 | ``` 73 | ### 表 74 | ``` 75 | name 表名 76 | label 标签 77 | ``` 78 | ### 字段 79 | ``` 80 | name 字段名 81 | label 字段标签 82 | component 组件类型 83 | mainField 是否主字段 /students?q=nick 将按此字段检索 84 | partOfPrimaryKey 是否联合主键,表中包含多个主键时,将按__分隔符拼接id的值 85 | sensitiveType 脱敏类型 86 | 87 | showInList 是否展示在列表页 88 | sortable 是否支持排序 89 | showInShow 是否展示在详情页 90 | 91 | showInFilter 是否以此字段过滤 92 | alwaysOn 前端是否总展示 93 | multiFilter 是否支持多选过滤 94 | 95 | showInEdit 是否展示在编辑页 96 | showInCreate 是否展示在新建页 97 | maxLength 最大长度(校验) 98 | required 是否必填 99 | defaultValue 默认值 100 | dbColumnType 数据库字段类型(varchar,int...) 101 | ``` 102 | 103 | #### 组件类型 104 | 105 | ``` 106 | Text 文本 107 | Number 数字 108 | Select 选择 109 | Reference 引用 110 | Date 日期 111 | ``` 112 | #### 脱敏类型 113 | 114 | ``` 115 | nonsensitive 非敏感数据 116 | sensitive 其他敏感数据 117 | mobile 手机 118 | card 银行卡号 119 | id 身份证号 120 | ``` -------------------------------------------------------------------------------- /architecture.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-server/jdatastudio/588dbba7fef8a9819a8f6d0439faf411a72d0ba8/architecture.jpeg -------------------------------------------------------------------------------- /jdatastudio-admin-app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | # /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/README.md: -------------------------------------------------------------------------------- 1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 2 | 3 | ## Available Scripts 4 | 5 | In the project directory, you can run: 6 | 7 | ### `npm start` 8 | 9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 11 | 12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console. 14 | 15 | ### `npm test` 16 | 17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 19 | 20 | ### `npm run build` 21 | 22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance. 24 | 25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed! 27 | 28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 29 | 30 | ### `npm run eject` 31 | 32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 33 | 34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 35 | 36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 37 | 38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 39 | 40 | ## Learn More 41 | 42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 43 | 44 | To learn React, check out the [React documentation](https://reactjs.org/). 45 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "main.css": "/static/css/main.d531bd2a.chunk.css", 3 | "main.js": "/static/js/main.66efbd61.chunk.js", 4 | "main.js.map": "/static/js/main.66efbd61.chunk.js.map", 5 | "static/js/1.f3a2887c.chunk.js": "/static/js/1.f3a2887c.chunk.js", 6 | "static/js/1.f3a2887c.chunk.js.map": "/static/js/1.f3a2887c.chunk.js.map", 7 | "runtime~main.js": "/static/js/runtime~main.229c360f.js", 8 | "runtime~main.js.map": "/static/js/runtime~main.229c360f.js.map", 9 | "static/css/main.d531bd2a.chunk.css.map": "/static/css/main.d531bd2a.chunk.css.map", 10 | "index.html": "/index.html", 11 | "precache-manifest.9c4002cf80b8311ff71860f5cb7e8faf.js": "/precache-manifest.9c4002cf80b8311ff71860f5cb7e8faf.js", 12 | "service-worker.js": "/service-worker.js" 13 | } -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-server/jdatastudio/588dbba7fef8a9819a8f6d0439faf411a72d0ba8/jdatastudio-admin-app/build/favicon.ico -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/index.html: -------------------------------------------------------------------------------- 1 | JDataStudio
-------------------------------------------------------------------------------- /jdatastudio-admin-app/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/precache-manifest.9c4002cf80b8311ff71860f5cb7e8faf.js: -------------------------------------------------------------------------------- 1 | self.__precacheManifest = [ 2 | { 3 | "revision": "229c360febb4351a89df", 4 | "url": "/static/js/runtime~main.229c360f.js" 5 | }, 6 | { 7 | "revision": "66efbd6157640fb9967a", 8 | "url": "/static/js/main.66efbd61.chunk.js" 9 | }, 10 | { 11 | "revision": "f3a2887cf848ced9f0cb", 12 | "url": "/static/js/1.f3a2887c.chunk.js" 13 | }, 14 | { 15 | "revision": "66efbd6157640fb9967a", 16 | "url": "/static/css/main.d531bd2a.chunk.css" 17 | }, 18 | { 19 | "revision": "e7edad06889d6f6eadb255253cc5807b", 20 | "url": "/index.html" 21 | } 22 | ]; -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your Workbox-powered service worker! 3 | * 4 | * You'll need to register this file in your web app and you should 5 | * disable HTTP caching for this file too. 6 | * See https://goo.gl/nhQhGp 7 | * 8 | * The rest of the code is auto-generated. Please don't update this file 9 | * directly; instead, make changes to your Workbox build configuration 10 | * and re-run your build process. 11 | * See https://goo.gl/2aRDsh 12 | */ 13 | 14 | importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js"); 15 | 16 | importScripts( 17 | "/precache-manifest.9c4002cf80b8311ff71860f5cb7e8faf.js" 18 | ); 19 | 20 | workbox.clientsClaim(); 21 | 22 | /** 23 | * The workboxSW.precacheAndRoute() method efficiently caches and responds to 24 | * requests for URLs in the manifest. 25 | * See https://goo.gl/S9QRab 26 | */ 27 | self.__precacheManifest = [].concat(self.__precacheManifest || []); 28 | workbox.precaching.suppressWarnings(); 29 | workbox.precaching.precacheAndRoute(self.__precacheManifest, {}); 30 | 31 | workbox.routing.registerNavigationRoute("/index.html", { 32 | 33 | blacklist: [/^\/_/,/\/[^\/]+\.[^\/]+$/], 34 | }); 35 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/static/css/main.d531bd2a.chunk.css: -------------------------------------------------------------------------------- 1 | body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace} 2 | /*# sourceMappingURL=main.d531bd2a.chunk.css.map */ -------------------------------------------------------------------------------- /jdatastudio-admin-app/build/static/js/runtime~main.229c360f.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,f,i=r[0],l=r[1],a=r[2],c=0,s=[];c0.2%", 25 | "not dead", 26 | "not ie <= 11", 27 | "not op_mini all" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-server/jdatastudio/588dbba7fef8a9819a8f6d0439faf411a72d0ba8/jdatastudio-admin-app/public/favicon.ico -------------------------------------------------------------------------------- /jdatastudio-admin-app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 22 | JDataStudio 23 | 24 | 25 | 28 |
29 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/App.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | animation: App-logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | } 9 | 10 | .App-header { 11 | background-color: #282c34; 12 | min-height: 100vh; 13 | display: flex; 14 | flex-direction: column; 15 | align-items: center; 16 | justify-content: center; 17 | font-size: calc(10px + 2vmin); 18 | color: white; 19 | } 20 | 21 | .App-link { 22 | color: #61dafb; 23 | } 24 | 25 | @keyframes App-logo-spin { 26 | from { 27 | transform: rotate(0deg); 28 | } 29 | to { 30 | transform: rotate(360deg); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/App.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment} from "react"; 2 | import {Admin, Resource, resolveBrowserLocale} from "react-admin"; 3 | import {UserCreate, UserEdit, UserList, UserShow} from "./admin/User"; 4 | import {RoleCreate, RoleEdit, RoleList, RoleShow} from "./admin/Role"; 5 | import {DataSourceCreate, DataSourceEdit, DataSourceList} from "./sys/DataSource"; 6 | import {EntityEdit, EntityList} from "./sys/Entity"; 7 | import {FieldEdit} from "./sys/Field"; 8 | import {PermissionEdit} from "./admin/Permission"; 9 | import {url} from "./constants"; 10 | import jsonServerProvider from "./dataProvider"; 11 | import chineseMessages from "./i18n/zh-hans"; 12 | import ActionSupervisorAccount from "@material-ui/icons/SupervisorAccount"; 13 | import ActionAccessibility from "@material-ui/icons/Accessibility"; 14 | import StorageIcon from "@material-ui/icons/Storage"; 15 | import Message from "@material-ui/icons/Message"; 16 | import {authProvider, httpClient} from "./authProvider"; 17 | import addUploadFeature from './addUploadFeature'; 18 | import {CRUDCreate, CRUDEdit, CRUDList, CRUDShow} from "./crud"; 19 | import polyglotI18nProvider from 'ra-i18n-polyglot'; 20 | import englishMessages from 'ra-language-english'; 21 | 22 | const i18nProvider = polyglotI18nProvider(locale => 23 | locale === 'en' ? englishMessages : chineseMessages, 24 | 'ch' 25 | ); 26 | 27 | const fetchResources = permissions => 28 | httpClient(url + "/schemas") 29 | .then(response => { 30 | return response.json; 31 | }) 32 | .then(json => { 33 | let resources = []; 34 | json.map(resource => 35 | resources.push() 44 | ) 45 | if (permissions.indexOf("ROLE_ADMIN") >= 0) { 46 | resources = resources.concat(adminResources); 47 | } 48 | return resources; 49 | }) 50 | .catch(error => { 51 | if (error.status === 401) { 52 | localStorage.removeItem('token'); 53 | localStorage.removeItem('permissions'); 54 | window.location.replace('/#/login'); 55 | } 56 | }); 57 | 58 | const adminResources = [ 59 | , 61 | , 63 | , 65 | , 66 | , 67 | 68 | ] 69 | const dataProvider = jsonServerProvider(url, httpClient); 70 | const uploadCapableDataProvider = addUploadFeature(dataProvider); 71 | const App = () => ( 72 | 74 | {fetchResources} 75 | 76 | ); 77 | 78 | export default App; 79 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import classnames from 'classnames'; 4 | import Card from '@material-ui/core/Card'; 5 | import Avatar from '@material-ui/core/Avatar'; 6 | import { withStyles } from '@material-ui/core/styles'; 7 | import {Notification} from 'react-admin'; 8 | import DefaultLoginForm from './LoginForm'; 9 | import { red } from '@material-ui/core/colors'; 10 | const styles = theme => ({ 11 | main: { 12 | display: 'flex', 13 | flexDirection: 'column', 14 | minHeight: '100vh', 15 | alignItems: 'center', 16 | justifyContent: 'center', 17 | // background: 'url(https://source.unsplash.com/random/1600x900)', 18 | backgroundColor: 'red', 19 | backgroundSize: 'cover', 20 | }, 21 | card: { 22 | minWidth: 300, 23 | }, 24 | avatar: { 25 | margin: '1em', 26 | display: 'flex', 27 | justifyContent: 'center', 28 | }, 29 | icon: { 30 | backgroundColor: red[500], 31 | }, 32 | }); 33 | 34 | const sanitizeRestProps = ({ 35 | classes, 36 | className, 37 | location, 38 | title, 39 | array, 40 | theme, 41 | staticContext, 42 | ...rest 43 | }) => rest; 44 | 45 | /** 46 | * A standalone login page, to serve as authentication gate to the admin 47 | * 48 | * Expects the user to enter a login and a password, which will be checked 49 | * by the `authProvider` using the AUTH_LOGIN verb. Redirects to the root page 50 | * (/) upon success, otherwise displays an authentication error message. 51 | * 52 | * Copy and adapt this component to implement your own login logic 53 | * (e.g. to authenticate via email or facebook or anything else). 54 | * 55 | * @example 56 | * import MyLoginPage from './MyLoginPage'; 57 | * const App = () => ( 58 | * 59 | * ... 60 | * 61 | * ); 62 | */ 63 | const Login = ({ classes, className, loginForm, ...rest }) => ( 64 |
68 | 69 |
70 | 71 | 72 |
73 | {loginForm} 74 |
75 | 76 |
77 | ); 78 | 79 | Login.propTypes = { 80 | className: PropTypes.string, 81 | authProvider: PropTypes.func, 82 | classes: PropTypes.object, 83 | input: PropTypes.object, 84 | meta: PropTypes.object, 85 | previousRoute: PropTypes.string, 86 | loginForm: PropTypes.element, 87 | }; 88 | 89 | Login.defaultProps = { 90 | loginForm: , 91 | }; 92 | 93 | export default withStyles(styles)(Login); 94 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/LoginForm.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Field, propTypes, reduxForm } from 'redux-form'; 4 | import { connect } from 'react-redux'; 5 | import compose from 'recompose/compose'; 6 | import CardActions from '@material-ui/core/CardActions'; 7 | import Button from '@material-ui/core/Button'; 8 | import TextField from '@material-ui/core/TextField'; 9 | import CircularProgress from '@material-ui/core/CircularProgress'; 10 | import { withStyles } from '@material-ui/core/styles'; 11 | import { translate, userLogin } from 'ra-core'; 12 | 13 | const styles = () => ({ 14 | form: { 15 | padding: '0 1em 1em 1em', 16 | }, 17 | input: { 18 | marginTop: '1em', 19 | }, 20 | button: { 21 | width: '100%', 22 | }, 23 | }); 24 | 25 | // see http://redux-form.com/6.4.3/examples/material-ui/ 26 | const renderInput = ({ 27 | meta: { touched, error } = {}, // eslint-disable-line react/prop-types 28 | input: { ...inputProps }, // eslint-disable-line react/prop-types 29 | ...props 30 | }) => ( 31 | 38 | ); 39 | const login = (auth, dispatch, { redirectTo }) => 40 | dispatch(userLogin(auth, redirectTo)); 41 | 42 | const LoginForm = ({ classes, isLoading, handleSubmit, translate }) => ( 43 |
44 |
45 |
46 | 52 |
53 |
54 | 61 |
62 |
63 | 64 | 74 | 75 |
76 | ); 77 | LoginForm.propTypes = { 78 | ...propTypes, 79 | classes: PropTypes.object, 80 | redirectTo: PropTypes.string, 81 | }; 82 | 83 | const mapStateToProps = state => ({ isLoading: state.admin.loading > 0 }); 84 | 85 | const enhance = compose( 86 | withStyles(styles), 87 | translate, 88 | connect(mapStateToProps), 89 | reduxForm({ 90 | form: 'signIn', 91 | validate: (values, props) => { 92 | const errors = {}; 93 | const { translate } = props; 94 | if (!values.username) 95 | errors.username = translate('ra.validation.required'); 96 | if (!values.password) 97 | errors.password = translate('ra.validation.required'); 98 | return errors; 99 | }, 100 | }) 101 | ); 102 | 103 | export default enhance(LoginForm); 104 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/addUploadFeature.js: -------------------------------------------------------------------------------- 1 | // in addUploadFeature.js 2 | /** 3 | * Convert a `File` object returned by the upload input into a base 64 string. 4 | * That's not the most optimized way to store images in production, but it's 5 | * enough to illustrate the idea of data provider decoration. 6 | */ 7 | const convertFileToBase64 = file => new Promise((resolve, reject) => { 8 | const reader = new FileReader(); 9 | reader.readAsDataURL(file.rawFile); 10 | 11 | reader.onload = () => resolve(reader.result); 12 | reader.onerror = reject; 13 | }); 14 | 15 | /** 16 | * For posts update only, convert uploaded image in base 64 and attach it to 17 | * the `picture` sent property, with `src` and `title` attributes. 18 | */ 19 | const addUploadFeature = requestHandler => (type, resource, params) => { 20 | if (type === 'UPDATE' || type === 'CREATE') { 21 | // notice that following condition can be true only when `` component has parameter `multiple={true}` 22 | // if parameter `multiple` is false, then data.pictures is not an array, but single object 23 | const newPictures = []; 24 | for (let key in params.data) { 25 | let p = params.data[key]; 26 | if (p.rawFile && p.rawFile instanceof File) { 27 | p.name = key; 28 | newPictures.push(p); 29 | } 30 | } 31 | if (newPictures.length !== 0) { 32 | return Promise.all(newPictures.map(convertFileToBase64)) 33 | .then(base64Pictures => base64Pictures.map((picture64, index) => ({ 34 | src: picture64, 35 | title: `${newPictures[index].title}`, 36 | name: `${newPictures[index].name}`, 37 | }))) 38 | .then(transformedNewPictures => { 39 | transformedNewPictures.map((transformedNewPicture, index) => { 40 | params.data[transformedNewPicture.name] = transformedNewPicture; 41 | }); 42 | requestHandler(type, resource, { 43 | ...params, 44 | data: { 45 | ...params.data 46 | }, 47 | }) 48 | }); 49 | } 50 | return requestHandler(type, resource, params); 51 | } 52 | // for other request types and resources, fall back to the default request handler 53 | return requestHandler(type, resource, params); 54 | }; 55 | 56 | export default addUploadFeature; -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/AddPermissionButton.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Link} from "react-router-dom"; 3 | import ChatBubbleIcon from "@material-ui/icons/ChatBubble"; 4 | import {withStyles} from "@material-ui/core/styles"; 5 | import {Button} from "react-admin"; 6 | 7 | const styles = { 8 | button: { 9 | marginTop: '1em' 10 | } 11 | }; 12 | 13 | const AddPermissionButton = ({classes, record}) => ( 14 | 24 | ); 25 | 26 | export default withStyles(styles)(AddPermissionButton); 27 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/CheckButton.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import PropTypes from "prop-types"; 3 | import {connect} from "react-redux"; 4 | import IconButton from "@material-ui/core/IconButton"; 5 | import {showNotification as showNotificationAction} from "react-admin"; 6 | import {push as pushAction} from "react-router-redux"; 7 | import {url} from "../constants"; 8 | import {httpClient} from "../authProvider"; 9 | import FalseIcon from "@material-ui/icons/Clear"; 10 | import TrueIcon from "@material-ui/icons/Done"; 11 | 12 | 13 | class CheckButton extends Component { 14 | constructor(props) { 15 | super(props); 16 | this.state = {status: props.record[props.source]}; 17 | } 18 | 19 | handleCheck = () => { 20 | const {record, showNotification, roleId,source} = this.props; 21 | const updatedRecord = record; 22 | let checkStatus = this.state.status; 23 | updatedRecord.id = record.id; 24 | updatedRecord[source] = !checkStatus; 25 | httpClient(url + `/roles/${roleId}/editPermission`, {method: 'PUT', body: JSON.stringify(updatedRecord)}) 26 | .then(() => { 27 | showNotification('操作成功'); 28 | this.setState({ 29 | status: !checkStatus 30 | }); 31 | pushAction('/permissions'); 32 | }) 33 | .catch((e) => { 34 | console.error(e); 35 | showNotification('操作失败', 'warning') 36 | }); 37 | } 38 | 39 | render() { 40 | return (this.state.status ? 41 | 42 | 43 | : 44 | 45 | 46 | 47 | ); 48 | } 49 | } 50 | 51 | CheckButton.propTypes = { 52 | push: PropTypes.func, 53 | record: PropTypes.object, 54 | source: PropTypes.string, 55 | showNotification: PropTypes.func, 56 | }; 57 | 58 | export default connect(null, { 59 | showNotification: showNotificationAction, 60 | push: pushAction, 61 | })(CheckButton); -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/PasswordInput.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import {TextInput} from "react-admin"; 3 | import AutorenewIcon from "@material-ui/icons/Autorenew"; 4 | import IconButton from "@material-ui/core/IconButton"; 5 | 6 | const generatePassword = () => { 7 | var length = 8, 8 | charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", 9 | retVal = ""; 10 | for (var i = 0, n = charset.length; i < length; ++i) { 11 | retVal += charset.charAt(Math.floor(Math.random() * n)); 12 | } 13 | return retVal; 14 | } 15 | 16 | class PasswordInput extends Component { 17 | 18 | render() { 19 | const {input: { onChange}} = this.props 20 | return ( 21 | 22 | 23 |   24 | {onChange(generatePassword()); }}> 25 | 26 | 27 | 28 | ) 29 | } 30 | } 31 | 32 | export default PasswordInput; -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/Permission.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ArrayInput, 4 | AutocompleteInput, 5 | BooleanInput, 6 | DisabledInput, 7 | Edit, 8 | FormTab, 9 | ReferenceInput, 10 | SelectInput, 11 | SimpleFormIterator, 12 | TabbedForm, 13 | TextInput 14 | } from "react-admin"; 15 | import {operatorType} from "../constants"; 16 | 17 | const redirect = (basePath, id, data) => `/roles/` + id.substr(0, id.lastIndexOf("_")) + "/show"; 18 | 19 | export const PermissionEdit = (props) => { 20 | let id = props.id; 21 | let eid = id.substr(id.lastIndexOf("_") + 1); 22 | return 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/Resource.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {Datagrid,SelectInput,SelectField,Edit,required, List, TextField,Create,SimpleForm,TextInput,EditButton} from "react-admin"; 3 | import {WithoutExportActions} from '../util/util'; 4 | import {methodType} from '../constants'; 5 | export const ResourceList = (props) => ( 6 | } bulkActions={false}> 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | 16 | export const ResourceCreate = (props) => ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | 26 | export const ResourceEdit = (props) => ( 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/Role.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ArrayField, 4 | Create, 5 | Datagrid, 6 | Edit, 7 | List, 8 | required, 9 | Show, 10 | ShowButton, 11 | SimpleForm, 12 | Tab, 13 | TabbedShowLayout, 14 | TextField, 15 | TextInput, 16 | EditButton 17 | } from "react-admin"; 18 | import {WithoutExportActions} from "../util/util"; 19 | import CheckButton from "./CheckButton"; 20 | 21 | export const RoleList = (props) => ( 22 | } bulkActionButtons={false}> 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | 30 | export const RoleEdit = (props) => ( 31 | 32 | 33 | 34 | 35 | 36 | 37 | ) 38 | 39 | export const RoleShow = (props) => ( 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | {/**/} 49 | {/**/} 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | ); 70 | 71 | export const RoleCreate = (props) => ( 72 | 73 | 74 | 75 | 76 | 77 | ); 78 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/admin/User.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ArrayField, 4 | BooleanField, 5 | BooleanInput, 6 | ChipField, 7 | Create, 8 | Datagrid, 9 | Edit, 10 | EditButton, 11 | Filter, 12 | FormDataConsumer, 13 | List, 14 | ReferenceArrayField, 15 | ReferenceArrayInput, 16 | required, 17 | SelectArrayInput, 18 | Show, 19 | ShowButton, 20 | SimpleForm, 21 | SimpleShowLayout, 22 | SingleFieldList, 23 | TextField, 24 | TextInput 25 | } from "react-admin"; 26 | import PasswordInput from "./PasswordInput"; 27 | import { Field } from 'react-final-form'; 28 | import {WithoutExportActions} from "../util/util"; 29 | 30 | const UserFilter = (props) => ( 31 | 32 | 33 | 34 | ); 35 | 36 | export const UserList = (props) => ( 37 | } bulkActionButtons={false} filters={}> 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | 52 | const PasswordRestInput = () => 53 | export const UserCreate = (props) => ( 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | ); 65 | 66 | export const UserEdit = (props) => ( 67 | 68 | 69 | 70 | 71 | 72 | 73 | {({formData, ...rest}) => formData.modifyPassword && 74 | 75 | } 76 | 77 | 78 | 79 | 80 | 81 | 82 | ); 83 | 84 | export const UserShow = (props) => ( 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/authProvider.js: -------------------------------------------------------------------------------- 1 | import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK,AUTH_GET_PERMISSIONS,fetchUtils } from "react-admin"; 2 | import {url} from "./constants"; 3 | import store from "storejs"; 4 | /** 5 | * add token before request 6 | * @param url 7 | * @param options 8 | * @returns {*} 9 | */ 10 | export const httpClient = (urlPath, options = {}) => { 11 | if (!options.headers) { 12 | options.headers = new Headers({Accept: 'application/json'}); 13 | } 14 | const token = store.get('token'); 15 | if (token && token !== 'undefined') { 16 | options.headers.set('Authorization', `Bearer ${token}`); 17 | } 18 | options.credentials = 'same-origin'; 19 | options.mode = 'cors'; 20 | return fetchUtils.fetchJson(urlPath, options); 21 | } 22 | 23 | export const authProvider = (type, params) => { 24 | if (type === AUTH_LOGIN) { 25 | const { username, password } = params; 26 | const request = new Request(url + "/auth", { 27 | method: "POST", 28 | mode: "cors", 29 | body: JSON.stringify({ username, password }), 30 | headers: new Headers({ "Content-Type": "application/json" }) 31 | }); 32 | return fetch(request) 33 | .then(response => { 34 | if (response.status < 200 || response.status >= 300) { 35 | throw new Error(response.statusText); 36 | } 37 | return response.json(); 38 | }) 39 | .then(({ token, roleName }) => { 40 | store.set("token", token); 41 | store.set("permissions", roleName); 42 | }); 43 | } 44 | if (type === AUTH_LOGOUT) { 45 | store.remove("token"); 46 | store.remove("permissions"); 47 | return Promise.resolve(); 48 | } 49 | if (type === AUTH_ERROR) { 50 | const { status } = params; 51 | if (status === 401 || status === 403) { 52 | store.remove("token"); 53 | return Promise.reject(); 54 | } 55 | return Promise.resolve(); 56 | } 57 | if (type === AUTH_CHECK) { 58 | return store.get("token") ? Promise.resolve() : Promise.reject(); 59 | } 60 | 61 | if (type === AUTH_GET_PERMISSIONS) { 62 | return Promise.resolve(store.get("permissions")); 63 | } 64 | 65 | return Promise.reject("Unkown method"); 66 | }; 67 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/configuration/Configuration.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Card from '@material-ui/core/Card'; 4 | import CardContent from '@material-ui/core/CardContent'; 5 | import Button from '@material-ui/core/Button'; 6 | import { translate, changeLocale, Title } from 'react-admin'; 7 | import withStyles from '@material-ui/core/styles/withStyles'; 8 | import compose from 'recompose/compose'; 9 | import { changeTheme } from './actions'; 10 | 11 | const styles = { 12 | label: { width: '10em', display: 'inline-block' }, 13 | button: { margin: '1em' }, 14 | }; 15 | 16 | const Configuration = ({ 17 | classes, 18 | theme, 19 | locale, 20 | changeTheme, 21 | changeLocale, 22 | translate, 23 | }) => ( 24 | 25 | 26 | <CardContent> 27 | <div className={classes.label}>{translate('pos.theme.name')}</div> 28 | <Button 29 | variant="raised" 30 | className={classes.button} 31 | color={theme === 'light' ? 'primary' : 'default'} 32 | onClick={() => changeTheme('light')} 33 | > 34 | {translate('pos.theme.light')} 35 | </Button> 36 | <Button 37 | variant="raised" 38 | className={classes.button} 39 | color={theme === 'dark' ? 'primary' : 'default'} 40 | onClick={() => changeTheme('dark')} 41 | > 42 | {translate('pos.theme.dark')} 43 | </Button> 44 | </CardContent> 45 | <CardContent> 46 | <div className={classes.label}>{translate('pos.language')}</div> 47 | <Button 48 | variant="raised" 49 | className={classes.button} 50 | color={locale === 'en' ? 'primary' : 'default'} 51 | onClick={() => changeLocale('en')} 52 | > 53 | en 54 | </Button> 55 | <Button 56 | variant="raised" 57 | className={classes.button} 58 | color={locale === 'fr' ? 'primary' : 'default'} 59 | onClick={() => changeLocale('fr')} 60 | > 61 | fr 62 | </Button> 63 | </CardContent> 64 | </Card> 65 | ); 66 | 67 | const mapStateToProps = state => ({ 68 | theme: state.theme, 69 | locale: state.i18n.locale, 70 | }); 71 | 72 | const enhance = compose( 73 | connect( 74 | mapStateToProps, 75 | { 76 | changeLocale, 77 | changeTheme, 78 | } 79 | ), 80 | translate, 81 | withStyles(styles) 82 | ); 83 | 84 | export default enhance(Configuration); -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/constants.js: -------------------------------------------------------------------------------- 1 | import store from "storejs"; 2 | 3 | const api_url = function () { 4 | let api_url = "//" + window.location.host; 5 | if (process.env.NODE_ENV === 'development') { 6 | // api_url = "//" + window.location.hostname + ":8081" 7 | api_url = "https://www.jdatastudio.com" 8 | } 9 | return api_url; 10 | } 11 | 12 | // function getCookie(name) { 13 | // var arr = document.cookie.match(new RegExp("(^| )" + name + "=([^;]*)(;|$)")); 14 | // if (arr != null) return unescape(arr[2]); 15 | // return null; 16 | // } 17 | export const methodType = () => [ 18 | {id: 'POST', name: '创建'}, 19 | {id: 'GET', name: '查询'}, 20 | {id: 'PUT', name: '更新'}, 21 | {id: 'DELETE', name: '删除'}, 22 | ]; 23 | 24 | export const jdatastudioComponent = () => [ 25 | {id: 'Text', name: '文本'}, 26 | {id: 'Number', name: '数字'}, 27 | {id: 'Select', name: '选择'}, 28 | {id: 'Reference', name: '引用'}, 29 | {id: 'Date', name: '日期'}, 30 | {id: 'Image', name: '图片'}, 31 | {id: 'File', name: '文件'}, 32 | // {id: 'DateTime', name: '日期时间'}, 33 | ]; 34 | export const sensitiveType = () => [ 35 | {id: 'nonsensitive', name: '非敏感数据'}, 36 | {id: 'sensitive', name: '其他敏感数据'}, 37 | {id: 'mobile', name: '手机'}, 38 | {id: 'card', name: '银行卡号'}, 39 | {id: 'id', name: '身份证号'}, 40 | ] 41 | 42 | 43 | export const dbType = () => [ 44 | {id: 'mysql', name: 'MySql'}, 45 | {id: 'elasticsearch', name: 'Elasticsearch'}, 46 | {id: 'mongo', name: 'Mongodb'}, 47 | ]; 48 | 49 | export const operatorType = () => [ 50 | {id: 'eq', name: '等于'}, 51 | {id: 'gte', name: '大于等于'}, 52 | {id: 'gt', name: '大于'}, 53 | {id: 'lte', name: '小于等于'}, 54 | {id: 'lt', name: '小于'}, 55 | {id: 'like', name: '模糊匹配'}, 56 | {id: 'in', name: '存在于(,分隔)'}, 57 | ]; 58 | export const isLogin = store.get('token'); 59 | export const url = api_url(); 60 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/CRUDCreate.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Create, SimpleForm } from "react-admin"; 3 | import { renderInput } from "./renderInput"; 4 | /** 5 | * create page with simple form layout 6 | * 7 | * contains validators 8 | * @param props 9 | * @constructor 10 | */ 11 | export const CRUDCreate = props => { 12 | const resource = props.options; 13 | return ( 14 | <Create {...props} title={resource.label}> 15 | <SimpleForm redirect={"list"}> 16 | {resource.fields.filter(field => field.showInCreate).map(renderInput)} 17 | </SimpleForm> 18 | </Create> 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/CRUDEdit.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import {renderInput} from "./renderInput"; 3 | import {Edit, EditActions, SimpleForm} from "react-admin"; 4 | /** 5 | * edit page with the record value 6 | * @param props 7 | * @constructor 8 | */ 9 | export const CRUDEdit = props => { 10 | const resource = props.options; 11 | return ( 12 | <Edit 13 | undoable={false} 14 | actions={<EditActions options={props}/>} 15 | {...props} 16 | title={resource.label} 17 | > 18 | <SimpleForm redirect={resource.redirect}> 19 | {resource.fields.filter(field => field.showInEdit).map(renderInput)} 20 | </SimpleForm> 21 | </Edit> 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/CRUDList.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from "react"; 2 | import { 3 | Datagrid, 4 | List, 5 | Pagination, 6 | ShowButton, 7 | EditButton, 8 | Filter 9 | } from "react-admin"; 10 | 11 | import { renderField } from "./renderField"; 12 | import { renderFilter } from "./renderFilter"; 13 | const CRUDPagination = props => ( 14 | <Pagination rowsPerPageOptions={[10, 25, 50, 100]} {...props} /> 15 | ); 16 | 17 | export const CRUDList = props => { 18 | const resource = props.options; 19 | return ( 20 | <List 21 | {...props} 22 | pagination={<CRUDPagination />} 23 | filters={<CRUDFilter {...props} />} 24 | title={resource.label} 25 | bulkActionButtons={false} 26 | > 27 | <Datagrid> 28 | {resource.fields.filter(field => field.showInList).map(renderField)} 29 | <ShowButton /> 30 | {resource.u ? <EditButton /> : <Fragment />} 31 | </Datagrid> 32 | </List> 33 | ); 34 | }; 35 | 36 | /** 37 | * filter 38 | * contains all the domain and the full text search input 39 | * @param props 40 | * @constructor 41 | */ 42 | const CRUDFilter = props => { 43 | const resource = props.options; 44 | return ( 45 | <Filter {...props}> 46 | {resource.fields.filter(field => field.showInFilter).map(renderFilters)} 47 | </Filter> 48 | ); 49 | }; 50 | 51 | const renderFilters = field => { 52 | let filter = renderFilter(field); 53 | // if (field.alwaysOn) { 54 | // return React.cloneElement(filter, { alwaysOn: true }); 55 | // } 56 | return filter; 57 | }; 58 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/CRUDShow.js: -------------------------------------------------------------------------------- 1 | import React, {Fragment} from "react"; 2 | import {Show, Tab, TabbedShowLayout, ReferenceManyField, Datagrid, EditButton, ShowButton} from "react-admin"; 3 | import {renderField} from "./renderField"; 4 | 5 | export const CRUDShow = props => { 6 | const resource = props.options; 7 | return ( 8 | <Show {...props} title={resource.label}> 9 | <TabbedShowLayout> 10 | <Tab label="Detail"> 11 | {resource.fields.filter(field => field.showInShow).map(renderField)} 12 | {resource.related ? resource.related.map(renderRelation) : null} 13 | </Tab> 14 | </TabbedShowLayout> 15 | </Show> 16 | ); 17 | }; 18 | 19 | const renderRelation = entity => ( 20 | <ReferenceManyField 21 | key={"rl" + entity.id} 22 | reference={"api/" + entity.name} 23 | target={entity.relatedTarget} 24 | addLabel={false} 25 | > 26 | <Datagrid> 27 | {entity.fields.filter(field => field.showInList).map(renderField)} 28 | <ShowButton/> 29 | {entity.u ? <EditButton/> : <Fragment/>} 30 | </Datagrid> 31 | </ReferenceManyField> 32 | ); 33 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/index.js: -------------------------------------------------------------------------------- 1 | export { CRUDList } from "./CRUDList"; 2 | export { CRUDEdit } from "./CRUDEdit"; 3 | export { CRUDCreate } from "./CRUDCreate"; 4 | export { CRUDShow } from "./CRUDShow"; 5 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/renderField.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | BooleanField, 4 | FileField, 5 | EmailField, 6 | ImageField, 7 | ReferenceField, 8 | SelectField, 9 | TextField, 10 | UrlField 11 | } from "react-admin"; 12 | import {url} from "../constants"; 13 | 14 | export const renderField = field => { 15 | switch (field.component) { 16 | case "Text": 17 | return renderTextField(field); 18 | case "Select": 19 | return renderSelectField(field); 20 | case "Reference": 21 | return renderReferenceField(field); 22 | case "Boolean": 23 | return renderBooleanField(field); 24 | case "Image": 25 | return renderImageField(field); 26 | case "File": 27 | return renderFileField(field); 28 | default: 29 | return renderTextField(field); 30 | } 31 | }; 32 | 33 | const renderSelectField = field => ( 34 | <SelectField 35 | key={field.name} 36 | source={field.name} 37 | label={field.label} 38 | choices={field.choices} 39 | sortable={field.sortable} 40 | /> 41 | ); 42 | 43 | const renderTextField = field => 44 | field.type === "email" ? ( 45 | <EmailField 46 | key={field.name} 47 | label={field.label} 48 | source={field.name} 49 | sortable={field.sortable} 50 | /> 51 | ) : field.type === "url" ? ( 52 | <UrlField 53 | key={field.name} 54 | label={field.label} 55 | source={field.name} 56 | sortable={field.sortable} 57 | /> 58 | ) : ( 59 | <TextField 60 | key={field.name} 61 | label={field.label} 62 | source={field.name} 63 | sortable={field.sortable} 64 | /> 65 | ); 66 | 67 | const renderReferenceField = field => ( 68 | <ReferenceField 69 | key={field.name} 70 | label={field.label} 71 | source={field.name} 72 | reference={"api/" + field.reference} 73 | link="show" 74 | sortable={field.sortable} 75 | > 76 | <TextField source={field.referenceOptionText}/> 77 | </ReferenceField> 78 | ); 79 | 80 | const renderBooleanField = field => ( 81 | <BooleanField 82 | key={field.name} 83 | label={field.label} 84 | source={field.name} 85 | sortable={field.sortable} 86 | /> 87 | ); 88 | 89 | const renderImageField = field => ( 90 | <ImageField 91 | key={field.name} 92 | label={field.label} 93 | source={field.name + ".src"} 94 | title={field.name + ".title"} 95 | /> 96 | ); 97 | 98 | const renderFileField = field => ( 99 | <FileField 100 | key={field.name} 101 | label={field.label} 102 | source={field.name+".src"} 103 | target="_blank" 104 | title={field.name + ".title"} 105 | /> 106 | ); -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/renderFilter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | AutocompleteInput, 4 | ReferenceArrayInput, 5 | SelectArrayInput, 6 | AutocompleteArrayInput, 7 | DateInput, 8 | NumberInput, 9 | BooleanInput, 10 | ReferenceInput, 11 | SelectInput, 12 | TextInput 13 | } from "react-admin"; 14 | 15 | export const renderFilter = field => { 16 | switch (field.component) { 17 | case "Text": 18 | return renderTextFilter(field); 19 | case "Select": 20 | return renderSelectFilter(field); 21 | case "Reference": 22 | return renderReferenceFilter(field); 23 | case "Boolean": 24 | return renderBooleanFilter(field); 25 | case "Number": 26 | return renderNumberFilter(field); 27 | case "Date": 28 | return renderDateRangeFilter(field); 29 | default: 30 | return renderTextFilter(field); 31 | } 32 | }; 33 | 34 | const renderDateRangeFilter = (field) => ( 35 | [<DateInput key={field.id + '_gte'} label={field.label + '开始'} source={field.name + '_gte'} 36 | options={{locale: 'zh-hans'}} alwaysOn={field.alwaysOn}/>, 37 | <DateInput key={field.id + '_lte'} label={field.label + '截止'} source={field.name + '_lte'} 38 | options={{locale: 'zh-hans'}} alwaysOn={field.alwaysOn}/>] 39 | ) 40 | 41 | const renderNumberFilter = (field) => ( 42 | [<NumberInput key={field.id + '_gte'} source={field.name + '_gte'} label={field.label + ' 大于等于'} 43 | alwaysOn={field.alwaysOn}/>, 44 | <NumberInput key={field.id + '_lte'} source={field.name + '_lte'} label={field.label + ' 小于等于'} 45 | alwaysOn={field.alwaysOn}/>] 46 | ) 47 | 48 | const renderTextFilter = field => ( 49 | <TextInput 50 | key={field.name} 51 | label={field.label} 52 | source={ 53 | field.isFilter && field.fuzzyQuery ? field.name + "_like" : field.name 54 | } 55 | type={field.type} 56 | alwaysOn={field.alwaysOn} 57 | resettable 58 | /> 59 | ); 60 | 61 | 62 | const renderSelectFilter = field => ( 63 | field.multiFilter ? 64 | <AutocompleteArrayInput 65 | key={field.name} 66 | label={field.label} 67 | source={field.name} 68 | choices={field.choices} 69 | alwaysOn={field.alwaysOn} 70 | /> : <AutocompleteInput 71 | key={field.name} 72 | label={field.label} 73 | source={field.name} 74 | choices={field.choices} 75 | alwaysOn={field.alwaysOn} 76 | /> 77 | ); 78 | 79 | 80 | const renderBooleanFilter = field => ( 81 | <BooleanInput 82 | key={field.name} 83 | label={field.label} 84 | source={field.name} 85 | alwaysOn={field.alwaysOn} 86 | /> 87 | ); 88 | 89 | const renderReferenceFilter = field => ( 90 | field.multiFilter ? 91 | <ReferenceArrayInput 92 | key={field.name} 93 | label={field.label} 94 | source={field.name} 95 | reference={"api/" + field.reference} 96 | alwaysOn={field.alwaysOn} 97 | > 98 | <AutocompleteArrayInput optionText={field.referenceOptionText}/> 99 | </ReferenceArrayInput> : <ReferenceInput 100 | key={field.name} 101 | label={field.label} 102 | source={field.name} 103 | reference={"api/" + field.reference} 104 | alwaysOn={field.alwaysOn} 105 | > 106 | <SelectInput optionText={field.referenceOptionText}/> 107 | </ReferenceInput> 108 | ); 109 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/crud/renderInput.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | AutocompleteInput, 4 | BooleanInput, 5 | email, 6 | maxLength, 7 | maxValue, 8 | minLength, 9 | minValue, 10 | number, 11 | ReferenceInput, 12 | required, 13 | SelectInput, 14 | TextInput, 15 | ImageInput, 16 | ImageField, 17 | FileInput, 18 | FileField 19 | } from "react-admin"; 20 | 21 | export const renderInput = field => { 22 | switch (field.component) { 23 | case "Text": 24 | return renderTextInput(field); 25 | case "Select": 26 | return renderSelectInput(field); 27 | case "Reference": 28 | return renderReferenceInput(field); 29 | case "Boolean": 30 | return renderBooleanInput(field); 31 | case "Image": 32 | return renderImageInput(field); 33 | case "File": 34 | return renderFileInput(field); 35 | default: 36 | return renderTextInput(field); 37 | } 38 | }; 39 | 40 | const renderTextInput = field => ( 41 | <TextInput 42 | key={field.name} 43 | label={field.label} 44 | source={ 45 | field.isFilter && field.fuzzyQuery ? field.name + "_like" : field.name 46 | } 47 | type={field.type} 48 | defaultValue={field.defaultValue} 49 | validate={generateValidators(field)} 50 | resettable 51 | /> 52 | ); 53 | 54 | const renderSelectInput = field => ( 55 | <SelectInput 56 | key={field.name} 57 | label={field.label} 58 | source={field.name} 59 | choices={field.choices} 60 | defaultValue={field.defaultValue} 61 | validate={generateValidators(field)} 62 | /> 63 | ); 64 | 65 | const renderBooleanInput = field => ( 66 | <BooleanInput 67 | key={field.name} 68 | label={field.label} 69 | source={field.name} 70 | defaultValue={field.defaultValue} 71 | /> 72 | ); 73 | 74 | const renderReferenceInput = field => ( 75 | <ReferenceInput 76 | key={field.name} 77 | label={field.label} 78 | source={field.name} 79 | reference={field.reference} 80 | validate={generateValidators(field)} 81 | > 82 | <AutocompleteInput optionText={field.referenceOptionText} /> 83 | </ReferenceInput> 84 | ); 85 | 86 | const renderImageInput = field => ( 87 | <ImageInput key={field.name} 88 | label={field.label} 89 | source={field.name} accept="image/*"> 90 | <ImageField source="src" title="title" /> 91 | </ImageInput> 92 | ) 93 | 94 | const renderFileInput = field => ( 95 | <FileInput key={field.name} 96 | label={field.label} 97 | source={field.name} accept="application/*"> 98 | <FileField source="src" title="title" /> 99 | </FileInput> 100 | ) 101 | 102 | /** 103 | * generateValidators 104 | * @param {*} field 105 | */ 106 | const generateValidators = field => { 107 | const validators = []; 108 | if (field.isFilter) return validators; 109 | if (field.required) validators.push(required()); 110 | if (field.type === "email" && field.component === "Text") 111 | validators.push(email()); 112 | if (field.component === "Number") { 113 | validators.push(number()); 114 | if (field.minValue) { 115 | validators.push(minValue(field.minValue)); 116 | } 117 | if (field.maxValue) { 118 | validators.push(maxValue(field.maxValue)); 119 | } 120 | } 121 | if (field.minLength) validators.push(minLength(field.minLength)); 122 | if (field.maxLength) validators.push(maxLength(field.maxLength)); 123 | return validators; 124 | }; 125 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/i18n/ra-language-chinese.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ra: { 3 | action: { 4 | delete: '删除', 5 | remove: '删除选项', 6 | show: '显示', 7 | list: '列表', 8 | save: '保存', 9 | create: '新建', 10 | edit: '编辑', 11 | cancel: '取消', 12 | refresh: '刷新', 13 | add_filter: '添加搜索条件', 14 | add: '增加选项', 15 | remove_filter: '删除搜索条件', 16 | back: '返回', 17 | export: '下载', 18 | sort: '排序', 19 | }, 20 | boolean: { 21 | true: '是', 22 | false: '否', 23 | }, 24 | page: { 25 | list: '%{name} 列表', 26 | edit: '%{name} #%{id}', 27 | show: '%{name} #%{id}', 28 | create: '新建 %{name}', 29 | delete: '删除 %{name} #%{id}', 30 | dashboard: '控制台', 31 | not_found: '没有找到', 32 | loading: '正在加载页面', 33 | }, 34 | input: { 35 | file: { 36 | upload_several: 37 | '拖动一些文件或点击上传.', 38 | upload_single: '拖动一个文件或点击上传.', 39 | }, 40 | image: { 41 | upload_several: '拖动一些图片文件或点击上传.', 42 | upload_single: '拖动一个图片文件或点击上传.', 43 | }, 44 | }, 45 | message: { 46 | yes: '是', 47 | no: '否', 48 | are_you_sure: '你确定?', 49 | about: '关于', 50 | not_found: 51 | '输入了一个错误的URL,或者非法的超链接.', 52 | invalid_form: '表单数据校验不通过,请检查', 53 | loading: '正在加载页面', 54 | }, 55 | navigation: { 56 | no_results: '找不到记录', 57 | page_out_of_boundaries: '%{page}不在范围内', 58 | page_out_from_end: '超过最后1页', 59 | page_out_from_begin: '超出第一页', 60 | page_range_info: '%{offsetBegin}-%{offsetEnd} 一共 %{total}', 61 | page_rows_per_page: '每页条数:', 62 | next: '下一页', 63 | prev: '上一页', 64 | }, 65 | auth: { 66 | auth_check_error: '请登录后继续', 67 | user_menu: '简档', 68 | username: '用户名', 69 | password: '密码', 70 | sign_in: '登录', 71 | sign_in_error: '验证失败,请重试', 72 | logout: '注销', 73 | }, 74 | notification: { 75 | updated: '记录已更新', 76 | created: '记录已新建', 77 | deleted: '记录已删除', 78 | item_doesnt_exist: '记录不存在', 79 | http_error: '服务器通讯错误', 80 | }, 81 | validation: { 82 | required: '必须', 83 | minLength: '至少%{min}个字符', 84 | maxLength: '必须不大于%{max}个字符', 85 | minValue: '至少是%{min}', 86 | maxValue: '不超过%{max}', 87 | number: '必须是数字', 88 | email: '必须是合法的email地址', 89 | }, 90 | }, 91 | }; -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/i18n/zh-hans.js: -------------------------------------------------------------------------------- 1 | import chineseMessages from "./ra-language-chinese"; 2 | 3 | export default { 4 | ...chineseMessages, 5 | resources: { 6 | users: { 7 | name: '用户', 8 | fields: { 9 | username: '用户名', 10 | password: '密码', 11 | confirmPassword: '确认密码', 12 | modifyPassword: '修改密码', 13 | roles: '角色', 14 | enabled: '是否有效', 15 | lastPasswordResetDate: '密码最后重置时间' 16 | } 17 | }, 18 | roles: { 19 | name: '角色', 20 | fields: { 21 | name: '角色名', 22 | permissions: '已有授权资源', 23 | users: '用户' 24 | }, 25 | createAuth: '新建授权', 26 | authlist: '权限列表' 27 | }, 28 | resources: { 29 | name: '资源', 30 | fields: { 31 | name: '资源名称', 32 | url: '路径', 33 | method: '方法', 34 | } 35 | }, 36 | _datasource: { 37 | name: '数据源', 38 | fields: { 39 | name: '数据源名称', 40 | url: 'URL', 41 | username: '用户名', 42 | password: '密码', 43 | dbType: '数据源类型', 44 | } 45 | }, 46 | _entity: { 47 | name: '对象', 48 | fields: { 49 | name: '对象名', 50 | label: '备注', 51 | jdatasource: '数据源' 52 | } 53 | }, 54 | _field: { 55 | name: '字段', 56 | fields: { 57 | name: '字段名', 58 | label: '标签', 59 | partOfPrimaryKey: '是否联合主键', 60 | maxLength: '最大长度', 61 | required: '是否必填', 62 | defaultValue: '默认值', 63 | dbColumnType: '原始字段类型', 64 | component: '组件类型', 65 | sensitiveType: '脱敏类型', 66 | reference: "引用对象", 67 | referenceOptionText: "引用对象显示字段", 68 | showInList: "显示在列表", 69 | showInShow: "显示在详情", 70 | showInEdit: "显示在编辑", 71 | showInCreate: "显示在新建", 72 | showInFilter: "以此字段过滤", 73 | alwaysOn: "过滤条件总显示", 74 | sortable: "支持排序", 75 | choices:"选项值", 76 | mainField:"主字段(引用字段根据此字段匹配)", 77 | multiFilter:"多选过滤" 78 | } 79 | }, 80 | _permission:{ 81 | name:"授权", 82 | fields:{ 83 | label:'标签', 84 | c:"新增", 85 | r:"读取", 86 | u:"编辑", 87 | d:"删除", 88 | filters:{ 89 | name:"过滤器", 90 | fields:{ 91 | field:"字段", 92 | operator:"操作符", 93 | value:"过滤值", 94 | } 95 | } 96 | } 97 | } 98 | } 99 | }; -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 5 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 6 | sans-serif; 7 | -webkit-font-smoothing: antialiased; 8 | -moz-osx-font-smoothing: grayscale; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 13 | monospace; 14 | } 15 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './App'; 5 | import * as serviceWorker from './serviceWorker'; 6 | 7 | ReactDOM.render(<App />, document.getElementById('root')); 8 | 9 | // If you want your app to work offline and load faster, you can change 10 | // unregister() to register() below. Note this comes with some pitfalls. 11 | // Learn more about service workers: http://bit.ly/CRA-PWA 12 | serviceWorker.unregister(); 13 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/logo.svg: -------------------------------------------------------------------------------- 1 | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"> 2 | <g fill="#61DAFB"> 3 | <path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/> 4 | <circle cx="420.9" cy="296.5" r="45.7"/> 5 | <path d="M520.5 78.1z"/> 6 | </g> 7 | </svg> 8 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/sys/DataSource.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | Create, 4 | Datagrid, 5 | Edit, 6 | EditButton, 7 | List, 8 | required, 9 | SelectField, 10 | SelectInput, 11 | SimpleForm, 12 | TextField, 13 | TextInput 14 | } from "react-admin"; 15 | import {WithoutExportActions} from "../util/util"; 16 | import {dbType} from "../constants"; 17 | import SyncButton from "./SyncButton"; 18 | 19 | export const DataSourceList = (props) => ( 20 | <List {...props} actions={<WithoutExportActions/>} bulkActionButtons={false}> 21 | <Datagrid> 22 | <TextField source="name"/> 23 | <TextField source="url"/> 24 | <SelectField source="dbType" choices={dbType()}/> 25 | <SyncButton/> 26 | <EditButton/> 27 | </Datagrid> 28 | </List> 29 | ); 30 | 31 | export const DataSourceEdit = (props) => ( 32 | <Edit {...props}> 33 | <SimpleForm> 34 | <SelectInput source="dbType" validate={[required()]} choices={dbType()}/> 35 | <TextInput source="name" validate={[required()]}/> 36 | <TextInput source="url" validate={[required()]}/> 37 | <TextInput source="username" validate={[required()]}/> 38 | <TextInput source="password" validate={[required()]}/> 39 | </SimpleForm> 40 | </Edit> 41 | 42 | ) 43 | 44 | export const DataSourceCreate = (props) => ( 45 | <Create {...props}> 46 | <SimpleForm redirect="list"> 47 | <TextInput source="name" validate={[required()]}/> 48 | <SelectInput source="dbType" validate={[required()]} choices={dbType()}/> 49 | <TextInput source="url" validate={[required()]}/> 50 | <TextInput source="username" validate={[required()]}/> 51 | <TextInput source="password" validate={[required()]}/> 52 | </SimpleForm> 53 | </Create> 54 | ); 55 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/sys/Entity.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | AutocompleteInput, 4 | BooleanField, 5 | Datagrid, 6 | Edit, 7 | EditButton, 8 | Filter, 9 | FormTab, 10 | List, 11 | ReferenceInput, 12 | ReferenceManyField, 13 | TabbedForm, 14 | TextField, 15 | TextInput 16 | } from "react-admin"; 17 | import {WithoutExportActions} from "../util/util"; 18 | 19 | const EntityFilter = (props) => ( 20 | <Filter {...props}> 21 | <ReferenceInput source="jdatasource" reference="_datasource" alwaysOn> 22 | <AutocompleteInput optionText="name"/> 23 | </ReferenceInput> 24 | </Filter> 25 | ) 26 | 27 | export const EntityList = (props) => ( 28 | <List {...props} actions={<WithoutExportActions/>} bulkActionButtons={false} filters={<EntityFilter />}> 29 | <Datagrid> 30 | <TextField source="name"/> 31 | <TextField source="label"/> 32 | <EditButton/> 33 | </Datagrid> 34 | </List> 35 | ); 36 | 37 | export const EntityEdit = (props) => ( 38 | <Edit {...props}> 39 | <TabbedForm> 40 | <FormTab label="对象信息"> 41 | <TextInput disabled source="name"/> 42 | <TextInput source="label" resettable/> 43 | </FormTab> 44 | <FormTab label="字段" path="fields"> 45 | <ReferenceManyField 46 | addLabel={false} 47 | reference="_field" 48 | target="eid" 49 | > 50 | <Datagrid> 51 | <TextField label="字段名称" source="name"/> 52 | <TextField label="字段标签" source="label"/> 53 | <BooleanField label="是否主键" source="partOfPrimaryKey"/> 54 | <EditButton /> 55 | </Datagrid> 56 | </ReferenceManyField> 57 | </FormTab> 58 | </TabbedForm> 59 | </Edit> 60 | ); 61 | 62 | 63 | -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/sys/Field.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | AutocompleteInput, 4 | BooleanInput, 5 | Edit, 6 | FormDataConsumer, 7 | FormTab, 8 | NumberInput, 9 | ReferenceInput, 10 | required, 11 | SelectInput, 12 | TabbedForm, 13 | TextInput 14 | } from "react-admin"; 15 | import {jdatastudioComponent, sensitiveType} from "../constants"; 16 | 17 | const redirect = (basePath, id, data) => `/_entity/` + id.substr(0, id.indexOf("_")) + `/fields`; 18 | 19 | 20 | export const FieldEdit = (props) => ( 21 | <Edit {...props}> 22 | <TabbedForm redirect={redirect}> 23 | <FormTab label="字段"> 24 | <TextInput disabled source="name"/> 25 | <SelectInput source="component" choices={jdatastudioComponent()}/> 26 | <TextInput source="label" validate={[required()]}/> 27 | <FormDataConsumer> 28 | {({formData, ...rest}) => formData.component === 'Reference' && 29 | <ReferenceInput source="reference" label="引用对象" reference="_entity"> 30 | <AutocompleteInput optionText="name"/> 31 | </ReferenceInput> 32 | } 33 | </FormDataConsumer> 34 | <FormDataConsumer> 35 | {({formData, ...rest}) => formData.component === 'Reference' && formData.reference && 36 | <ReferenceInput source="referenceOptionText" label="显示的字段" reference="_field" filter={{ eid: formData.reference }}> 37 | <AutocompleteInput optionValue="name" optionText="label"/> 38 | </ReferenceInput> 39 | } 40 | </FormDataConsumer> 41 | <FormDataConsumer> 42 | {({formData, ...rest}) => formData.component === 'Select' && 43 | <TextInput multiline source="choicesStr" label="选项,value|name"/> 44 | } 45 | </FormDataConsumer> 46 | <BooleanInput source="mainField"/> 47 | <BooleanInput source="partOfPrimaryKey"/> 48 | <SelectInput source="sensitiveType" choices={sensitiveType()}/> 49 | </FormTab> 50 | <FormTab label="列表"> 51 | <BooleanInput source="showInList"/> 52 | <BooleanInput source="sortable"/> 53 | <BooleanInput source="showInShow"/> 54 | </FormTab> 55 | <FormTab label="筛选"> 56 | <BooleanInput source="showInFilter"/> 57 | <BooleanInput source="alwaysOn"/> 58 | <FormDataConsumer> 59 | {({formData, ...rest}) => ['Reference','Select'].includes(formData.component) && 60 | <BooleanInput source="multiFilter" label={"多选过滤"}/> 61 | } 62 | </FormDataConsumer> 63 | </FormTab> 64 | <FormTab label="编辑"> 65 | <BooleanInput source="showInEdit"/> 66 | <BooleanInput source="showInCreate"/> 67 | <NumberInput source="maxLength"/> 68 | <BooleanInput source="required"/> 69 | <TextInput source="defaultValue"/> 70 | <TextInput disabled source="dbColumnType"/> 71 | </FormTab> 72 | </TabbedForm> 73 | </Edit> 74 | 75 | ) -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/sys/SyncButton.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from "react"; 2 | import PropTypes from "prop-types"; 3 | import {connect} from "react-redux"; 4 | import Button from "@material-ui/core/Button"; 5 | import NotificationSync from "@material-ui/icons/Sync"; 6 | import {showNotification as showNotificationAction} from "react-admin"; 7 | import {push as pushAction} from "react-router-redux"; 8 | import {url} from "../constants"; 9 | import {httpClient} from "./../authProvider"; 10 | class SyncButton extends Component { 11 | handleSync = () => { 12 | const {record, showNotification} = this.props; 13 | httpClient(url + `/_datasource/sync/${record.id}`, {method: 'GET'}) 14 | .then(() => { 15 | showNotification('同步成功!'); 16 | }) 17 | .catch((e) => { 18 | console.error(e); 19 | showNotification(e.message, 'warning') 20 | }); 21 | } 22 | 23 | render() { 24 | return ( 25 | <Button onClick={this.handleSync}> 26 | <NotificationSync/> 27 | 同步表结构 28 | </Button> 29 | ); 30 | } 31 | } 32 | 33 | SyncButton.propTypes = { 34 | push: PropTypes.func, 35 | record: PropTypes.object, 36 | showNotification: PropTypes.func, 37 | }; 38 | 39 | export default connect(null, { 40 | showNotification: showNotificationAction, 41 | push: pushAction, 42 | })(SyncButton); -------------------------------------------------------------------------------- /jdatastudio-admin-app/src/util/util.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | CardActions, 4 | CreateButton 5 | } from "react-admin"; 6 | 7 | export const WithoutExportActions = ({ 8 | basePath, 9 | currentSort, 10 | displayedFilters, 11 | exporter, 12 | filters, 13 | filterValues, 14 | resource, 15 | showFilter 16 | }) => ( 17 | <CardActions> 18 | {filters && React.cloneElement(filters, { 19 | resource, 20 | showFilter, 21 | displayedFilters, 22 | filterValues, 23 | context: 'button', 24 | }) } 25 | <CreateButton basePath={basePath}/> 26 | </CardActions> 27 | ); -------------------------------------------------------------------------------- /jdatastudio-admin/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | *.class 3 | !.mvn/wrapper/maven-wrapper.jar 4 | 5 | ### STS ### 6 | .apt_generated 7 | .classpath 8 | .factorypath 9 | .project 10 | .settings 11 | .springBeans 12 | .sts4-cache 13 | 14 | ### IntelliJ IDEA ### 15 | .idea 16 | *.iws 17 | *.iml 18 | *.ipr 19 | 20 | ### NetBeans ### 21 | /nbproject/private/ 22 | /build/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/Application.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; 6 | //import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 7 | import org.springframework.context.annotation.ComponentScan; 8 | 9 | /** 10 | * Created by gongxinyi on 2018-11-01. 11 | */ 12 | //@EnableDiscoveryClient 13 | @ComponentScan(basePackages = {"com.dataserver.connector", "com.dataserver.admin", "com.dataserver.dataapi"}) 14 | @SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) 15 | public class Application { 16 | public static void main(String[] args) { 17 | SpringApplication.run(Application.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/config/Constants.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.config; 2 | 3 | /** 4 | * 关键字 5 | * 6 | * @author gongxinyi 7 | * @date 2018-09-27 8 | */ 9 | public class Constants { 10 | public static final String SYS_COL_DS = "_datasource"; 11 | public static final String SYS_COL_ENTITY = "_entity"; 12 | public static final String SYS_COL_ROLE = "_role"; 13 | public static final String ApiStandard = "apiStandard"; 14 | public static final String ApiStandard_postgrest = "postgrest"; 15 | public static final String ApiStandard_jsonserver = "jsonserver"; 16 | public static final String delimiter = "__"; 17 | public static final String id = "id"; 18 | public static final String _id = "_id"; 19 | 20 | public static final String ENTITY_NAME_PREFIX = "e"; 21 | public static final String FIELD_NAME_PREFIX = "f"; 22 | } 23 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/config/HttpsConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.config; 2 | 3 | import org.apache.catalina.Context; 4 | import org.apache.catalina.connector.Connector; 5 | import org.apache.tomcat.util.descriptor.web.SecurityCollection; 6 | import org.apache.tomcat.util.descriptor.web.SecurityConstraint; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; 9 | import org.springframework.boot.web.servlet.server.ServletWebServerFactory; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.context.annotation.Profile; 13 | 14 | @Profile("aliyun") 15 | @Configuration 16 | public class HttpsConfiguration { 17 | @Value("${http-port}") 18 | private int port; 19 | 20 | @Value("${server.port}") 21 | private int sslPort; 22 | 23 | @Bean 24 | public ServletWebServerFactory servletContainer() { 25 | TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory() { 26 | @Override 27 | protected void postProcessContext(Context context) { 28 | SecurityConstraint securityConstraint = new SecurityConstraint(); 29 | securityConstraint.setUserConstraint("CONFIDENTIAL"); 30 | SecurityCollection collection = new SecurityCollection(); 31 | collection.addPattern("/*"); 32 | securityConstraint.addCollection(collection); 33 | context.addConstraint(securityConstraint); 34 | } 35 | }; 36 | tomcat.addAdditionalTomcatConnectors(redirectConnector()); 37 | return tomcat; 38 | } 39 | 40 | private Connector redirectConnector() { 41 | Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); 42 | connector.setScheme("http"); 43 | connector.setPort(port); 44 | connector.setSecure(false); 45 | connector.setRedirectPort(sslPort); 46 | return connector; 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/config/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.config; 2 | 3 | import com.dataserver.admin.util.SnowFlake; 4 | import com.dataserver.api.schema.DbTypeEnum; 5 | import com.dataserver.api.schema.ISchemaService; 6 | import com.dataserver.connector.elasticsearch.EsSchemaService; 7 | import com.dataserver.connector.mongodb.MongoSchemaService; 8 | import com.dataserver.connector.mysql.MySqlSchemaService; 9 | import com.fasterxml.jackson.annotation.JsonInclude; 10 | import com.fasterxml.jackson.databind.ObjectMapper; 11 | import com.fasterxml.jackson.databind.module.SimpleModule; 12 | import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.core.env.ConfigurableEnvironment; 18 | import org.springframework.http.CacheControl; 19 | import org.springframework.http.converter.HttpMessageConverter; 20 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 21 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 22 | import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 23 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 24 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; 25 | import org.springframework.web.servlet.resource.VersionResourceResolver; 26 | 27 | import java.text.SimpleDateFormat; 28 | import java.util.HashMap; 29 | import java.util.List; 30 | import java.util.Map; 31 | import java.util.concurrent.TimeUnit; 32 | 33 | @Slf4j 34 | @Configuration 35 | public class MvcConfig implements WebMvcConfigurer { 36 | @Autowired 37 | private ConfigurableEnvironment env; 38 | 39 | @Bean 40 | public WebMvcConfigurer corsConfigurer() { 41 | return new WebMvcConfigurerAdapter() { 42 | @Override 43 | public void addCorsMappings(CorsRegistry registry) { 44 | registry.addMapping("/**") 45 | .allowedOrigins("*") 46 | .allowedMethods("*") 47 | .allowedHeaders("*") 48 | .allowCredentials(true).maxAge(3600); 49 | } 50 | }; 51 | } 52 | 53 | @Override 54 | public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { 55 | MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter(); 56 | ObjectMapper objectMapper = jackson2HttpMessageConverter.getObjectMapper(); 57 | objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 58 | //不显示为null的字段 59 | objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); 60 | SimpleModule simpleModule = new SimpleModule(); 61 | simpleModule.addSerializer(Long.class, ToStringSerializer.instance); 62 | simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); 63 | objectMapper.registerModule(simpleModule); 64 | 65 | jackson2HttpMessageConverter.setObjectMapper(objectMapper); 66 | //放到第一个 67 | converters.add(0, jackson2HttpMessageConverter); 68 | } 69 | 70 | 71 | @Bean 72 | public Map<DbTypeEnum, Class<? extends ISchemaService>> schemaServiceMap() { 73 | Map<DbTypeEnum, Class<? extends ISchemaService>> schemaServiceMap = new HashMap<>(); 74 | schemaServiceMap.put(DbTypeEnum.mysql, MySqlSchemaService.class); 75 | schemaServiceMap.put(DbTypeEnum.mongo, MongoSchemaService.class); 76 | schemaServiceMap.put(DbTypeEnum.elasticsearch, EsSchemaService.class); 77 | return schemaServiceMap; 78 | } 79 | 80 | @Override 81 | public void addResourceHandlers(ResourceHandlerRegistry registry) { 82 | registry.addResourceHandler("/static/**") 83 | .addResourceLocations("classpath:/static/static/") 84 | .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS)) 85 | .resourceChain(false) 86 | .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**")); 87 | } 88 | 89 | @Bean 90 | public SnowFlake snowFlake() { 91 | return new SnowFlake(1, 1); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/exception/RestResponseEntityExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.exception; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.ControllerAdvice; 7 | import org.springframework.web.bind.annotation.ExceptionHandler; 8 | import org.springframework.web.context.request.WebRequest; 9 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 10 | 11 | /** 12 | * Created by gongxinyi on 2017-09-10. 13 | */ 14 | @ControllerAdvice 15 | public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler { 16 | 17 | @ExceptionHandler(value = {IllegalArgumentException.class, IllegalStateException.class}) 18 | protected ResponseEntity<Object> handleConflict(RuntimeException ex, WebRequest request) { 19 | String bodyOfResponse = "This should be application specific"; 20 | return handleExceptionInternal(ex, bodyOfResponse, 21 | new HttpHeaders(), HttpStatus.CONFLICT, request); 22 | } 23 | 24 | @ExceptionHandler(value = {RuntimeException.class}) 25 | protected ResponseEntity<Object> handleRuntimeException(RuntimeException ex, WebRequest request) { 26 | ex.printStackTrace(); 27 | String bodyOfResponse = "系统繁忙"; 28 | return handleExceptionInternal(ex, bodyOfResponse, 29 | new HttpHeaders(), HttpStatus.CONFLICT, request); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/model/Permission.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.model; 2 | 3 | import com.dataserver.admin.security.service.CRUDPermission; 4 | import com.dataserver.api.Filter; 5 | import com.dataserver.api.schema.Field; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Builder; 8 | import lombok.Data; 9 | import lombok.NoArgsConstructor; 10 | import org.mongodb.morphia.annotations.Embedded; 11 | import org.mongodb.morphia.annotations.Transient; 12 | 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | /** 17 | * @author gongxinyi 18 | * @date 2019-03-06 19 | */ 20 | @Builder 21 | @Data 22 | @AllArgsConstructor 23 | @NoArgsConstructor 24 | @Embedded 25 | public class Permission { 26 | private String id; 27 | private String name; 28 | private boolean r; 29 | private boolean u; 30 | private boolean c; 31 | private boolean d; 32 | 33 | /** 34 | * 数据权限表达式 35 | */ 36 | private List<Filter> filters; 37 | 38 | @Transient 39 | private List<Field> fields; 40 | 41 | @Transient 42 | private String relatedTarget; 43 | @Transient 44 | private Set<Permission> related; 45 | 46 | private String label; 47 | 48 | public String getEid() { 49 | return id.substring(id.lastIndexOf("_") + 1); 50 | } 51 | 52 | public boolean allow(CRUDPermission crudPermission) { 53 | return (crudPermission.equals(CRUDPermission.r) && r) || (crudPermission.equals(CRUDPermission.u) && u) || (crudPermission.equals(CRUDPermission.c) && c); 54 | } 55 | 56 | public boolean own() { 57 | return c || r || u || d; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/model/security/AuthorityName.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.model.security; 2 | 3 | /** 4 | * Created by gongxinyi on 2018-11-13. 5 | */ 6 | public enum AuthorityName { 7 | ROLE_USER, ROLE_ADMIN 8 | } 9 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/model/security/ListRoleConverter.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.model.security; 2 | 3 | import com.dataserver.admin.security.Role; 4 | import com.fasterxml.jackson.databind.util.StdConverter; 5 | 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | public class ListRoleConverter extends StdConverter<List<Role>, List<String>> { 10 | @Override 11 | public List<String> convert(List<Role> authorities) { 12 | return authorities.stream().map(Role::getId).collect(Collectors.toList()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/rest/ParamUtil.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.rest; 2 | 3 | import org.mongodb.morphia.query.FindOptions; 4 | 5 | import java.util.Map; 6 | 7 | /** 8 | * Created by gongxinyi on 2018-11-08. 9 | */ 10 | public class ParamUtil { 11 | public static FindOptions wrapFindOptions(Map<String, Object> params) { 12 | Integer start = Integer.parseInt(params.getOrDefault(_start, "0").toString()); 13 | Integer end = Integer.parseInt(params.getOrDefault(_end, "10").toString()); 14 | return new FindOptions().skip(start).limit(end-start); 15 | } 16 | 17 | public static final String _start = "_start"; 18 | public static final String _end = "_end"; 19 | public static final String _sort = "_sort"; 20 | public static final String _order = "_order"; 21 | } 22 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/rest/PermissionApi.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.rest; 2 | 3 | import com.dataserver.admin.model.Permission; 4 | import com.dataserver.admin.security.Role; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.access.prepost.PreAuthorize; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.Optional; 12 | import java.util.Set; 13 | 14 | @Slf4j 15 | @RestController 16 | @RequestMapping("_permission") 17 | public class PermissionApi { 18 | @Autowired 19 | RoleService roleService; 20 | 21 | @GetMapping("/{id}") 22 | @PreAuthorize("hasRole('ADMIN')") 23 | public ResponseEntity<Permission> getPermission(@PathVariable("id") String id) { 24 | String rid = id.substring(0, id.lastIndexOf("_")); 25 | Set<Permission> permissions = roleService.getRole(rid).getPermissions(); 26 | Optional<Permission> optionalPermission = permissions.stream().filter(permission -> permission.getId().equals(id)).findFirst(); 27 | return ResponseEntity.ok(optionalPermission.get()); 28 | } 29 | 30 | @PutMapping("/{id}") 31 | @PreAuthorize("hasRole('ADMIN')") 32 | public ResponseEntity editPermission(@PathVariable("id") String id,@RequestBody Permission permission) { 33 | String rid = id.substring(0, id.lastIndexOf("_")); 34 | Role role = roleService.getRole(rid); 35 | role.getPermissions().removeIf(permission1 -> permission1.getId().equals(id)); 36 | role.getPermissions().add(permission); 37 | roleService.saveRole(role); 38 | return ResponseEntity.ok().build(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/rest/RoleApi.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.rest; 2 | 3 | 4 | import com.dataserver.admin.model.Permission; 5 | import com.dataserver.admin.security.Role; 6 | import com.dataserver.admin.security.User; 7 | import com.dataserver.admin.sys.SysService; 8 | import com.dataserver.admin.util.JsonUtil; 9 | import com.dataserver.admin.util.ResponseUtil; 10 | import com.dataserver.api.schema.Entity; 11 | import com.dataserver.api.schema.Field; 12 | import lombok.extern.slf4j.Slf4j; 13 | import org.mongodb.morphia.query.FindOptions; 14 | import org.mongodb.morphia.query.Query; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.http.HttpStatus; 17 | import org.springframework.http.ResponseEntity; 18 | import org.springframework.security.access.prepost.PreAuthorize; 19 | import org.springframework.util.CollectionUtils; 20 | import org.springframework.web.bind.annotation.*; 21 | 22 | import java.util.*; 23 | import java.util.stream.Collectors; 24 | 25 | /** 26 | * @author gongxinyi 27 | * @date 2018-11-08 28 | */ 29 | @Slf4j 30 | @RestController 31 | @RequestMapping("roles") 32 | public class RoleApi { 33 | 34 | @Autowired 35 | SysService sysService; 36 | @Autowired 37 | RoleService roleService; 38 | 39 | @GetMapping 40 | @PreAuthorize("hasRole('ADMIN')") 41 | public ResponseEntity<List<Role>> getRoles(@RequestParam Map<String, Object> params) { 42 | FindOptions findOptions = ParamUtil.wrapFindOptions(params); 43 | Query<Role> query = sysService.getTenantDataStore().createQuery(Role.class); 44 | Long totalCount = query.count(); 45 | List<Role> roles = query.asList(findOptions); 46 | return ResponseEntity 47 | .status(HttpStatus.OK) 48 | .header("X-Total-Count", totalCount + "") 49 | .header("Access-Control-Expose-Headers", "X-Total-Count") 50 | .body(roles); 51 | } 52 | 53 | @PostMapping 54 | @PreAuthorize("hasRole('ADMIN')") 55 | public ResponseEntity<Role> addRole(@RequestBody Role role) { 56 | roleService.saveRole(role); 57 | return ResponseEntity 58 | .ok(role); 59 | } 60 | 61 | @GetMapping("/{id}") 62 | @PreAuthorize("hasRole('ADMIN')") 63 | public ResponseEntity<Role> getRole(@PathVariable("id") String id) { 64 | return ResponseEntity 65 | .ok(roleService.getRole(id)); 66 | } 67 | 68 | @PutMapping("/{roleId}/editPermission") 69 | @PreAuthorize("hasRole('ADMIN')") 70 | public ResponseEntity saveRolePermission(@PathVariable("roleId") String roleId, @RequestBody Map<String, String> params) { 71 | log.info("params:{}", params); 72 | Permission permission = JsonUtil.parseJson(JsonUtil.toJsonStr(params), Permission.class); 73 | roleService.editRolePermission(roleId, permission); 74 | return ResponseEntity.ok().build(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/rest/RoleService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.rest; 2 | 3 | 4 | import com.dataserver.admin.config.Constants; 5 | import com.dataserver.admin.model.Permission; 6 | import com.dataserver.admin.security.Role; 7 | import com.dataserver.admin.security.User; 8 | import com.dataserver.admin.sys.SequenceService; 9 | import com.dataserver.admin.sys.SysService; 10 | import com.dataserver.api.schema.Entity; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Service; 13 | import org.springframework.util.StringUtils; 14 | 15 | import java.util.List; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * Created by gongxinyi on 2018-11-12. 21 | */ 22 | @Service 23 | public class RoleService { 24 | @Autowired 25 | SysService sysService; 26 | @Autowired 27 | SequenceService sequenceService; 28 | 29 | public void editRolePermission(String roleId, Permission permission) { 30 | Role role = sysService.getTenantDataStore().get(Role.class, roleId); 31 | role.getPermissions().removeIf(ownerPermission -> ownerPermission.getId().equals(permission.getId())); 32 | role.getPermissions().add(permission); 33 | sysService.getTenantDataStore().save(role); 34 | } 35 | 36 | public void saveRole(Role role){ 37 | if(StringUtils.isEmpty(role.getId())){ 38 | role.setId(sequenceService.getNextSequence(Constants.SYS_COL_ROLE + Constants._id)); 39 | } 40 | sysService.getTenantDataStore().save(role); 41 | } 42 | 43 | public Role getRole(String id){ 44 | Role role = sysService.getTenantDataStore().get(Role.class, id); 45 | 46 | List<User> users = sysService.getTenantDataStore().createQuery(User.class).field("roles").hasThisOne(new Role(id)).asList(); 47 | role.setUsers(users); 48 | 49 | List<Entity> entities = sysService.getTenantDataStore().createQuery(Entity.class).asList(); 50 | 51 | Set<Permission> permissions = entities.stream() 52 | .filter(entity -> !role.getPermissions().stream() 53 | .filter(permission -> permission.getEid().equals(entity.getId())) 54 | .findAny() 55 | .isPresent()) 56 | .map(entity -> wrapPermission(entity, id)) 57 | .collect(Collectors.toSet()); 58 | 59 | role.getPermissions().addAll(permissions); 60 | return role; 61 | } 62 | 63 | private Permission wrapPermission(Entity entity, String rid) { 64 | return Permission.builder().id(rid + "_" + entity.getId()).name(entity.getId()).label(entity.getLabel()).build(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/rest/UserApi.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.rest; 2 | 3 | 4 | import com.dataserver.admin.security.User; 5 | import com.dataserver.admin.sys.SysService; 6 | import org.mongodb.morphia.query.FindOptions; 7 | import org.mongodb.morphia.query.Query; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.security.access.prepost.PreAuthorize; 12 | import org.springframework.util.StringUtils; 13 | import org.springframework.web.bind.annotation.*; 14 | 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | /** 19 | * Created by gongxinyi on 2018-11-08. 20 | */ 21 | @RestController 22 | @RequestMapping("users") 23 | public class UserApi { 24 | 25 | 26 | @Autowired 27 | UserService userService; 28 | @Autowired 29 | SysService sysService; 30 | @GetMapping 31 | @PreAuthorize("hasRole('ADMIN')") 32 | public ResponseEntity<List<User>> getUsers(@RequestParam Map<String, Object> params) { 33 | FindOptions findOptions = ParamUtil.wrapFindOptions(params); 34 | String username = (String) params.get("username"); 35 | Query<User> query = sysService.getTenantDataStore().createQuery(User.class); 36 | 37 | if (!StringUtils.isEmpty(username)) { 38 | query.field("username").contains(username); 39 | } 40 | Long totalCount = query.count(); 41 | List<User> users = query.asList(findOptions); 42 | return ResponseEntity 43 | .status(HttpStatus.OK) 44 | .header("X-Total-Count", totalCount + "") 45 | .header("Access-Control-Expose-Headers", "X-Total-Count") 46 | .body(users); 47 | } 48 | 49 | @PostMapping 50 | @PreAuthorize("hasRole('ADMIN')") 51 | public ResponseEntity<User> addUser(@RequestBody final User user) { 52 | userService.addUser(user); 53 | return ResponseEntity.ok(user); 54 | } 55 | 56 | @PutMapping("/{id}") 57 | @PreAuthorize("hasRole('ADMIN')") 58 | public ResponseEntity<User> editUser(@PathVariable("id") String id, @RequestBody User user) { 59 | userService.editUser(id,user); 60 | return ResponseEntity.ok(user); 61 | } 62 | 63 | @GetMapping("/{id}") 64 | @PreAuthorize("hasRole('ADMIN')") 65 | public ResponseEntity getUser(@PathVariable("id") String id) { 66 | return ResponseEntity.ok(userService.getUser(id)); 67 | } 68 | 69 | // @DeleteMapping("/{id}") 70 | // @PreAuthorize("hasRole('ADMIN')") 71 | // public ResponseEntity deleteUser(@PathVariable("id") Long id) { 72 | // userService.deleteUser(id); 73 | // return ResponseEntity.ok().build(); 74 | // } 75 | } 76 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/schema/DataServiceProxy.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.schema; 2 | 3 | import com.dataserver.api.IDataService; 4 | import com.dataserver.api.schema.DbTypeEnum; 5 | import com.dataserver.api.schema.ISchemaService; 6 | import com.dataserver.api.schema.JDataSource; 7 | import com.dataserver.dataapi.data.AbstractRequestWrapper; 8 | import com.dataserver.dataapi.data.ApiStandard; 9 | import com.dataserver.dataapi.data.ResponseWrapper; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.context.ApplicationContext; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.Map; 15 | 16 | /** 17 | * @author gongxinyi 18 | * @date 2018-10-31 19 | */ 20 | @Component 21 | public class DataServiceProxy { 22 | 23 | @Autowired 24 | ApplicationContext applicationContext; 25 | 26 | @Autowired 27 | Map<DbTypeEnum, Class<? extends ISchemaService>> schemaServiceMap; 28 | @Autowired 29 | Map<DbTypeEnum, Class<? extends IDataService>> dataServiceMap; 30 | @Autowired 31 | Map<ApiStandard, Class<? extends AbstractRequestWrapper>> apiRequestWrapperMap; 32 | @Autowired 33 | Map<ApiStandard, Class<? extends ResponseWrapper>> apiResponseWrapperMap; 34 | 35 | public IDataService getDataService(JDataSource jDataSource) { 36 | return applicationContext.getBean(dataServiceMap.get(jDataSource.getDbType())); 37 | } 38 | 39 | public AbstractRequestWrapper getRequestWrapper(ApiStandard apiStandard) { 40 | return applicationContext.getBean(apiRequestWrapperMap.get(apiStandard), AbstractRequestWrapper.class); 41 | } 42 | 43 | public ResponseWrapper getResponseWrapper(ApiStandard apiStandard) { 44 | return applicationContext.getBean(apiResponseWrapperMap.get(apiStandard), ResponseWrapper.class); 45 | } 46 | public ISchemaService getSchemaService(JDataSource jDataSource) { 47 | return applicationContext.getBean(schemaServiceMap.get(jDataSource.getDbType())); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/schema/DataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.schema; 2 | 3 | import com.dataserver.admin.config.Constants; 4 | import com.dataserver.admin.sys.SequenceService; 5 | import com.dataserver.admin.sys.SysService; 6 | import com.dataserver.api.schema.JDataSource; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.util.StringUtils; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * Created by gongxinyi on 2018/11/27. 16 | */ 17 | @Service 18 | public class DataSourceService { 19 | @Autowired 20 | SequenceService sequenceService; 21 | @Autowired 22 | SysService sysService; 23 | 24 | @Autowired 25 | SchemaService schemaService; 26 | 27 | public JDataSource save(JDataSource jDataSource) { 28 | if (StringUtils.isEmpty(jDataSource.getId())) { 29 | String id = sequenceService.getNextSequence(Constants.SYS_COL_DS + Constants._id); 30 | jDataSource.setId(id); 31 | schemaService.syncSchemas(jDataSource); 32 | } 33 | sysService.getTenantDataStore().save(jDataSource); 34 | return jDataSource; 35 | } 36 | 37 | public JDataSource get(String id) { 38 | return sysService.getTenantDataStore().get(JDataSource.class,id); 39 | } 40 | 41 | public List<JDataSource> getAll() { 42 | return sysService.getTenantDataStore().createQuery(JDataSource.class).asList(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/schema/DatasourceApi.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.schema; 2 | 3 | import com.dataserver.api.schema.JDataSource; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.access.prepost.PreAuthorize; 9 | import org.springframework.web.bind.annotation.*; 10 | 11 | import java.util.List; 12 | import java.util.Map; 13 | 14 | /** 15 | * Created by gongxinyi on 2018/11/27. 16 | */ 17 | @Slf4j 18 | @RestController 19 | @RequestMapping("/_datasource") 20 | public class DatasourceApi { 21 | 22 | @Autowired 23 | DataSourceService dataSourceService; 24 | 25 | @Autowired 26 | SchemaService schemaService; 27 | 28 | @PostMapping 29 | @PreAuthorize("hasRole('ADMIN')") 30 | public ResponseEntity addDataSource(@RequestBody final JDataSource jDataSource) { 31 | return ResponseEntity.ok(dataSourceService.save(jDataSource)); 32 | } 33 | 34 | @PutMapping(value = "/{id}") 35 | @PreAuthorize("hasRole('ADMIN')") 36 | public ResponseEntity editDataSource(@PathVariable("id") String id, @RequestBody JDataSource jDataSource) { 37 | jDataSource.setId(id); 38 | return ResponseEntity.ok(dataSourceService.save(jDataSource)); 39 | } 40 | 41 | @GetMapping("/{id}") 42 | @PreAuthorize("hasRole('ADMIN')") 43 | public ResponseEntity<JDataSource> findDataSource(@PathVariable("id") String id) { 44 | return ResponseEntity.ok(dataSourceService.get(id)); 45 | } 46 | 47 | @GetMapping 48 | @PreAuthorize("hasRole('ADMIN')") 49 | public ResponseEntity<List<JDataSource>> list(@RequestParam final Map<String, Object> params) { 50 | List<JDataSource> jDataSources = dataSourceService.getAll(); 51 | return ResponseEntity 52 | .status(HttpStatus.OK) 53 | .header("X-Total-Count", jDataSources.size() + "") 54 | .header("Access-Control-Expose-Headers", "X-Total-Count") 55 | .body(jDataSources); 56 | } 57 | 58 | @GetMapping("/sync/{id}") 59 | @PreAuthorize("hasRole('ADMIN')") 60 | public ResponseEntity syncSchemas(@PathVariable("id") String id) { 61 | synchronized (this) { 62 | schemaService.syncSchemas(dataSourceService.get(id)); 63 | } 64 | return ResponseEntity.ok().build(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/schema/SchemaService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.schema; 2 | 3 | import com.dataserver.admin.config.Constants; 4 | import com.dataserver.admin.sys.SequenceService; 5 | import com.dataserver.admin.sys.SysService; 6 | import com.dataserver.api.schema.Entity; 7 | import com.dataserver.api.schema.Field; 8 | import com.dataserver.api.schema.JDataSource; 9 | import org.mongodb.morphia.query.Query; 10 | import org.mongodb.morphia.query.UpdateOperations; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.CollectionUtils; 14 | import org.springframework.util.StringUtils; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.Optional; 19 | 20 | /** 21 | * Created by gongxinyi on 2018-11-19. 22 | */ 23 | @Component 24 | public class SchemaService { 25 | 26 | @Autowired 27 | DataServiceProxy dataServiceProxy; 28 | @Autowired 29 | SequenceService sequenceService; 30 | @Autowired 31 | SysService sysService; 32 | 33 | public Entity getEntity(String eid) { 34 | return sysService.getTenantDataStore().get(Entity.class, eid); 35 | } 36 | 37 | public Entity saveEntity(Entity entity) { 38 | sysService.getTenantDataStore().merge(entity); 39 | return entity; 40 | } 41 | 42 | public void syncSchemas(JDataSource jDataSource) { 43 | List<Entity> persistentEntities = getEntities(jDataSource); 44 | List<Entity> entities = dataServiceProxy.getSchemaService(jDataSource).getSchemas(jDataSource); 45 | List<Field> newFields = new ArrayList<>(); 46 | entities.forEach(outerEntity -> { 47 | Optional<Entity> optionalEntity = persistentEntities.stream() 48 | .filter(persistentEntity -> persistentEntity.getName().equals(outerEntity.getName())) 49 | .findAny(); 50 | if (!optionalEntity.isPresent()) { 51 | String id = sequenceService.getNextSequence(Constants.SYS_COL_ENTITY + Constants._id); 52 | outerEntity.setId(Constants.ENTITY_NAME_PREFIX + id); 53 | outerEntity.getFields().forEach(field -> field.setId(outerEntity.getId() + "_" + field.getName())); 54 | outerEntity.setJDataSource(jDataSource); 55 | sysService.getTenantDataStore().save(outerEntity); 56 | } else { 57 | Entity findEntity = optionalEntity.get(); 58 | outerEntity.getFields().forEach(outerField -> { 59 | if (!CollectionUtils.isEmpty(findEntity.getFields())) { 60 | Optional<Field> findField = findEntity.getFields().stream().filter(field -> field.getName().equals(outerField.getName())).findAny(); 61 | if (!findField.isPresent()) { 62 | Field field = outerField; 63 | field.setId(findEntity.getId() + "_" + field.getName()); 64 | newFields.add(field); 65 | } 66 | } 67 | }); 68 | if (!CollectionUtils.isEmpty(newFields)) { 69 | Query<Entity> updateQuery = sysService.getTenantDataStore().createQuery(Entity.class).field("id").equal(findEntity.getId()); 70 | UpdateOperations<Entity> ops = sysService.getTenantDataStore() 71 | .createUpdateOperations(Entity.class) 72 | .addToSet("fields", newFields); 73 | sysService.getTenantDataStore().update(updateQuery, ops); 74 | } 75 | } 76 | }); 77 | } 78 | 79 | public List<Entity> getEntities(JDataSource jDataSource) { 80 | if (StringUtils.isEmpty(jDataSource.getId())) { 81 | return sysService.getTenantDataStore().createQuery(Entity.class).asList(); 82 | }else{ 83 | return sysService.getTenantDataStore().createQuery(Entity.class).field("jDataSource").equal(jDataSource).asList(); 84 | } 85 | 86 | } 87 | 88 | public List<Entity> getEntities(String jDataSourceId) { 89 | JDataSource jDataSource = new JDataSource(); 90 | jDataSource.setId(jDataSourceId); 91 | return getEntities(jDataSource); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/JwtAuthenticationEntryPoint.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | import org.springframework.security.web.AuthenticationEntryPoint; 5 | import org.springframework.stereotype.Component; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.IOException; 10 | import java.io.Serializable; 11 | 12 | @Component 13 | public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable { 14 | 15 | private static final long serialVersionUID = -8970718410437077606L; 16 | 17 | @Override 18 | public void commence(HttpServletRequest request, 19 | HttpServletResponse response, 20 | AuthenticationException authException) throws IOException { 21 | // This is invoked when user tries to access a secured REST resource without supplying any credentials 22 | // We should just send a 401 Unauthorized response because there is no 'login page' to redirect to 23 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); 24 | } 25 | } -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/JwtAuthenticationRequest.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * Created by stephan on 20.03.16. 7 | */ 8 | public class JwtAuthenticationRequest implements Serializable { 9 | 10 | private static final long serialVersionUID = -8445943548965154778L; 11 | 12 | private String username; 13 | private String password; 14 | 15 | public JwtAuthenticationRequest() { 16 | super(); 17 | } 18 | 19 | public JwtAuthenticationRequest(String username, String password) { 20 | this.setUsername(username); 21 | this.setPassword(password); 22 | } 23 | 24 | public String getUsername() { 25 | return this.username; 26 | } 27 | 28 | public void setUsername(String username) { 29 | this.username = username; 30 | } 31 | 32 | public String getPassword() { 33 | return this.password; 34 | } 35 | 36 | public void setPassword(String password) { 37 | this.password = password; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/JwtAuthorizationTokenFilter.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | import io.jsonwebtoken.ExpiredJwtException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 8 | import org.springframework.security.core.context.SecurityContextHolder; 9 | import org.springframework.security.core.userdetails.UserDetails; 10 | import org.springframework.security.core.userdetails.UserDetailsService; 11 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.web.filter.OncePerRequestFilter; 14 | 15 | import javax.servlet.FilterChain; 16 | import javax.servlet.ServletException; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | 21 | @Component 22 | public class JwtAuthorizationTokenFilter extends OncePerRequestFilter { 23 | 24 | private final Logger logger = LoggerFactory.getLogger(this.getClass()); 25 | 26 | private final UserDetailsService userDetailsService; 27 | private final JwtTokenUtil jwtTokenUtil; 28 | private final String tokenHeader; 29 | 30 | public JwtAuthorizationTokenFilter(UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) { 31 | this.userDetailsService = userDetailsService; 32 | this.jwtTokenUtil = jwtTokenUtil; 33 | this.tokenHeader = tokenHeader; 34 | } 35 | 36 | @Override 37 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { 38 | logger.debug("processing authentication for '{}'", request.getRequestURL()); 39 | 40 | final String requestHeader = request.getHeader(this.tokenHeader); 41 | 42 | String username = null; 43 | String authToken = null; 44 | if (requestHeader != null && requestHeader.startsWith("Bearer ")) { 45 | authToken = requestHeader.substring(7); 46 | try { 47 | username = jwtTokenUtil.getUsernameFromToken(authToken); 48 | } catch (IllegalArgumentException e) { 49 | logger.error("an error occured during getting username from token", e); 50 | } catch (ExpiredJwtException e) { 51 | logger.warn("the token is expired and not valid anymore", e); 52 | } 53 | } else { 54 | logger.warn("couldn't find bearer string, will ignore the header"); 55 | } 56 | 57 | logger.debug("checking authentication for user '{}'", username); 58 | if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { 59 | logger.debug("security context was null, so authorizating user"); 60 | 61 | // It is not compelling necessary to load the use details from the database. You could also store the information 62 | // in the token and read it from it. It's up to you ;) 63 | UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); 64 | 65 | // For simple validation it is completely sufficient to just check the token integrity. You don't have to call 66 | // the database compellingly. Again it's up to you ;) 67 | if (jwtTokenUtil.validateToken(authToken, userDetails)) { 68 | UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); 69 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); 70 | logger.debug("authorizated user '{}', setting security context", username); 71 | SecurityContextHolder.getContext().setAuthentication(authentication); 72 | } 73 | } 74 | 75 | chain.doFilter(request, response); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/JwtUser.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | 7 | import java.util.Collection; 8 | import java.util.Date; 9 | 10 | /** 11 | * Created by stephan on 20.03.16. 12 | */ 13 | public class JwtUser implements UserDetails { 14 | 15 | private final String username; 16 | private final String password; 17 | private final Collection<? extends GrantedAuthority> authorities; 18 | private final boolean enabled; 19 | private final Date lastPasswordResetDate; 20 | 21 | public JwtUser( 22 | String username, 23 | String password, Collection<? extends GrantedAuthority> authorities, 24 | boolean enabled, 25 | Date lastPasswordResetDate 26 | ) { 27 | this.username = username; 28 | this.password = password; 29 | this.authorities = authorities; 30 | this.enabled = enabled; 31 | this.lastPasswordResetDate = lastPasswordResetDate; 32 | } 33 | 34 | @Override 35 | public String getUsername() { 36 | return username; 37 | } 38 | 39 | @JsonIgnore 40 | @Override 41 | public boolean isAccountNonExpired() { 42 | return true; 43 | } 44 | 45 | @JsonIgnore 46 | @Override 47 | public boolean isAccountNonLocked() { 48 | return true; 49 | } 50 | 51 | @JsonIgnore 52 | @Override 53 | public boolean isCredentialsNonExpired() { 54 | return true; 55 | } 56 | 57 | @JsonIgnore 58 | @Override 59 | public String getPassword() { 60 | return password; 61 | } 62 | 63 | @Override 64 | public Collection<? extends GrantedAuthority> getAuthorities() { 65 | return authorities; 66 | } 67 | 68 | @Override 69 | public boolean isEnabled() { 70 | return enabled; 71 | } 72 | 73 | @JsonIgnore 74 | public Date getLastPasswordResetDate() { 75 | return lastPasswordResetDate; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/JwtUserFactory.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class JwtUserFactory { 11 | 12 | private JwtUserFactory() { 13 | } 14 | 15 | public static JwtUser create(User user) { 16 | return new JwtUser( 17 | user.getUsername(), 18 | user.getPassword(), 19 | mapToGrantedAuthorities(user.getRoles()), 20 | user.getEnabled(), 21 | user.getLastPasswordResetDate() 22 | ); 23 | } 24 | 25 | private static List<GrantedAuthority> mapToGrantedAuthorities(List<Role> roles) { 26 | return roles.stream() 27 | .map(role -> new SimpleGrantedAuthority(role.getId())) 28 | .collect(Collectors.toList()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/Role.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | import com.dataserver.admin.model.Permission; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | import org.mongodb.morphia.annotations.*; 7 | import org.springframework.util.CollectionUtils; 8 | 9 | import java.util.HashSet; 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | @Entity(value = "_role", noClassnameStored = true) 14 | @Data 15 | @NoArgsConstructor 16 | public class Role { 17 | @Id 18 | private String id; 19 | private String name; 20 | @Transient 21 | @Reference 22 | private List<User> users; 23 | @Embedded 24 | private Set<Permission> permissions; 25 | 26 | public Set<Permission> getPermissions() { 27 | if (permissions == null) { 28 | permissions = new HashSet<>(); 29 | } 30 | return permissions; 31 | } 32 | 33 | public Role(String id) { 34 | this.id = id; 35 | } 36 | } -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/User.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security; 2 | 3 | import com.dataserver.admin.model.Permission; 4 | import com.dataserver.admin.model.security.ListRoleConverter; 5 | import com.fasterxml.jackson.annotation.JsonIgnore; 6 | import com.fasterxml.jackson.annotation.JsonProperty; 7 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 8 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 9 | import lombok.Data; 10 | import org.mongodb.morphia.annotations.Entity; 11 | import org.mongodb.morphia.annotations.Id; 12 | import org.mongodb.morphia.annotations.Reference; 13 | 14 | import java.util.Date; 15 | import java.util.List; 16 | import java.util.Set; 17 | import java.util.stream.Collectors; 18 | 19 | @Entity(value = "_user", noClassnameStored = true) 20 | @Data 21 | public class User { 22 | private String id; 23 | @Id 24 | private String username; 25 | @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) 26 | private String password; 27 | private String email; 28 | private Boolean enabled; 29 | private Date lastPasswordResetDate; 30 | @Reference 31 | @JsonDeserialize(contentAs = Role.class) 32 | @JsonSerialize(converter = ListRoleConverter.class) 33 | private List<Role> roles; 34 | 35 | public Set<Permission> getPermissions(){ 36 | return roles.stream().map(role -> role.getPermissions()).flatMap(Set::stream).collect(Collectors.toSet()); 37 | } 38 | public String getId() { 39 | return username; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/controller/AuthenticationException.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.controller; 2 | 3 | public class AuthenticationException extends RuntimeException { 4 | public AuthenticationException(String message, Throwable cause) { 5 | super(message, cause); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/repository/MongoUserRepository.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.repository; 2 | 3 | import com.dataserver.admin.security.User; 4 | import com.dataserver.admin.sys.JDataContext; 5 | import com.dataserver.admin.sys.SysService; 6 | import com.dataserver.admin.sys.Tenant; 7 | import com.dataserver.admin.sys.TenantUser; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.beans.factory.annotation.Value; 11 | import org.springframework.core.env.ConfigurableEnvironment; 12 | import org.springframework.core.env.Profiles; 13 | import org.springframework.stereotype.Component; 14 | 15 | /** 16 | * Created by gongxinyi on 2018/12/5. 17 | */ 18 | @Component 19 | @Slf4j 20 | public class MongoUserRepository implements UserRepository { 21 | @Autowired 22 | SysService sysService; 23 | 24 | @Autowired 25 | ConfigurableEnvironment env; 26 | 27 | @Value("${spring.data.mongodb.uri}") 28 | String uri; 29 | @Value("${jdatastudio.cloud}") 30 | boolean cloud; 31 | 32 | @Override 33 | public User findByUsername(String username) { 34 | 35 | Tenant tenant; 36 | 37 | if (cloud) { 38 | TenantUser tenantUser = sysService.getSysDataStore().get(TenantUser.class, username); 39 | if (tenantUser == null) { 40 | log.error("user not exists or exist!!! user :{}", username); 41 | return null; 42 | } 43 | tenant = tenantUser.getTenant(); 44 | } else { 45 | tenant = new Tenant(); 46 | tenant.setId("jdatastudio"); 47 | tenant.setConnectionStr(uri); 48 | } 49 | JDataContext jDataContext = new JDataContext(); 50 | jDataContext.setTenant(tenant); 51 | JDataContext.set(jDataContext); 52 | 53 | return sysService.getTenantDataStore().get(User.class, username); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.repository; 2 | 3 | 4 | import com.dataserver.admin.security.User; 5 | 6 | /** 7 | * Created by stephan on 20.03.16. 8 | */ 9 | public interface UserRepository { 10 | User findByUsername(String username); 11 | } 12 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/service/CRUDPermission.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.service; 2 | 3 | /** 4 | * Created by gongxinyi on 2017-08-26. 5 | */ 6 | public enum CRUDPermission { 7 | c, 8 | r, 9 | u, 10 | d 11 | } 12 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/service/JwtAuthenticationResponse.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.service; 2 | 3 | import com.dataserver.admin.model.security.AuthorityName; 4 | 5 | import java.io.Serializable; 6 | 7 | /** 8 | * Created by stephan on 20.03.16. 9 | */ 10 | public class JwtAuthenticationResponse implements Serializable { 11 | 12 | private static final long serialVersionUID = 1250166508152483573L; 13 | 14 | private final String token; 15 | private final AuthorityName roleName; 16 | 17 | public JwtAuthenticationResponse(String token, AuthorityName roleName) { 18 | this.token = token; 19 | this.roleName = roleName; 20 | } 21 | 22 | public String getToken() { 23 | return this.token; 24 | } 25 | 26 | public AuthorityName getRoleName() { 27 | return roleName; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/service/JwtUserDetailsService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.service; 2 | 3 | 4 | import com.dataserver.admin.security.JwtUserFactory; 5 | import com.dataserver.admin.security.User; 6 | import com.dataserver.admin.security.repository.UserRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.security.core.userdetails.UserDetailsService; 10 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 11 | import org.springframework.stereotype.Service; 12 | 13 | @Service 14 | public class JwtUserDetailsService implements UserDetailsService { 15 | 16 | @Autowired 17 | private UserRepository userRepository; 18 | 19 | @Override 20 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 21 | User user = userRepository.findByUsername(username); 22 | 23 | if (user == null) { 24 | throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username)); 25 | } else { 26 | return JwtUserFactory.create(user); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/service/SecurityService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.service; 2 | 3 | 4 | /** 5 | * current login user has the permission of an entity 6 | * Created by gongxinyi on 2017-08-30. 7 | */ 8 | public interface SecurityService { 9 | Boolean hasProtectedAccess(String entity, CRUDPermission permission); 10 | } 11 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/security/service/impl/SecurityServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.security.service.impl; 2 | 3 | import com.dataserver.admin.model.Permission; 4 | import com.dataserver.admin.model.security.AuthorityName; 5 | import com.dataserver.admin.security.service.CRUDPermission; 6 | import com.dataserver.admin.security.service.SecurityService; 7 | import com.dataserver.admin.sys.SysService; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.stereotype.Service; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | import java.util.Set; 15 | 16 | /** 17 | * @author gongxinyi 18 | * @date 2017-08-30 19 | */ 20 | @Service 21 | @Slf4j 22 | public class SecurityServiceImpl implements SecurityService { 23 | 24 | @Autowired 25 | SysService sysService; 26 | 27 | // TODO 权限授权还需要完善 28 | @Override 29 | public Boolean hasProtectedAccess(String eid, CRUDPermission permission) { 30 | Set<Permission> permissions = sysService.getPermissions(); 31 | Optional<Permission> result = permissions.stream() 32 | .filter(authPermission -> authPermission.getEid().equals(eid) && authPermission.allow(permission)) 33 | .findAny(); 34 | return result.isPresent(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/sys/JDataContext.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.sys; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * Created by gongxinyi on 2018/11/27. 7 | */ 8 | @Data 9 | public class JDataContext { 10 | private Tenant tenant; 11 | 12 | private static InheritableThreadLocal<JDataContext> threadLocal = new InheritableThreadLocal<JDataContext>() { 13 | @Override 14 | protected JDataContext initialValue() { 15 | return new JDataContext(); 16 | } 17 | }; 18 | 19 | public static JDataContext get() { 20 | return threadLocal.get(); 21 | } 22 | 23 | public static void set(JDataContext jDataContext) { 24 | threadLocal.set(jDataContext); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/sys/MorphiaFactory.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.sys; 2 | 3 | import com.mongodb.Mongo; 4 | import com.mongodb.MongoClient; 5 | import org.mongodb.morphia.Datastore; 6 | import org.mongodb.morphia.Morphia; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 9 | import org.springframework.boot.autoconfigure.mongo.MongoProperties; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | /** 14 | * Created by gongxinyi on 2018/11/26. 15 | */ 16 | @Configuration 17 | @ConditionalOnClass(Mongo.class) 18 | public class MorphiaFactory { 19 | 20 | @Autowired 21 | private Mongo mongo; 22 | 23 | @Autowired 24 | MongoProperties mongoProperties; 25 | 26 | @Bean 27 | public Datastore get() { 28 | Morphia morphia = new Morphia(); 29 | return morphia.createDatastore((MongoClient) mongo,mongoProperties.getMongoClientDatabase()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/sys/SequenceService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.sys; 2 | 3 | import com.mongodb.client.MongoCollection; 4 | import com.mongodb.client.model.FindOneAndUpdateOptions; 5 | import com.mongodb.client.model.ReturnDocument; 6 | import org.bson.Document; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * sequence util 12 | * <p> 13 | * getNextSequence base on mongo 14 | */ 15 | @Component 16 | public class SequenceService { 17 | 18 | public final static String SEQUENCE_COLLECTION = "_sequence"; 19 | 20 | @Autowired 21 | SysService sysService; 22 | 23 | private void createCountersCollection(MongoCollection countersCollection, String sequenceName) { 24 | 25 | Document document = new Document(); 26 | document.append("_id", sequenceName); 27 | document.append("seq", 0); 28 | countersCollection.insertOne(document); 29 | } 30 | 31 | /** 32 | * get next sequence 33 | * <p> 34 | * for this project backend use {entity+"_"+domain} name to keep identity 35 | * 36 | * @param sequenceName must identity for one collection 37 | * @return 38 | */ 39 | public String getNextSequence(String sequenceName) { 40 | MongoCollection<Document> countersCollection = sysService.getTenantCollection(SEQUENCE_COLLECTION); 41 | if (countersCollection.count() == 0) { 42 | createCountersCollection(countersCollection, sequenceName); 43 | } 44 | Document searchQuery = new Document("_id", sequenceName); 45 | Document increase = new Document("seq", 1); 46 | Document updateQuery = new Document("$inc", increase); 47 | Document result = countersCollection.findOneAndUpdate(searchQuery, updateQuery, new FindOneAndUpdateOptions().upsert(true).returnDocument(ReturnDocument.AFTER)); 48 | return result.get("seq").toString(); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/sys/SysService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.sys; 2 | 3 | import com.dataserver.admin.model.Permission; 4 | import com.dataserver.admin.model.security.AuthorityName; 5 | import com.dataserver.admin.security.User; 6 | import com.dataserver.api.Filter; 7 | import com.mongodb.MongoClient; 8 | import com.mongodb.MongoClientURI; 9 | import com.mongodb.client.MongoCollection; 10 | import io.jsonwebtoken.lang.Collections; 11 | import org.mongodb.morphia.Datastore; 12 | import org.mongodb.morphia.Morphia; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 15 | import org.springframework.security.core.GrantedAuthority; 16 | import org.springframework.security.core.context.SecurityContextHolder; 17 | import org.springframework.stereotype.Component; 18 | 19 | import java.util.*; 20 | import java.util.stream.Collectors; 21 | 22 | /** 23 | * 24 | */ 25 | @Component 26 | public class SysService { 27 | 28 | @Autowired 29 | MorphiaFactory morphiaFactory; 30 | 31 | /** 32 | * all the tenant cache the mongoClient 33 | */ 34 | @Autowired 35 | Map<String, MongoClient> uriMongoMap; 36 | 37 | public Datastore getSysDataStore() { 38 | return morphiaFactory.get(); 39 | } 40 | 41 | public Datastore getTenantDataStore() { 42 | 43 | // setJDataContext(); 44 | JDataContext jDataContext = JDataContext.get(); 45 | return getTenantDataStore(jDataContext.getTenant().getConnectionStr()); 46 | } 47 | 48 | public Datastore getTenantDataStore(Tenant tenant) { 49 | return getTenantDataStore(tenant.getConnectionStr()); 50 | } 51 | 52 | public Datastore getTenantDataStore(String mongoUri) { 53 | MongoClientURI mongoClientURI = getMongoClientURI(mongoUri); 54 | initMongoClient(mongoUri); 55 | return new Morphia().createDatastore(uriMongoMap.get(mongoUri), mongoClientURI.getDatabase()); 56 | } 57 | 58 | public MongoCollection getTenantCollection(String entity) { 59 | // setJDataContext(); 60 | JDataContext jDataContext = JDataContext.get(); 61 | MongoClientURI mongoClientURI = getMongoClientURI(jDataContext.getTenant().getConnectionStr()); 62 | initMongoClient(jDataContext.getTenant().getConnectionStr()); 63 | return uriMongoMap.get(jDataContext.getTenant().getConnectionStr()).getDatabase(mongoClientURI.getDatabase()).getCollection(entity); 64 | } 65 | 66 | public MongoClient initMongoClient(String uri) { 67 | if (!uriMongoMap.containsKey(uri)) { 68 | MongoClientURI mongoClientURI = getMongoClientURI(uri); 69 | MongoClient client = new MongoClient(mongoClientURI); 70 | uriMongoMap.put(uri, client); 71 | } 72 | return uriMongoMap.get(uri); 73 | } 74 | 75 | public MongoClientURI getMongoClientURI(String uri) { 76 | return new MongoClientURI(uri); 77 | } 78 | 79 | public List<String> getRoles() { 80 | UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication(); 81 | Collection<GrantedAuthority> grantedAuthorities = authentication.getAuthorities(); 82 | return grantedAuthorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); 83 | } 84 | 85 | public Set<Permission> getPermissions() { 86 | User user = getTenantDataStore().get(User.class, SecurityContextHolder.getContext().getAuthentication().getName()); 87 | return user.getPermissions(); 88 | } 89 | 90 | 91 | public List<Filter> getFilters(String eid) { 92 | for (Permission permission : getPermissions()) { 93 | if (eid.equals(permission.getEid()) && !Collections.isEmpty(permission.getFilters())) { 94 | return permission.getFilters(); 95 | } 96 | } 97 | return new ArrayList<>(); 98 | } 99 | 100 | public boolean isAdmin() { 101 | return getRoles().contains(AuthorityName.ROLE_ADMIN.toString()); 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/sys/Tenant.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.sys; 2 | 3 | import lombok.Data; 4 | import org.mongodb.morphia.annotations.Entity; 5 | import org.mongodb.morphia.annotations.Id; 6 | 7 | /** 8 | * Created by gongxinyi on 2018/11/27. 9 | */ 10 | @Data 11 | @Entity(value = "_tenant", noClassnameStored = true) 12 | public class Tenant { 13 | @Id 14 | private String id; 15 | private String connectionStr; 16 | } 17 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/sys/TenantUser.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.sys; 2 | 3 | import lombok.Data; 4 | import org.mongodb.morphia.annotations.Entity; 5 | import org.mongodb.morphia.annotations.Id; 6 | import org.mongodb.morphia.annotations.Reference; 7 | 8 | /** 9 | * Created by gongxinyi on 2018/12/5. 10 | */ 11 | @Entity(value = "_tenantUser", noClassnameStored = true) 12 | @Data 13 | public class TenantUser { 14 | @Id 15 | private String username; 16 | @Reference 17 | private Tenant tenant; 18 | } 19 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/util/JsonUtil.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.util; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerationException; 4 | import com.fasterxml.jackson.core.JsonParseException; 5 | import com.fasterxml.jackson.databind.DeserializationFeature; 6 | import com.fasterxml.jackson.databind.JavaType; 7 | import com.fasterxml.jackson.databind.JsonMappingException; 8 | import com.fasterxml.jackson.databind.ObjectMapper; 9 | 10 | import java.io.IOException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | /** 15 | * @author paul 16 | * @version V1.0 17 | * @description json工具类, 依赖jackson 18 | * @date 2017年7月10日 上午10:54:43 19 | * @update 2017年7月10日 上午10:54:43 20 | */ 21 | public class JsonUtil { 22 | private static ObjectMapper INSTANCE = new ObjectMapper(); 23 | 24 | static { 25 | INSTANCE.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 26 | } 27 | 28 | private JsonUtil() { 29 | } 30 | 31 | /** 32 | * @param obj 准备转换对象 33 | * @return 34 | * @throws JsonProcessingException 35 | * @description 对象转换成json字符串 36 | * @author paul 37 | * @date 2017年7月10日 上午10:54:50 38 | * @update 2017年7月10日 上午10:54:50 39 | * @version V1.0 40 | */ 41 | public static String toJsonStr(Object obj) { 42 | try { 43 | return INSTANCE.writeValueAsString(obj); 44 | } catch (JsonGenerationException e) { 45 | e.printStackTrace(); 46 | } catch (JsonMappingException e) { 47 | e.printStackTrace(); 48 | } catch (IOException e) { 49 | e.printStackTrace(); 50 | } 51 | return null; 52 | } 53 | 54 | /** 55 | * @param json 准备转换json 56 | * @param type 转换类型 57 | * @return 58 | * @throws Exception 转换异常 59 | * @description json字符串转换成对象 60 | * @author paul 61 | * @date 2017年7月10日 上午11:08:34 62 | * @update 2017年7月10日 上午11:08:34 63 | * @version V1.0 64 | */ 65 | @SuppressWarnings("unchecked") 66 | public static <T> T parseJson(String json, String type) { 67 | try { 68 | return (T) parseJson(json, Class.forName(type)); 69 | } catch (ClassNotFoundException e) { 70 | e.printStackTrace(); 71 | } catch (Exception e) { 72 | e.printStackTrace(); 73 | } 74 | return null; 75 | } 76 | 77 | /** 78 | * @param json 准备转换json 79 | * @param clazz 转换类型 80 | * @return 81 | * @description json字符串转换成对象 82 | * @author paul 83 | * @date 2017年7月10日 上午11:12:58 84 | * @update 2017年7月10日 上午11:12:58 85 | * @version V1.0 86 | */ 87 | public static <T> T parseJson(String json, Class<T> clazz) { 88 | try { 89 | return (T) INSTANCE.readValue(json, clazz); 90 | } catch (JsonParseException e) { 91 | e.printStackTrace(); 92 | } catch (JsonMappingException e) { 93 | e.printStackTrace(); 94 | } catch (IOException e) { 95 | e.printStackTrace(); 96 | } 97 | return null; 98 | } 99 | 100 | /** 101 | * @param json 准备转换json 102 | * @param clazz 集合元素类型 103 | * @return 104 | * @description json字符串转换成对象集合 105 | * @author paul 106 | * @date 2017年8月12日 下午1:28:27 107 | * @update 2017年8月12日 下午1:28:27 108 | * @version V1.0 109 | */ 110 | @SuppressWarnings("unchecked") 111 | public static <T> List<T> parseJsonList(String json, Class<T> clazz) { 112 | try { 113 | JavaType javaType = getCollectionType(ArrayList.class, clazz); 114 | return (List<T>) INSTANCE.readValue(json, javaType); 115 | } catch (IOException e) { 116 | e.printStackTrace(); 117 | } 118 | return null; 119 | } 120 | 121 | /** 122 | * @param collectionClass 集合类 123 | * @param elementClasses 集合元素类 124 | * @return 125 | * @description 获取泛型的ColloectionType 126 | * @author paul 127 | * @date 2017年8月12日 下午2:17:38 128 | * @update 2017年8月12日 下午2:17:38 129 | * @version V1.0 130 | */ 131 | private static JavaType getCollectionType(Class<?> collectionClass, Class<?>... elementClasses) { 132 | return INSTANCE.getTypeFactory().constructParametricType(collectionClass, elementClasses); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/util/ResponseUtil.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.util; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.http.ResponseEntity; 5 | 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | /** 10 | * Created by gongxinyi on 2019-05-08. 11 | */ 12 | public class ResponseUtil { 13 | public static ResponseEntity listToResponseEntity(Collection results, Long totalCount) { 14 | return ResponseEntity 15 | .status(HttpStatus.OK) 16 | .header("X-Total-Count", totalCount + "") 17 | .header("Access-Control-Expose-Headers", "X-Total-Count") 18 | .body(results); 19 | } 20 | 21 | public static ResponseEntity listToResponseEntity(Collection results) { 22 | return listToResponseEntity(results, new Long(results.size())); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/admin/util/SnowFlake.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin.util; 2 | 3 | /** 4 | * Created by gongxinyi on 2018-11-12. 5 | */ 6 | public class SnowFlake { 7 | 8 | /** 9 | * 起始的时间戳 10 | */ 11 | private final static long START_STMP = 1480166465631L; 12 | 13 | /** 14 | * 每一部分占用的位数 15 | */ 16 | private final static long SEQUENCE_BIT = 12; //序列号占用的位数 17 | private final static long MACHINE_BIT = 5; //机器标识占用的位数 18 | private final static long DATACENTER_BIT = 5;//数据中心占用的位数 19 | 20 | /** 21 | * 每一部分的最大值 22 | */ 23 | private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); 24 | private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); 25 | private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); 26 | 27 | /** 28 | * 每一部分向左的位移 29 | */ 30 | private final static long MACHINE_LEFT = SEQUENCE_BIT; 31 | private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; 32 | private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; 33 | 34 | private long datacenterId; //数据中心 35 | private long machineId; //机器标识 36 | private long sequence = 0L; //序列号 37 | private long lastStmp = -1L;//上一次时间戳 38 | 39 | public SnowFlake(long datacenterId, long machineId) { 40 | if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { 41 | throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); 42 | } 43 | if (machineId > MAX_MACHINE_NUM || machineId < 0) { 44 | throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); 45 | } 46 | this.datacenterId = datacenterId; 47 | this.machineId = machineId; 48 | } 49 | 50 | /** 51 | * 产生下一个ID 52 | * 53 | * @return 54 | */ 55 | public synchronized long nextId() { 56 | long currStmp = getNewstmp(); 57 | if (currStmp < lastStmp) { 58 | throw new RuntimeException("Clock moved backwards. Refusing to generate id"); 59 | } 60 | 61 | if (currStmp == lastStmp) { 62 | //相同毫秒内,序列号自增 63 | sequence = (sequence + 1) & MAX_SEQUENCE; 64 | //同一毫秒的序列数已经达到最大 65 | if (sequence == 0L) { 66 | currStmp = getNextMill(); 67 | } 68 | } else { 69 | //不同毫秒内,序列号置为0 70 | sequence = 0L; 71 | } 72 | 73 | lastStmp = currStmp; 74 | 75 | return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分 76 | | datacenterId << DATACENTER_LEFT //数据中心部分 77 | | machineId << MACHINE_LEFT //机器标识部分 78 | | sequence; //序列号部分 79 | } 80 | 81 | private long getNextMill() { 82 | long mill = getNewstmp(); 83 | while (mill <= lastStmp) { 84 | mill = getNewstmp(); 85 | } 86 | return mill; 87 | } 88 | 89 | private long getNewstmp() { 90 | return System.currentTimeMillis(); 91 | } 92 | 93 | public static void main(String[] args) { 94 | SnowFlake snowFlake = new SnowFlake(2, 3); 95 | 96 | for (int i = 0; i < (1 << 12); i++) { 97 | System.out.println(snowFlake.nextId()); 98 | } 99 | 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/Constants.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi; 2 | 3 | /** 4 | * 关键字 5 | * 6 | * @author gongxinyi 7 | * @date 2018-09-27 8 | */ 9 | public class Constants { 10 | public static final String SYS_COL_DS = "_datasource"; 11 | public static final String SYS_COL_ENTITY = "_entity"; 12 | public static final String ApiStandard = "apiStandard"; 13 | public static final String ApiStandard_postgrest = "postgrest"; 14 | public static final String ApiStandard_jsonserver = "jsonserver"; 15 | public static final String delimiter = "__"; 16 | public static final String id = "id"; 17 | public static final String q = "q"; 18 | 19 | public static final String _id = "_id"; 20 | 21 | public static final String ENTITY_NAME_PREFIX = "e"; 22 | public static final String FIELD_NAME_PREFIX = "f"; 23 | } 24 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import springfox.documentation.builders.PathSelectors; 6 | import springfox.documentation.builders.RequestHandlerSelectors; 7 | import springfox.documentation.service.ApiKey; 8 | import springfox.documentation.service.AuthorizationScope; 9 | import springfox.documentation.service.SecurityReference; 10 | import springfox.documentation.spi.DocumentationType; 11 | import springfox.documentation.spi.service.contexts.SecurityContext; 12 | import springfox.documentation.spring.web.plugins.Docket; 13 | import springfox.documentation.swagger2.annotations.EnableSwagger2; 14 | 15 | import java.util.Arrays; 16 | import java.util.List; 17 | 18 | @Configuration 19 | @EnableSwagger2 20 | public class SwaggerConfig { 21 | @Bean 22 | public Docket api() { 23 | return new Docket(DocumentationType.SWAGGER_2) 24 | .select() 25 | .apis(RequestHandlerSelectors.any()) 26 | .paths(PathSelectors.regex("^(?!auth).*$")) 27 | .build() 28 | .securitySchemes(securitySchemes()) 29 | .securityContexts(securityContexts()); 30 | } 31 | 32 | private List<ApiKey> securitySchemes() { 33 | return Arrays.asList(new ApiKey("Authorization", "Authorization", "header")); 34 | } 35 | private List<SecurityContext> securityContexts() { 36 | return Arrays.asList( 37 | SecurityContext.builder() 38 | .securityReferences(defaultAuth()) 39 | .forPaths(PathSelectors.regex("^(?!auth).*$")) 40 | .build() 41 | ); 42 | } 43 | 44 | List<SecurityReference> defaultAuth() { 45 | AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); 46 | AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; 47 | authorizationScopes[0] = authorizationScope; 48 | return Arrays.asList( 49 | new SecurityReference("Authorization", authorizationScopes)); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/SysConfig.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi; 2 | 3 | import com.aliyun.oss.OSSClient; 4 | import com.dataserver.api.IDataService; 5 | import com.dataserver.api.schema.DbTypeEnum; 6 | import com.dataserver.connector.elasticsearch.EsDataService; 7 | import com.dataserver.connector.mongodb.MongoDataService; 8 | import com.dataserver.connector.mysql.MySqlDataService; 9 | import com.dataserver.dataapi.data.AbstractRequestWrapper; 10 | import com.dataserver.dataapi.data.ApiStandard; 11 | import com.dataserver.dataapi.data.FileStorageApi; 12 | import com.dataserver.dataapi.data.ResponseWrapper; 13 | import com.dataserver.dataapi.jsonserver.JsonServerRequestWrapper; 14 | import com.dataserver.dataapi.jsonserver.JsonServerResponseWrapper; 15 | import com.dataserver.dataapi.postgrest.PostgrestRequestWrapper; 16 | import com.dataserver.dataapi.postgrest.PostgrestResponseWrapper; 17 | import org.springframework.beans.factory.BeanFactory; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.beans.factory.annotation.Value; 20 | import org.springframework.context.annotation.Bean; 21 | import org.springframework.stereotype.Component; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | /** 27 | * Created by gongxinyi on 2018-11-01. 28 | */ 29 | @Component 30 | public class SysConfig { 31 | 32 | @Bean 33 | public Map<ApiStandard, Class<? extends AbstractRequestWrapper>> apiRequestWrapperMap() { 34 | Map<ApiStandard, Class<? extends AbstractRequestWrapper>> apiRequestWrapperMap = new HashMap<>(); 35 | apiRequestWrapperMap.put(ApiStandard.postgrest, PostgrestRequestWrapper.class); 36 | apiRequestWrapperMap.put(ApiStandard.jsonserver, JsonServerRequestWrapper.class); 37 | return apiRequestWrapperMap; 38 | } 39 | 40 | @Bean 41 | public Map<ApiStandard, Class<? extends ResponseWrapper>> apiResponseWrapperMap() { 42 | Map<ApiStandard, Class<? extends ResponseWrapper>> apiResponseWrapperMap = new HashMap<>(); 43 | apiResponseWrapperMap.put(ApiStandard.postgrest, PostgrestResponseWrapper.class); 44 | apiResponseWrapperMap.put(ApiStandard.jsonserver, JsonServerResponseWrapper.class); 45 | return apiResponseWrapperMap; 46 | } 47 | 48 | @Bean 49 | public Map<DbTypeEnum, Class<? extends IDataService>> dataServiceMap() { 50 | Map<DbTypeEnum, Class<? extends IDataService>> dbTypeEnumClassMap = new HashMap<>(); 51 | dbTypeEnumClassMap.put(DbTypeEnum.mysql, MySqlDataService.class); 52 | dbTypeEnumClassMap.put(DbTypeEnum.mongo, MongoDataService.class); 53 | dbTypeEnumClassMap.put(DbTypeEnum.elasticsearch, EsDataService.class); 54 | return dbTypeEnumClassMap; 55 | } 56 | 57 | 58 | @Value("${jdatastudio.storage.endpoint}") 59 | String endpoint; 60 | 61 | @Bean 62 | public OSSClient cosClient() { 63 | // Endpoint以杭州为例,其它Region请按实际情况填写。 64 | // 阿里云主账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM账号进行API访问或日常运维,请登录 https://ram.console.aliyun.com 创建RAM账号。 65 | String accessKeyId = "***"; 66 | String accessKeySecret = "***"; 67 | 68 | // 创建OSSClient实例。 69 | OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret); 70 | return ossClient; 71 | } 72 | 73 | @Autowired 74 | private BeanFactory beanFactory; 75 | 76 | @Value("${jdatastudio.storage.strategy}") 77 | String storageStrategy; 78 | 79 | @Bean 80 | public FileStorageApi fileStorageApi() { 81 | return (FileStorageApi) beanFactory.getBean(storageStrategy); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/AbstractRequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | import com.dataserver.api.Filter; 4 | import com.dataserver.api.Pagination; 5 | import com.dataserver.api.QueryParams; 6 | import com.dataserver.api.Sort; 7 | import com.dataserver.dataapi.Constants; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author gongxinyi 14 | * @date 2018-11-02 15 | */ 16 | public abstract class AbstractRequestWrapper { 17 | protected abstract Pagination wrapPagination(Map<String, Object> requestParams); 18 | 19 | protected abstract List<Sort> wrapSort(Map<String, Object> requestParams); 20 | 21 | protected abstract String[] wrapSelect(Map<String, Object> requestParams); 22 | 23 | protected abstract List<Filter> wrapFilters(Map<String, Object> requestParams); 24 | 25 | protected String wrapQ(Map<String, Object> requestParams){ 26 | String q = (String)requestParams.get(Constants.q); 27 | requestParams.remove(Constants.q); 28 | return q; 29 | } 30 | 31 | public QueryParams wrapQueryParams(Map<String, Object> requestParams) { 32 | requestParams.remove(Constants.ApiStandard); 33 | return QueryParams.builder() 34 | .q(wrapQ(requestParams)) 35 | .filters(wrapFilters(requestParams)) 36 | .pagination(wrapPagination(requestParams)) 37 | .sorts(wrapSort(requestParams)) 38 | .select(wrapSelect(requestParams)) 39 | .build(); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/ApiStandard.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | /** 4 | * Created by gongxinyi on 2018-11-02. 5 | */ 6 | public enum ApiStandard { 7 | postgrest, 8 | jsonserver, 9 | } 10 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/DataServiceProxy.java: -------------------------------------------------------------------------------- 1 | //package com.dataserver.dataapi.data; 2 | // 3 | //import com.dataserver.api.IDataService; 4 | //import com.dataserver.api.schema.DbTypeEnum; 5 | //import com.dataserver.api.schema.JDataSource; 6 | //import org.springframework.beans.factory.annotation.Autowired; 7 | //import org.springframework.context.ApplicationContext; 8 | //import org.springframework.stereotype.Component; 9 | // 10 | //import java.util.Map; 11 | // 12 | ///** 13 | // * @author gongxinyi 14 | // * @date 2018-10-31 15 | // */ 16 | //@Component 17 | //public class DataServiceProxy { 18 | // 19 | // @Autowired 20 | // ApplicationContext applicationContext; 21 | // 22 | // 23 | // 24 | // public JDataSource getJDataSource() { 25 | //// JDataSource jDataSource = JDataSource.builder() 26 | //// .id("1") 27 | //// .dbType(DbTypeEnum.mysql) 28 | //// .url("jdbc:mysql://172.24.7.29:3306/test_easyadmin") 29 | //// .username("root") 30 | //// .password("JRTESTbai!@#123") 31 | //// .build(); 32 | // 33 | //// JDataSource jDataSource = JDataSource.builder() 34 | //// .id("2") 35 | //// .dbType(DbTypeEnum.mongo) 36 | //// .url("mongodb://192.168.202.238:27017/wnsyrtdipt") 37 | //// .build(); 38 | // 39 | //// JDataSource jDataSource = JDataSource.builder() 40 | //// .dbType(DbTypeEnum.elasticsearch) 41 | //// .url("http://10.194.32.168:40000") 42 | //// .clusterName("") 43 | //// .indexName("") 44 | //// .build(); 45 | // 46 | // return null; 47 | // } 48 | //} 49 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/FileStorageApi.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | import lombok.SneakyThrows; 4 | 5 | /** 6 | * Created by gongxinyi on 2019-05-25. 7 | */ 8 | public interface FileStorageApi { 9 | @SneakyThrows 10 | String saveBase64File(String base64Str); 11 | 12 | @SneakyThrows 13 | String getBase64Str(String objectName); 14 | } 15 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/JdsFile.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | @Data 7 | @Builder 8 | public class JdsFile { 9 | String title; 10 | String id; 11 | 12 | public String toPersistentStr() { 13 | return id + ";" + title; 14 | } 15 | 16 | public JdsFile toPojo(String persistentStr) { 17 | String[] jdsFile = persistentStr.split(";"); 18 | return JdsFile.builder().id(jdsFile[0]).title(jdsFile[1]).build(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/ResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 9 | * @author gongxinyi 10 | * @date 2018-11-02 11 | */ 12 | public interface ResponseWrapper { 13 | ResponseEntity listToResponseEntity(List results, Long totalCount); 14 | } 15 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/StorageAliyunServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | import com.aliyun.oss.OSSClient; 4 | import com.aliyun.oss.model.OSSObject; 5 | import com.dataserver.admin.util.SnowFlake; 6 | import lombok.SneakyThrows; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Service; 9 | 10 | import java.io.BufferedReader; 11 | import java.io.ByteArrayInputStream; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | 15 | @Service(value = "aliyun") 16 | public class StorageAliyunServiceImpl implements FileStorageApi { 17 | @Autowired 18 | SnowFlake snowFlake; 19 | 20 | @Autowired 21 | OSSClient ossClient; 22 | 23 | static final String bucketName = "jdatastudio"; 24 | 25 | /** 26 | * 保存base64文件 27 | * 28 | * @param base64Str 29 | * @return 唯一id 30 | */ 31 | @Override 32 | @SneakyThrows 33 | public String saveBase64File(String base64Str) { 34 | String objectName = String.valueOf(snowFlake.nextId()); 35 | ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(base64Str.getBytes("UTF-8"))); 36 | return "aliyun:" + objectName; 37 | } 38 | 39 | /** 40 | * 根据文件id,返回base64 41 | * 42 | * @param fileId 43 | * @return 44 | */ 45 | @Override 46 | @SneakyThrows 47 | public String getBase64Str(String fileId) { 48 | String objectName = fileId.replace("aliyun:", ""); 49 | // 调用ossClient.getObject返回一个OSSObject实例,该实例包含文件内容及文件元信息。 50 | OSSObject ossObject = ossClient.getObject(bucketName, objectName); 51 | // 调用ossObject.getObjectContent获取文件输入流,可读取此输入流获取其内容。 52 | InputStream content = ossObject.getObjectContent(); 53 | StringBuffer stringBuffer = new StringBuffer(); 54 | if (content != null) { 55 | BufferedReader reader = new BufferedReader(new InputStreamReader(content)); 56 | while (true) { 57 | String line = reader.readLine(); 58 | if (line == null) { 59 | break; 60 | } 61 | stringBuffer.append(line); 62 | } 63 | // 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。 64 | content.close(); 65 | } 66 | 67 | return stringBuffer.toString(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/data/StorageJdsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.data; 2 | 3 | import com.dataserver.admin.util.SnowFlake; 4 | import lombok.SneakyThrows; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.io.File; 10 | import java.nio.charset.StandardCharsets; 11 | import java.nio.file.Files; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | 15 | @Service("jds") 16 | public class StorageJdsServiceImpl implements FileStorageApi { 17 | 18 | @Autowired 19 | SnowFlake snowFlake; 20 | @Value("${jdatastudio.storage.rootPath}") 21 | String rootPath; 22 | 23 | /** 24 | * 保存base64文件 25 | * 26 | * @param base64Str 27 | * @return 唯一id 28 | */ 29 | @Override 30 | @SneakyThrows 31 | public String saveBase64File(String base64Str) { 32 | String fileId = String.valueOf(snowFlake.nextId()); 33 | if(!Files.exists(Paths.get(rootPath))){ 34 | Files.createDirectories(Paths.get(rootPath)); 35 | } 36 | Files.write(Paths.get(rootPath + File.separator + fileId), base64Str.getBytes("UTF-8")); 37 | return "jds:" + fileId; 38 | } 39 | 40 | /** 41 | * 根据文件id,返回base64 42 | * 43 | * @param fileId 44 | * @return 45 | */ 46 | @Override 47 | @SneakyThrows 48 | public String getBase64Str(String fileId) { 49 | String fileName = fileId.replace("jds:", ""); 50 | if(!Files.exists(Paths.get(rootPath))){ 51 | Files.createDirectories(Paths.get(rootPath)); 52 | } 53 | Path path = Paths.get(rootPath + File.separator + fileName); 54 | return Files.exists(path) ? new String(Files.readAllBytes(path), StandardCharsets.UTF_8) : null; 55 | } 56 | } 57 | 58 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/jsonserver/JsonServerResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.jsonserver; 2 | 3 | import com.dataserver.dataapi.data.ResponseWrapper; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * Created by gongxinyi on 2018-11-02. 12 | */ 13 | @Component 14 | public class JsonServerResponseWrapper implements ResponseWrapper { 15 | @Override 16 | public ResponseEntity listToResponseEntity(List results, Long totalCount) { 17 | return ResponseEntity 18 | .status(HttpStatus.OK) 19 | .header("X-Total-Count", totalCount + "") 20 | .header("Access-Control-Expose-Headers", "X-Total-Count") 21 | .body(results); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/postgrest/PostgrestRequestWrapper.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.postgrest; 2 | 3 | import com.dataserver.api.*; 4 | import com.dataserver.dataapi.data.AbstractRequestWrapper; 5 | import org.apache.commons.lang.StringUtils; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.ArrayList; 9 | import java.util.Arrays; 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | /** 14 | * @author gongxinyi 15 | * @date 2018-11-02 16 | */ 17 | @Component 18 | public class PostgrestRequestWrapper extends AbstractRequestWrapper { 19 | @Override 20 | protected Pagination wrapPagination(Map<String, Object> requestParams) { 21 | return Pagination.builder() 22 | .offset(Integer.parseInt(requestParams.getOrDefault(offset, "0").toString())) 23 | .limit(Integer.parseInt(requestParams.getOrDefault(limit, "10").toString())) 24 | .build(); 25 | } 26 | 27 | @Override 28 | protected List<Sort> wrapSort(Map<String, Object> requestParams) { 29 | List<Sort> sorts = new ArrayList<>(); 30 | String orderStr = requestParams.getOrDefault(order, "").toString(); 31 | if (!StringUtils.isEmpty(orderStr)) { 32 | String[] orders = orderStr.split(","); 33 | for (String order : orders) { 34 | String[] orderArray = order.split("\\."); 35 | sorts.add(new Sort(orderArray[0], OrderEnum.valueOf(orderArray[1]))); 36 | } 37 | } 38 | return sorts; 39 | } 40 | 41 | @Override 42 | protected String[] wrapSelect(Map<String, Object> requestParams) { 43 | return String.valueOf(requestParams.getOrDefault(select, allcolumns)).split(","); 44 | } 45 | 46 | @Override 47 | protected List<Filter> wrapFilters(Map<String, Object> requestParams) { 48 | List<Filter> filters = new ArrayList<>(); 49 | requestParams.forEach((k, v) -> { 50 | if (!sysKeys.contains(k)) { 51 | String[] operatorAndValue = v.toString().split("\\."); 52 | OperatorEnum operatorEnum = OperatorEnum.eq; 53 | String value; 54 | if (operatorAndValue.length == 2) { 55 | operatorEnum = OperatorEnum.valueOf(operatorAndValue[0]); 56 | value = operatorAndValue[1]; 57 | } else { 58 | value = operatorAndValue[0]; 59 | } 60 | filters.add(new Filter(k, operatorEnum, value)); 61 | } 62 | }); 63 | return filters; 64 | } 65 | 66 | public static final String select = "select"; 67 | public static final String order = "order"; 68 | public static final String limit = "limit"; 69 | public static final String offset = "offset"; 70 | public static final String and = "and"; 71 | public static final String or = "or"; 72 | public static final String allcolumns = "*"; 73 | 74 | static List<String> sysKeys = Arrays.asList(offset, 75 | select, 76 | and, 77 | limit, 78 | order, 79 | or); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/java/com/dataserver/dataapi/postgrest/PostgrestResponseWrapper.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.dataapi.postgrest; 2 | 3 | import com.dataserver.dataapi.data.ResponseWrapper; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.stereotype.Component; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * 12 | * @author gongxinyi 13 | * @date 2018-11-02 14 | */ 15 | @Component 16 | public class PostgrestResponseWrapper implements ResponseWrapper { 17 | @Override 18 | public ResponseEntity listToResponseEntity(List results, Long totalCount) { 19 | return ResponseEntity 20 | .status(HttpStatus.OK) 21 | .header("Content-Range", totalCount + "") 22 | .header("Access-Control-Expose-Headers", "Content-Range") 23 | .body(results); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/resources/2110293_jdatastudio.com.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-server/jdatastudio/588dbba7fef8a9819a8f6d0439faf411a72d0ba8/jdatastudio-admin/src/main/resources/2110293_jdatastudio.com.pfx -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/resources/application-aliyun.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 443 3 | ssl: 4 | enabled: true 5 | key-store: classpath:2110293_jdatastudio.com.pfx 6 | key-password: 80hKDEWT 7 | key-store-type: PFX 8 | 9 | http-port: 80 10 | 11 | jwt: 12 | header: Authorization 13 | secret: datastudio 14 | expiration: 604800 15 | route: 16 | authentication: 17 | path: /auth 18 | refresh: /refresh 19 | 20 | jdatastudio: 21 | cloud: true 22 | storage: 23 | strategy: aliyun 24 | endpoint: http://oss-cn-shanghai-internal.aliyuncs.com 25 | 26 | spring: 27 | application: 28 | name: jdatastudio-admin 29 | data: 30 | mongodb: 31 | uri: mongodb://jdatastudioadmin:jdatastudioadmin@101.132.97.131:27017/jdatastudio-admin 32 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/resources/application-lcloud.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 80 3 | 4 | jwt: 5 | header: Authorization 6 | secret: datastudio 7 | expiration: 604800 8 | route: 9 | authentication: 10 | path: /auth 11 | refresh: /refresh 12 | 13 | jdatastudio: 14 | cloud: true 15 | 16 | spring: 17 | application: 18 | name: jdatastudio-admin 19 | data: 20 | mongodb: 21 | uri: mongodb://localhost:27017/jdatastudio-admin -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 80 3 | 4 | jwt: 5 | header: Authorization 6 | secret: datastudio 7 | expiration: 604800 8 | route: 9 | authentication: 10 | path: /auth 11 | refresh: /refresh 12 | 13 | jdatastudio: 14 | cloud: false 15 | tenant: 16 | uri: mongodb://localhost:27017/testLocal2 17 | admin: gongxinong@gmail.com 18 | storage: 19 | strategy: jds 20 | rootPath: /Users/gongmark/data/jds 21 | endpoint: http://oss-cn-shanghai.aliyuncs.com 22 | 23 | spring: 24 | application: 25 | name: jdatastudio-admin 26 | data: 27 | mongodb: 28 | uri: mongodb://192.168.202.238:27017/jdatastudio -------------------------------------------------------------------------------- /jdatastudio-admin/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <configuration scan="true"> 3 | <property name="APP" value="jdatastudio.com"/> 4 | <property name="LOG_HOME" value="/Users/gongmark/Logs/${APP}"/> 5 | <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 6 | <encoder> 7 | <pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern> 8 | <charset>UTF-8</charset> 9 | </encoder> 10 | </appender> 11 | <appender name="DETAIL" class="ch.qos.logback.core.rolling.RollingFileAppender" additivity="false"> 12 | <File>${LOG_HOME}/${APP}_detail.log</File> 13 | <encoder> 14 | <pattern>%d{yy-MM-dd.HH:mm:ss.SSS} [%-16t] %-5p %-22c{0} %X{ServiceId} - %m%n</pattern> 15 | <charset>UTF-8</charset> 16 | </encoder> 17 | <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> 18 | <fileNamePattern>${LOG_HOME}/${APP}_detail.log.%d{yyyyMMdd}</fileNamePattern> 19 | </rollingPolicy> 20 | </appender> 21 | <logger name="org.springframework" level="WARN"/> 22 | <logger name="org.apache.httpclient.wire" level="INFO"/> 23 | <logger name="org.apache.commons.httpclient" level="INFO"/> 24 | <logger name="org.apache.zookeeper" level="INFO"/> 25 | <root level="INFO"> 26 | <appender-ref ref="CONSOLE"/> 27 | <appender-ref ref="DETAIL"/> 28 | </root> 29 | </configuration> 30 | -------------------------------------------------------------------------------- /jdatastudio-admin/src/test/java/com/dataserver/admin/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.admin; 2 | 3 | import com.dataserver.admin.security.Role; 4 | import com.dataserver.admin.security.User; 5 | import com.fasterxml.jackson.databind.ObjectWriter; 6 | import lombok.extern.slf4j.Slf4j; 7 | import org.junit.Test; 8 | import org.springframework.http.MediaType; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 14 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 15 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 16 | 17 | /** 18 | * Created by gongxinyi on 2018/12/5. 19 | */ 20 | @Slf4j 21 | public class UserServiceTest extends AbstractTest { 22 | @Test 23 | public void getUsers() throws Exception { 24 | final String token = extractToken(login("admin", "admin").andReturn()); 25 | log.info("token:{}", token); 26 | mockMvc.perform(get("/users") 27 | .header("Authorization", "Bearer " + token) 28 | .accept(MediaType.APPLICATION_JSON)) 29 | .andExpect(status().isOk()); 30 | } 31 | 32 | @Test 33 | public void addUser() throws Exception { 34 | final String token = extractToken(login("admin", "admin").andReturn()); 35 | User user = new User(); 36 | user.setUsername("foo"); 37 | user.setPassword("foo"); 38 | user.setEnabled(true); 39 | Role role = new Role(); 40 | role.setId("ROLE_ADMIN"); 41 | List<Role> roles = new ArrayList(); 42 | roles.add(role); 43 | user.setRoles(roles); 44 | ObjectWriter ow = mapper.writer().withDefaultPrettyPrinter(); 45 | mockMvc.perform(post("/users") 46 | .content(ow.writeValueAsString(user)) 47 | .header("Authorization", "Bearer " + token) 48 | .accept(MediaType.APPLICATION_JSON)) 49 | .andExpect(status().isOk()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /jdatastudio-api/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /jdatastudio-api/pom.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 | <modelVersion>4.0.0</modelVersion> 5 | 6 | <artifactId>jdatastudio-api</artifactId> 7 | 8 | <parent> 9 | <groupId>com.dataserver</groupId> 10 | <artifactId>jdatastudio</artifactId> 11 | <version>0.0.1-SNAPSHOT</version> 12 | </parent> 13 | 14 | <dependencies> 15 | <dependency> 16 | <groupId>org.projectlombok</groupId> 17 | <artifactId>lombok</artifactId> 18 | <optional>true</optional> 19 | </dependency> 20 | <dependency> 21 | <groupId>commons-lang</groupId> 22 | <artifactId>commons-lang</artifactId> 23 | <version>2.6</version> 24 | </dependency> 25 | <dependency> 26 | <groupId>org.mongodb.morphia</groupId> 27 | <artifactId>morphia</artifactId> 28 | <version>1.3.1</version> 29 | </dependency> 30 | <dependency> 31 | <groupId>com.fasterxml.jackson.core</groupId> 32 | <artifactId>jackson-annotations</artifactId> 33 | <version>2.8.0</version> 34 | </dependency> 35 | </dependencies> 36 | </project> 37 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/ComplexFilter.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 复杂组合 9 | * 10 | * @author gongxinyi 11 | * @date 2018-09-27 12 | */ 13 | @Data 14 | public class ComplexFilter { 15 | LogicEnum logicEnum; 16 | List<Filters> filtersList; 17 | } 18 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/Constants.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | /** 4 | * 关键字 5 | * 6 | * @author gongxinyi 7 | * @date 2018-09-27 8 | */ 9 | public class Constants { 10 | public static final String choiceitemdelimiter = "\\|"; 11 | public static final String choiceitemtab = "\n"; 12 | public static final String allcolumns = "*"; 13 | public static final String dataService = "dataService"; 14 | public static final String schemaService = "dataService"; 15 | } 16 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/Filter.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.NoArgsConstructor; 7 | 8 | /** 9 | * 过滤器 10 | * 11 | * @author gongxinyi 12 | * @date 2018-09-27 13 | */ 14 | @Builder 15 | @Data 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | public class Filter { 19 | private String field; 20 | private OperatorEnum operator; 21 | private String value; 22 | } 23 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/Filters.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * 过滤组合 9 | * 10 | * @author gongxinyi 11 | * @date 2018-09-27 12 | */ 13 | @Data 14 | public class Filters { 15 | LogicEnum logicEnum; 16 | List<Filter> filterList; 17 | } 18 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/IDataService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import com.dataserver.api.schema.JDataSource; 4 | 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * 数据服务 10 | * 11 | * @author gongxinyi 12 | * @date 2018-09-27 13 | */ 14 | public interface IDataService { 15 | 16 | /** 17 | * 查询数据列表 18 | * 19 | * @param jDataSource 20 | * @param entityName 21 | * @param queryParams 22 | * @return 23 | */ 24 | List<Map<String, Object>> list(JDataSource jDataSource, String entityName, QueryParams queryParams); 25 | 26 | /** 27 | * count 28 | * 29 | * @param jDataSource 30 | * @param entityName 31 | * @param queryParams 32 | * @return 33 | */ 34 | Long count(JDataSource jDataSource, String entityName, QueryParams queryParams); 35 | 36 | /** 37 | * 保存数据 38 | * 39 | * @param jDataSource 40 | * @param entityName 41 | * @param data 42 | * @return 43 | */ 44 | Map<String, Object> create(JDataSource jDataSource, String entityName, Map<String, Object> data); 45 | 46 | /** 47 | * 保存数据 48 | * 49 | * @param jDataSource 50 | * @param entityName 51 | * @param datas 52 | * @return 53 | */ 54 | List<Map<String, Object>> create(JDataSource jDataSource, String entityName, List<Map<String, Object>> datas); 55 | 56 | /** 57 | * @param jDataSource 58 | * @param entityName 59 | * @param data 60 | * @param filters 61 | * @return 62 | */ 63 | Map<String, Object> update(JDataSource jDataSource, String entityName, Map<String, Object> data, List<Filter> filters); 64 | 65 | } 66 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/LogicEnum.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | /** 4 | * 逻辑操作 5 | * 6 | * @author gongxinyi 7 | * @date 2018-09-27 8 | */ 9 | public enum LogicEnum { 10 | and, 11 | or 12 | } 13 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/OperatorEnum.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | /** 4 | * 操作符简称 5 | * 6 | * @author gongxinyi 7 | * @date 2018-09-27 8 | */ 9 | public enum OperatorEnum { 10 | eq, 11 | gte, 12 | gt, 13 | lte, 14 | lt, 15 | like, 16 | in, 17 | } 18 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/OrderEnum.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | /** 4 | * 排序顺序 5 | * 6 | * @author gongxinyi 7 | * @date 2018-09-27 8 | */ 9 | public enum OrderEnum { 10 | desc, 11 | asc 12 | } 13 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/Pagination.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | /** 7 | * 分页 8 | * 9 | * @author gongxinyi 10 | * @date 2018-09-27 11 | */ 12 | @Data 13 | @Builder 14 | public class Pagination { 15 | private int offset; 16 | private int limit; 17 | } 18 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/QueryParams.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 请求参数 10 | * 11 | * @author gongxinyi 12 | * @date 2018-09-27 13 | */ 14 | @Data 15 | @Builder 16 | public class QueryParams { 17 | private String q; 18 | private String[] select; 19 | private List<Sort> sorts; 20 | private ComplexFilter complexFilter; 21 | private List<Filter> filters; 22 | private Pagination pagination; 23 | } 24 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/Sort.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 排序 7 | * 8 | * @author gongxinyi 9 | * @date 2018-09-27 10 | */ 11 | @Data 12 | public class Sort { 13 | private String field; 14 | private OrderEnum order; 15 | 16 | public Sort(String field, OrderEnum order) { 17 | this.field = field; 18 | this.order = order; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/ChoiceItem.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | 6 | @Data 7 | @NoArgsConstructor 8 | public class ChoiceItem { 9 | private String id; 10 | private String name; 11 | 12 | public ChoiceItem(String id, String name) { 13 | this.id = id; 14 | this.name = name; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/ComponentType.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | /** 4 | * Created by gongxinyi on 2018/11/30. 5 | */ 6 | public enum ComponentType { 7 | Text, 8 | Number, 9 | Select, 10 | Date, 11 | DateTime, 12 | Reference, 13 | Boolean, 14 | Image, 15 | File, 16 | } 17 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/DbColumnType.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | /** 4 | * Created by gongxinyi on 2018-10-15. 5 | */ 6 | public enum DbColumnType { 7 | varchar, number, datetime, bool 8 | } 9 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/DbTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | /** 4 | * @author gongxinyi 5 | * @date 2018-11-01 6 | */ 7 | public enum DbTypeEnum { 8 | mongo, 9 | mysql, 10 | elasticsearch 11 | } 12 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/Entity.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Builder; 5 | import lombok.Data; 6 | import lombok.experimental.Tolerate; 7 | import org.mongodb.morphia.annotations.Embedded; 8 | import org.mongodb.morphia.annotations.Id; 9 | import org.mongodb.morphia.annotations.Reference; 10 | 11 | import java.util.List; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Created by gongxinyi on 2018-10-15. 16 | */ 17 | @Data 18 | @Builder 19 | @org.mongodb.morphia.annotations.Entity(value = "_entity", noClassnameStored = true) 20 | public class Entity { 21 | 22 | @Id 23 | private String id; 24 | private String name; 25 | private String label; 26 | @Embedded 27 | private List<Field> fields; 28 | @Reference 29 | @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) 30 | private JDataSource jDataSource; 31 | @Tolerate 32 | public Entity() { 33 | } 34 | public List<Field> getPrimaryFields() { 35 | return fields.stream().filter(field -> field.isPartOfPrimaryKey()).collect(Collectors.toList()); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/Field.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | import com.dataserver.api.Constants; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Builder; 6 | import lombok.Data; 7 | import lombok.experimental.Tolerate; 8 | import net.sf.cglib.core.CollectionUtils; 9 | import org.mongodb.morphia.annotations.Embedded; 10 | 11 | import java.sql.JDBCType; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.stream.Collectors; 15 | 16 | /** 17 | * Created by gongxinyi on 2018-10-15. 18 | */ 19 | @Data 20 | @Builder 21 | @Embedded 22 | public class Field { 23 | protected String id; 24 | protected String name; 25 | 26 | protected String label; 27 | protected Integer maxLength; 28 | protected boolean required; 29 | protected String defaultValue; 30 | private boolean autoIncremented; 31 | private boolean partOfPrimaryKey; 32 | 33 | private JDBCType jdbcType; 34 | private DbColumnType dbColumnType; 35 | 36 | ///////////////////////////jdatastudio 属性///////////////////////////// 37 | private ComponentType component; 38 | private boolean showInList; 39 | private boolean showInShow; 40 | private boolean showInEdit; 41 | private boolean showInCreate; 42 | private boolean showInFilter; 43 | private boolean alwaysOn; 44 | private boolean sortable; 45 | 46 | /** 47 | * 引用字段的属性 48 | */ 49 | private String reference; 50 | private String referenceOptionText; 51 | /** 52 | * 脱敏类型 53 | */ 54 | private SensitiveEnum sensitiveType; 55 | 56 | /** 57 | * 选项值 58 | */ 59 | private List<ChoiceItem> choices; 60 | 61 | /** 62 | * 多值过滤 63 | */ 64 | private boolean multiFilter; 65 | 66 | /** 67 | * 前端设置下拉选项值 68 | * 69 | * @return 70 | */ 71 | public String getChoicesStr() { 72 | return choices == null ? "":choices.stream().map(choiceItem -> 73 | choiceItem.getId() + "|" + choiceItem.getName() 74 | ).collect(Collectors.joining(Constants.choiceitemtab)); 75 | } 76 | 77 | public void setChoicesStr(String choicesStr){ 78 | choices = new ArrayList<>(); 79 | String[] choicesArray = choicesStr.split(Constants.choiceitemtab); 80 | for(String choice:choicesArray){ 81 | String[] choiceItems = choice.split(Constants.choiceitemdelimiter); 82 | if(choiceItems.length==2){ 83 | choices.add(new ChoiceItem(choiceItems[0],choiceItems[1])); 84 | } 85 | } 86 | } 87 | 88 | /** 89 | * 是否主字段 90 | */ 91 | private boolean mainField; 92 | 93 | /** 94 | * 解决mysql字段名称包含`字符的问题 95 | * 96 | * @return 97 | */ 98 | @JsonProperty("name") 99 | public String getName() { 100 | if (name == null) { 101 | return ""; 102 | } else { 103 | return name.replace("`", ""); 104 | } 105 | } 106 | 107 | @Tolerate 108 | public Field() { 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/ISchemaService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * 获取schema 7 | * 8 | * @author gongxinyi 9 | * @date 2018-10-31 10 | */ 11 | public interface ISchemaService { 12 | List<Entity> getSchemas(JDataSource dataSource); 13 | } 14 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/JDataSource.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | import lombok.Data; 4 | import lombok.NoArgsConstructor; 5 | import org.mongodb.morphia.annotations.Id; 6 | 7 | /** 8 | * Created by gongxinyi on 2018-11-01. 9 | */ 10 | @Data 11 | @NoArgsConstructor 12 | @org.mongodb.morphia.annotations.Entity(value = "_datasource", noClassnameStored = true) 13 | public class JDataSource { 14 | @Id 15 | private String id; 16 | private String name; 17 | private String url; 18 | private String username; 19 | private String password; 20 | private DbTypeEnum dbType; 21 | 22 | // for elasticsearch 23 | private String indexName; 24 | private String clusterName; 25 | } 26 | -------------------------------------------------------------------------------- /jdatastudio-api/src/main/java/com/dataserver/api/schema/SensitiveEnum.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.api.schema; 2 | 3 | /** 4 | * @author gongxinyi 5 | * @date 2017-11-19 6 | */ 7 | public enum SensitiveEnum { 8 | nonsensitive, 9 | sensitive, 10 | mobile, 11 | card, 12 | id; 13 | } 14 | -------------------------------------------------------------------------------- /jdatastudio-connector-elasticsearch/pom.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 | <modelVersion>4.0.0</modelVersion> 6 | 7 | <parent> 8 | <artifactId>jdatastudio</artifactId> 9 | <groupId>com.dataserver</groupId> 10 | <version>0.0.1-SNAPSHOT</version> 11 | </parent> 12 | 13 | 14 | <artifactId>jdatastudio-connector-elasticsearch</artifactId> 15 | 16 | <dependencies> 17 | <dependency> 18 | <groupId>org.springframework.boot</groupId> 19 | <artifactId>spring-boot-starter</artifactId> 20 | </dependency> 21 | <dependency> 22 | <groupId>io.searchbox</groupId> 23 | <artifactId>jest</artifactId> 24 | <version>5.3.2</version> 25 | </dependency> 26 | 27 | <dependency> 28 | <groupId>org.projectlombok</groupId> 29 | <artifactId>lombok</artifactId> 30 | <optional>true</optional> 31 | </dependency> 32 | <dependency> 33 | <groupId>org.elasticsearch</groupId> 34 | <artifactId>elasticsearch</artifactId> 35 | <version>5.3.2</version> 36 | </dependency> 37 | <dependency> 38 | <groupId>org.elasticsearch.client</groupId> 39 | <artifactId>transport</artifactId> 40 | <version>5.3.2</version> 41 | </dependency> 42 | <dependency> 43 | <groupId>com.fasterxml.jackson.core</groupId> 44 | <artifactId>jackson-core</artifactId> 45 | </dependency> 46 | <dependency> 47 | <groupId>com.fasterxml.jackson.core</groupId> 48 | <artifactId>jackson-databind</artifactId> 49 | </dependency> 50 | 51 | <dependency> 52 | <groupId>com.dataserver</groupId> 53 | <artifactId>jdatastudio-api</artifactId> 54 | <version>0.0.1-SNAPSHOT</version> 55 | </dependency> 56 | </dependencies> 57 | </project> 58 | -------------------------------------------------------------------------------- /jdatastudio-connector-elasticsearch/src/main/java/com/dataserver/connector/elasticsearch/EsDataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.connector.elasticsearch; 2 | 3 | import com.dataserver.api.schema.JDataSource; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.JsonObject; 6 | import io.searchbox.client.JestClient; 7 | import io.searchbox.client.JestClientFactory; 8 | import io.searchbox.client.JestResult; 9 | import io.searchbox.client.config.HttpClientConfig; 10 | import io.searchbox.indices.mapping.GetMapping; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.io.IOException; 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | /** 19 | * Created by gongxinyi on 2018-11-01. 20 | */ 21 | @Service 22 | public class EsDataSourceService { 23 | static Map<String, JestClient> jestClientMap = new HashMap<>(); 24 | 25 | public JestClient getJestClient(JDataSource jDataSource) { 26 | String nodes = jDataSource.getUrl(); 27 | if (!jestClientMap.containsKey(nodes)) { 28 | jestClientMap.put(nodes, getJestClient(nodes)); 29 | } 30 | return jestClientMap.get(nodes); 31 | } 32 | 33 | 34 | public JestClient getJestClient(String connectionUrl) { 35 | JestClientFactory factory = new JestClientFactory(); 36 | factory.setHttpClientConfig(new HttpClientConfig 37 | .Builder(connectionUrl) 38 | .gson(new GsonBuilder() 39 | .setDateFormat("yyyy-MM-dd HH:mm:ss") 40 | .create()) 41 | .multiThreaded(true) 42 | .discoveryEnabled(true) 43 | .discoveryFrequency(60, TimeUnit.SECONDS) 44 | .readTimeout(20000) 45 | .build()); 46 | return factory.getObject(); 47 | } 48 | 49 | public JsonObject getMapping(JestClient client, JDataSource jDataSource) 50 | throws IOException { 51 | String index = jDataSource.getIndexName(); 52 | Map<String, Object> headers = new HashMap(); 53 | final JestResult result = client.execute( 54 | new GetMapping.Builder().addIndex(index).setHeader(headers).build() 55 | ); 56 | final JsonObject indexRoot = result.getJsonObject().getAsJsonObject(index); 57 | if (indexRoot == null) { 58 | return null; 59 | } 60 | final JsonObject mappingsJson = indexRoot.getAsJsonObject("mappings"); 61 | if (mappingsJson == null) { 62 | return null; 63 | } 64 | return mappingsJson.getAsJsonObject(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /jdatastudio-connector-mongodb/pom.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" 3 | xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 4 | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 5 | 6 | <modelVersion>4.0.0</modelVersion> 7 | <artifactId>jdatastudio-connector-mongodb</artifactId> 8 | 9 | <parent> 10 | <groupId>com.dataserver</groupId> 11 | <artifactId>jdatastudio</artifactId> 12 | <version>0.0.1-SNAPSHOT</version> 13 | </parent> 14 | <dependencies> 15 | <dependency> 16 | <groupId>org.springframework.boot</groupId> 17 | <artifactId>spring-boot-starter</artifactId> 18 | </dependency> 19 | <dependency> 20 | <groupId>org.mongodb.morphia</groupId> 21 | <artifactId>morphia</artifactId> 22 | <version>1.3.1</version> 23 | </dependency> 24 | <dependency> 25 | <groupId>org.projectlombok</groupId> 26 | <artifactId>lombok</artifactId> 27 | <optional>true</optional> 28 | </dependency> 29 | <dependency> 30 | <groupId>com.dataserver</groupId> 31 | <artifactId>jdatastudio-api</artifactId> 32 | <version>0.0.1-SNAPSHOT</version> 33 | </dependency> 34 | </dependencies> 35 | </project> 36 | -------------------------------------------------------------------------------- /jdatastudio-connector-mongodb/src/main/java/com/dataserver/connector/mongodb/MongoDataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.connector.mongodb; 2 | 3 | import com.dataserver.api.schema.JDataSource; 4 | import com.mongodb.MongoClient; 5 | import com.mongodb.MongoClientURI; 6 | import com.mongodb.client.MongoDatabase; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.HashMap; 10 | import java.util.Map; 11 | 12 | /** 13 | * @author gongxinyi 14 | * @date 2018-11-01 15 | */ 16 | @Service 17 | public class MongoDataSourceService { 18 | static Map<String, MongoClient> uriMongoClientMap = new HashMap<>(); 19 | 20 | /** 21 | * 获取mongoclient 22 | * 23 | * @param jDataSource 24 | * @return 25 | */ 26 | public MongoClient getMongoClient(JDataSource jDataSource) { 27 | if (!uriMongoClientMap.containsKey(jDataSource.getUrl())) { 28 | MongoClientURI mongoClientURI = new MongoClientURI(jDataSource.getUrl()); 29 | MongoClient client = new MongoClient(mongoClientURI); 30 | uriMongoClientMap.put(jDataSource.getUrl(), client); 31 | } 32 | return uriMongoClientMap.get(jDataSource.getUrl()); 33 | } 34 | 35 | /** 36 | * @param jDataSource 37 | * @return 38 | */ 39 | public MongoDatabase getMongoDatabase(JDataSource jDataSource) { 40 | return getMongoClient(jDataSource).getDatabase(getDatabase(jDataSource)); 41 | } 42 | 43 | /** 44 | * 根据url 获取 database 45 | * 46 | * @param dataSource 47 | * @return 48 | */ 49 | private String getDatabase(JDataSource dataSource) { 50 | MongoClientURI mongoClientURI = new MongoClientURI(dataSource.getUrl()); 51 | return mongoClientURI.getDatabase(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /jdatastudio-connector-mongodb/src/main/java/com/dataserver/connector/mongodb/MongoSchemaService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.connector.mongodb; 2 | 3 | import com.dataserver.api.schema.*; 4 | import com.mongodb.client.FindIterable; 5 | import com.mongodb.client.MongoCollection; 6 | import com.mongodb.client.MongoDatabase; 7 | import com.mongodb.client.MongoIterable; 8 | import lombok.extern.slf4j.Slf4j; 9 | import org.bson.Document; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.stereotype.Service; 12 | 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * @author gongxinyi 21 | * @date 2018-10-31 22 | */ 23 | @Slf4j 24 | @Service 25 | public class MongoSchemaService implements ISchemaService { 26 | @Autowired 27 | MongoDataSourceService mongoDataSourceService; 28 | 29 | @Override 30 | public List<Entity> getSchemas(JDataSource jDataSource) { 31 | MongoDatabase database = mongoDataSourceService.getMongoDatabase(jDataSource); 32 | MongoIterable<String> collections = database.listCollectionNames(); 33 | List<Entity> entities = new ArrayList<>(); 34 | for (String collection : collections) { 35 | log.info("collection:{}", collection); 36 | if ("system.indexes".equals(collection)) { 37 | continue; 38 | } 39 | MongoCollection dbCollection = database.getCollection(collection); 40 | FindIterable<Document> documents = dbCollection.find(); 41 | if (!documents.iterator().hasNext()) { 42 | continue; 43 | } 44 | Document doc = documents.iterator().next(); 45 | 46 | Entity entity = Entity.builder() 47 | .name(collection) 48 | .label(collection) 49 | .build(); 50 | List<Field> fields = mongoDoc2Fields(doc); 51 | entity.setFields(fields); 52 | entities.add(entity); 53 | } 54 | return entities; 55 | } 56 | 57 | /** 58 | * mongo document 2 field 59 | * 60 | * @param doc 61 | * @return 62 | */ 63 | private List<Field> mongoDoc2Fields(Document doc) { 64 | return doc.entrySet().stream() 65 | .map(entry -> doc2Field(entry.getKey(), entry.getValue())) 66 | .collect(Collectors.toList()); 67 | } 68 | 69 | private Field doc2Field(String k, Object o) { 70 | String clazz = o == null ? String.class.getSimpleName() : o.getClass().getSimpleName(); 71 | 72 | Field field = Field.builder() 73 | .name(k) 74 | .label(k) 75 | .dbColumnType(mongoDocTypeComponentMap.getOrDefault(clazz, DbColumnType.varchar)) 76 | .build(); 77 | return field; 78 | } 79 | 80 | static Map<String, DbColumnType> mongoDocTypeComponentMap = new HashMap<>(); 81 | 82 | static { 83 | mongoDocTypeComponentMap.put(String.class.getSimpleName(), DbColumnType.varchar); 84 | mongoDocTypeComponentMap.put(Integer.class.getSimpleName(), DbColumnType.number); 85 | mongoDocTypeComponentMap.put(Boolean.class.getSimpleName(), DbColumnType.bool); 86 | mongoDocTypeComponentMap.put(Double.class.getSimpleName(), DbColumnType.number); 87 | mongoDocTypeComponentMap.put(Long.class.getSimpleName(), DbColumnType.number); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /jdatastudio-connector-mysql/.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | 4 | ### STS ### 5 | .apt_generated 6 | .classpath 7 | .factorypath 8 | .project 9 | .settings 10 | .springBeans 11 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /jdatastudio-connector-mysql/pom.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 | <modelVersion>4.0.0</modelVersion> 5 | <artifactId>jdatastudio-connector-mysql</artifactId> 6 | 7 | <parent> 8 | <groupId>com.dataserver</groupId> 9 | <artifactId>jdatastudio</artifactId> 10 | <version>0.0.1-SNAPSHOT</version> 11 | </parent> 12 | 13 | <dependencies> 14 | <dependency> 15 | <groupId>org.springframework.boot</groupId> 16 | <artifactId>spring-boot-starter</artifactId> 17 | </dependency> 18 | <dependency> 19 | <groupId>org.springframework.boot</groupId> 20 | <artifactId>spring-boot-starter-jdbc</artifactId> 21 | <exclusions> 22 | <exclusion> 23 | <groupId>org.apache.tomcat</groupId> 24 | <artifactId>tomcat-jdbc</artifactId> 25 | </exclusion> 26 | </exclusions> 27 | </dependency> 28 | <dependency> 29 | <groupId>com.healthmarketscience.sqlbuilder</groupId> 30 | <artifactId>sqlbuilder</artifactId> 31 | <version>2.1.7</version> 32 | </dependency> 33 | <dependency> 34 | <groupId>org.projectlombok</groupId> 35 | <artifactId>lombok</artifactId> 36 | <optional>true</optional> 37 | </dependency> 38 | <dependency> 39 | <groupId>mysql</groupId> 40 | <artifactId>mysql-connector-java</artifactId> 41 | </dependency> 42 | <dependency> 43 | <groupId>us.fatehi</groupId> 44 | <artifactId>schemacrawler</artifactId> 45 | <version>14.16.03</version> 46 | </dependency> 47 | <dependency> 48 | <groupId>com.dataserver</groupId> 49 | <artifactId>jdatastudio-api</artifactId> 50 | <version>0.0.1-SNAPSHOT</version> 51 | </dependency> 52 | </dependencies> 53 | </project> 54 | -------------------------------------------------------------------------------- /jdatastudio-connector-mysql/src/main/java/com/dataserver/connector/mysql/MysqlDataSourceService.java: -------------------------------------------------------------------------------- 1 | package com.dataserver.connector.mysql; 2 | 3 | import com.dataserver.api.schema.JDataSource; 4 | import com.zaxxer.hikari.HikariDataSource; 5 | import org.springframework.jdbc.core.JdbcTemplate; 6 | import org.springframework.stereotype.Service; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * Created by gongxinyi on 2018-11-01. 13 | */ 14 | @Service 15 | public class MysqlDataSourceService { 16 | static Map<String, JdbcTemplate> jdbcTemplateMap = new HashMap<>(); 17 | 18 | /** 19 | * 根据url缓存jdbctemplate 20 | * 21 | * @param jDataSource 22 | * @return 23 | */ 24 | public JdbcTemplate getJdbcTemplate(JDataSource jDataSource) { 25 | if (!jdbcTemplateMap.containsKey(jDataSource.getUrl())) { 26 | jdbcTemplateMap.put(jDataSource.getUrl(), new JdbcTemplate(dataSource2Hikari(jDataSource))); 27 | } 28 | return jdbcTemplateMap.get(jDataSource.getUrl()); 29 | } 30 | 31 | 32 | /** 33 | * jdatasource转换成HikariDataSource 34 | * 35 | * @param jDataSource 36 | * @return 37 | */ 38 | public HikariDataSource dataSource2Hikari(JDataSource jDataSource) { 39 | HikariDataSource ds = new HikariDataSource(); 40 | ds.setDriverClassName(com.mysql.jdbc.Driver.class.getName()); 41 | ds.setJdbcUrl(jDataSource.getUrl()); 42 | ds.setUsername(jDataSource.getUsername()); 43 | ds.setPassword(jDataSource.getPassword()); 44 | return ds; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /jdatastudio.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/data-server/jdatastudio/588dbba7fef8a9819a8f6d0439faf411a72d0ba8/jdatastudio.mp4 -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 | <modelVersion>4.0.0</modelVersion> 5 | 6 | <groupId>com.dataserver</groupId> 7 | <artifactId>jdatastudio</artifactId> 8 | <version>0.0.1-SNAPSHOT</version> 9 | 10 | <packaging>pom</packaging> 11 | 12 | <parent> 13 | <groupId>org.springframework.boot</groupId> 14 | <artifactId>spring-boot-starter-parent</artifactId> 15 | <version>2.1.2.RELEASE</version> 16 | </parent> 17 | <modules> 18 | <module>jdatastudio-api</module> 19 | <module>jdatastudio-connector-mysql</module> 20 | <module>jdatastudio-connector-mongodb</module> 21 | <module>jdatastudio-connector-elasticsearch</module> 22 | <module>jdatastudio-admin</module> 23 | <module>jdatastudio-gateway</module> 24 | </modules> 25 | <properties> 26 | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 27 | <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> 28 | <java.version>1.8</java.version> 29 | <maven.compile.source>1.8</maven.compile.source> 30 | <maven.compile.target>1.8</maven.compile.target> 31 | </properties> 32 | 33 | <dependencies> 34 | <dependency> 35 | <groupId>org.projectlombok</groupId> 36 | <artifactId>lombok</artifactId> 37 | <version>1.16.8</version> 38 | <scope>provided</scope> 39 | </dependency> 40 | </dependencies> 41 | <build> 42 | <plugins> 43 | <plugin> 44 | <groupId>org.apache.maven.plugins</groupId> 45 | <artifactId>maven-compiler-plugin</artifactId> 46 | <version>${maven-compiler-plugin.version}</version> 47 | <configuration> 48 | <source>${maven.compile.source}</source> 49 | <target>${maven.compile.target}</target> 50 | <encoding>${project.build.sourceEncoding}</encoding> 51 | </configuration> 52 | </plugin> 53 | </plugins> 54 | </build> 55 | </project> 56 | --------------------------------------------------------------------------------