├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── README-CN.md ├── README.md ├── build ├── build.js ├── check-versions.js ├── dev-client.js ├── dev-server.js ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.box.config.js ├── webpack.dev.conf.js ├── webpack.gui-server.config.js └── webpack.prod.conf.js ├── config └── index.js ├── lib ├── .DS_Store ├── handler │ └── DataTransactionHandler.js ├── index.js ├── routes │ ├── api.js │ ├── demo.js │ └── rpc.js ├── services │ ├── DatasourceService.js │ ├── FaucetService.js │ ├── GXChainService.js │ ├── IPFSService.js │ ├── LevelDBService.js │ ├── MerchantService.js │ └── TimeoutService.js ├── tasks │ ├── DatasourceTask.js │ └── MerchantTask.js └── utils │ ├── ConfigStore.js │ ├── constants.js │ └── validator.js ├── package.json ├── script ├── start.cmd └── start.sh ├── server ├── index.js ├── routes │ └── api.js ├── services │ ├── AccountService.js │ ├── BoxService.js │ ├── ConfigStore.js │ ├── ConnectService.js │ ├── DataService.js │ └── ZipArchive.js └── utils │ └── dictionary_en.js ├── src ├── app.vue ├── libs │ ├── china_regions │ │ ├── area.js │ │ ├── city.js │ │ ├── index.js │ │ └── province.js │ ├── handler.js │ └── util.js ├── main.js ├── router.js ├── template │ └── index.ejs ├── vendors.js └── views │ ├── 404.vue │ ├── Console.vue │ ├── Init.vue │ ├── League.vue │ ├── Market.vue │ ├── Product.vue │ ├── Setting.vue │ ├── common │ ├── Footer.vue │ └── Header.vue │ └── components │ ├── AccountCertification.vue │ ├── AccountConfig.vue │ ├── AccountCreate.vue │ ├── AccountImage.vue │ ├── AccountType.vue │ ├── ConsoleManage.vue │ ├── EnvType.vue │ ├── GxbBoxStart.vue │ ├── SettingAccount.vue │ ├── SettingApi.vue │ ├── SettingArchive.vue │ └── SettingConfig.vue ├── static └── img │ ├── gxb-box.png │ ├── init.svg │ └── logo.png ├── test ├── connect.js ├── data_product.js ├── encrypt_decrypt.js ├── faucet.js ├── ipfs.js ├── league.js └── leveldb.js └── upgrade.sh /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env", "stage-2"], 3 | "plugins": [] 4 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | charset = utf-8 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | test 3 | config 4 | server/utils/dictionary_en.js 5 | src/vendors.js 6 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "standard", 3 | "root": true, 4 | "parserOptions": { 5 | "ecmaVersion": 6, 6 | "sourceType": "module", 7 | "ecmaFeatures": { 8 | "experimentalObjectRestSpread": true 9 | } 10 | }, 11 | "env": { 12 | "node": true, 13 | "browser": true, 14 | "es6": true, 15 | "amd": true 16 | }, 17 | "plugins": ["html", "vue", "standard"], 18 | "rules": { 19 | "indent": ["error", 4, { "SwitchCase": 1 }], 20 | "quotes": ["error", "single"], 21 | "semi": ["error", "always", { "omitLastInOneLineBlock": true }], 22 | "no-debugger": ["error"], 23 | "no-console": ["off"], 24 | "no-new": ["off"], 25 | "eqeqeq": ["off"], 26 | "no-extend-native": ["off"], 27 | "camelcase": ["off"], 28 | "vue/jsx-uses-vars": 2 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | .idea 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | es5 27 | dist 28 | 29 | # Dependency directory 30 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 31 | node_modules 32 | 33 | # development 34 | release 35 | .data 36 | sample 37 | 38 | # prod 39 | .DS_Store 40 | */.DS_Store 41 | archive 42 | -------------------------------------------------------------------------------- /README-CN.md: -------------------------------------------------------------------------------- 1 | # 产品介绍 ([For Eglish](README.md)) 2 | 3 | 公信宝数据交易客户端GXB-Box是基于Nodejs开发的一个部署在商户和数据源本地的客户端,商户和数据源可以通过本地调用的方式购买和出售数据,数据交易的全程请求参数和回传数据都是经过加密处理的,而GXB-BOX简化了这样的一个流程。 4 | 5 | ## 环境依赖 6 | 7 | 必要环境: Node 6+ 8 | 9 | 建议系统: OSX、Linux 10 | 11 | ## Node环境安装 12 | 13 | 建议使用NVM([Node Version Manager](https://github.com/creationix/nvm))进行安装: 14 | 15 | ``` 16 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.30.2/install.sh | bash 17 | . ~/.nvm/nvm.sh 18 | nvm install v6 19 | nvm use v6 20 | ``` 21 | 22 | ## 克隆项目 23 | 24 | ``` 25 | git clone https://github.com/gxchain/gxb-box.git 26 | ``` 27 | ## 快速开始 28 | 29 | ``` 30 | npm install 31 | npm run build 32 | npm run gui 33 | ``` 34 | 35 | ## 开发模式启动 36 | 37 | 开发模式依赖于babel-node, 在克隆的工程下执行以下命令安装依赖: 38 | 39 | ``` 40 | npm install -g babel-node 41 | npm install 42 | npm start 43 | ``` 44 | 45 | ## 常见问题 46 | 47 | ### Q: 在设置了多重签名后,数据交易失败了 48 | A: 多重签名涉及到效率问题, 数据交易采用活跃权限进行单重签名, 请勿在参与数据交易的账户中设置多重签名,以免造成签名验证失败 49 | 50 | ### Q: 发生错误:"获取初始信息失败,请检查:账号(merchant或者datasource)是否正确配置" 51 | A: 检查config/config.json文件是否配置了错误的merchant或者datasource账户名, 如果是商户则**不需要**datasource配置,如果是数据源则**不一定需要**商户配置 52 | 53 | 商户配置示例: 54 | 55 | ``` 56 | { 57 | "common": { 58 | "port":"3000", 59 | "ipfs_addr": "/ip4/139.196.138.193/tcp/5001", 60 | "witnesses": [ 61 | "wss://node1.gxb.io","wss://node5.gxb.io","wss://node8.gxb.io","wss://node11.gxb.io" 62 | ], 63 | "faucet_url": "https://opengateway.gxb.io" 64 | }, 65 | "merchant":{ 66 | "account_name": "sample_user", 67 | "private_key":"5Ka9YjFQtfUUX2DdnqkaPWH1rVeSeby7Cj2VdjRt79S9kKLvXR7", 68 | "callback_url":"http://localhost:3000/demo/callback", 69 | "privacy_request_timeout":120000, 70 | "default_timeout":8000 71 | } 72 | } 73 | ``` 74 | 75 | 数据源配置示例: 76 | 77 | ``` 78 | { 79 | "common": { 80 | "port":"3000", 81 | "ipfs_addr": "/ip4/139.196.138.193/tcp/5001", 82 | "witnesses": [ 83 | "wss://node1.gxb.io","wss://node5.gxb.io","wss://node8.gxb.io","wss://node11.gxb.io" 84 | ], 85 | "faucet_url": "https://opengateway.gxb.io" 86 | }, 87 | "datasource": { 88 | "account_name": "sample_datasource", 89 | "private_key": "5JLL3mqAFt2YHVJf8W3h9oUPP2sjceLYSSyEbSt1yMjeucxGH98", 90 | "service": "http://localhost:3000/demo/call", 91 | "subscribed_data_product": [ 92 | "1.17.1" 93 | ] 94 | } 95 | } 96 | ``` 97 | 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction ([中文](README-CN.md)) 2 | GXB-BOX is a client-side, deployed at local merchant and datasource, and based on Node.js. 3 | Merchant and datasource can trade data via locally call, data transfer is all the way encrypted, and GXB-BOX simplified this process. 4 | ## System requirement 5 | 6 | (Required): Node 6+ 7 | 8 | (Operation system): OSX、Linux 9 | 10 | ## Install under Node environment 11 | 12 | Recommend to use NVM([Node Version Manager](https://github.com/creationix/nvm)) for installation: 13 | 14 | ``` 15 | curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.30.2/install.sh | bash 16 | . ~/.nvm/nvm.sh 17 | nvm install v6 18 | nvm use v6 19 | ``` 20 | 21 | ## Clone project 22 | 23 | ``` 24 | git clone https://github.com/gxchain/gxb-box.git 25 | ``` 26 | 27 | ## Quick start 28 | 29 | ``` 30 | npm install 31 | npm run build 32 | npm run gui 33 | ``` 34 | 35 | ## Development mode start 36 | 37 | Development mode depends on babel-node, execute the following commands to install dependencies under cloned engineering mode: 38 | 39 | ``` 40 | npm install -g babel-node 41 | npm install 42 | npm start 43 | ``` 44 | 45 | ## FAQ 46 | 47 | ### Q: Data transaction failed after set multi-signature 48 | A: We recommend to use single signature for data transactions, due to the efficiency limitation of multi-signature 49 | ### Q: Error: "failed to get initial information, please check configuration of merchant account and datasource account". 50 | A: Check config/config.json to see if the name of merchant or datasource is correct. For merchant, NO need for datasource configuration; for datasource, do not have to perform merchant configuration. 51 | 52 | merchant configuration sample: 53 | 54 | ``` 55 | { 56 | "common": { 57 | "port":"3000", 58 | "ipfs_addr": "/ip4/139.196.138.193/tcp/5001", 59 | "witnesses": [ 60 | "wss://node1.gxb.io","wss://node5.gxb.io","wss://node8.gxb.io","wss://node11.gxb.io" 61 | ], 62 | "faucet_url": "https://opengateway.gxb.io" 63 | }, 64 | "merchant":{ 65 | "account_name": "sample_user", 66 | "private_key":"5Ka9YjFQtfUUX2DdnqkaPWH1rVeSeby7Cj2VdjRt79S9kKLvXR7", 67 | "callback_url":"http://localhost:3000/demo/callback", 68 | "privacy_request_timeout":120000, 69 | "default_timeout":8000 70 | } 71 | } 72 | ``` 73 | 74 | Datasource configuration sample: 75 | 76 | ``` 77 | { 78 | "common": { 79 | "port":"3000", 80 | "ipfs_addr": "/ip4/139.196.138.193/tcp/5001", 81 | "witnesses": [ 82 | "wss://node1.gxb.io","wss://node5.gxb.io","wss://node8.gxb.io","wss://node11.gxb.io" 83 | ], 84 | "faucet_url": "https://opengateway.gxb.io" 85 | }, 86 | "datasource": { 87 | "account_name": "sample_datasource", 88 | "private_key": "5JLL3mqAFt2YHVJf8W3h9oUPP2sjceLYSSyEbSt1yMjeucxGH98", 89 | "service": "http://localhost:3000/demo/call", 90 | "subscribed_data_product": [ 91 | "1.17.1" 92 | ] 93 | } 94 | } 95 | ``` 96 | -------------------------------------------------------------------------------- /build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')(); 2 | 3 | process.env.NODE_ENV = 'production'; 4 | 5 | var ora = require('ora'); 6 | var rm = require('rimraf'); 7 | var path = require('path'); 8 | var chalk = require('chalk'); 9 | var webpack = require('webpack'); 10 | var config = require('../config'); 11 | var webpackConfig = require('./webpack.prod.conf'); 12 | 13 | var spinner = ora('building for production...'); 14 | spinner.start(); 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err; 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop(); 20 | if (err) throw err; 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n'); 28 | 29 | console.log(chalk.cyan(' Build complete.\n')); 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk'); 2 | var semver = require('semver'); 3 | var packageConfig = require('../package.json'); 4 | var shell = require('shelljs'); 5 | 6 | function exec (cmd) { 7 | return require('child_process').execSync(cmd).toString().trim(); 8 | } 9 | 10 | var versionRequirements = [ 11 | { 12 | name: 'node', 13 | currentVersion: semver.clean(process.version), 14 | versionRequirement: packageConfig.engines.node 15 | } 16 | ]; 17 | 18 | module.exports = function () { 19 | var warnings = []; 20 | for (var i = 0; i < versionRequirements.length; i++) { 21 | var mod = versionRequirements[i]; 22 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 23 | warnings.push(mod.name + ': ' + 24 | chalk.red(mod.currentVersion) + ' should be ' + 25 | chalk.green(mod.versionRequirement) 26 | ); 27 | } 28 | } 29 | 30 | if (warnings.length) { 31 | console.log(''); 32 | console.log(chalk.yellow('To use this template, you must update following to modules:')); 33 | console.log(); 34 | for (var i = 0; i < warnings.length; i++) { 35 | var warning = warnings[i]; 36 | console.log(' ' + warning); 37 | } 38 | console.log(); 39 | process.exit(1); 40 | } 41 | }; 42 | -------------------------------------------------------------------------------- /build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill'); 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true'); 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload(); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')(); 2 | 3 | var config = require('../config'); 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV); 6 | } 7 | 8 | var opn = require('opn'); 9 | var path = require('path'); 10 | var express = require('express'); 11 | var webpack = require('webpack'); 12 | var proxyMiddleware = require('http-proxy-middleware'); 13 | var webpackConfig = require('./webpack.dev.conf'); 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port; 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser; 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable; 22 | 23 | var app = express(); 24 | var compiler = webpack(webpackConfig); 25 | 26 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true 29 | }); 30 | 31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | log: () => { 33 | }, 34 | heartbeat: 2000 35 | }); 36 | // force page reload when html-webpack-plugin template changes 37 | compiler.plugin('compilation', function (compilation) { 38 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 39 | hotMiddleware.publish({action: 'reload'}); 40 | cb(); 41 | }); 42 | }); 43 | 44 | // proxy api requests 45 | Object.keys(proxyTable).forEach(function (context) { 46 | var options = proxyTable[context]; 47 | if (typeof options === 'string') { 48 | options = {target: options}; 49 | } 50 | app.use(proxyMiddleware(options.filter || context, options)); 51 | }); 52 | 53 | // handle fallback for HTML5 history API 54 | app.use(require('connect-history-api-fallback')()); 55 | 56 | // serve webpack bundle output 57 | app.use(devMiddleware); 58 | 59 | // enable hot-reload and state-preserving 60 | // compilation error display 61 | app.use(hotMiddleware); 62 | 63 | // serve pure static assets 64 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory); 65 | app.use(staticPath, express.static('./static')); 66 | 67 | var uri = 'http://localhost:' + port; 68 | 69 | var _resolve; 70 | var readyPromise = new Promise(resolve => { 71 | _resolve = resolve; 72 | }); 73 | 74 | console.log('> Starting dev server...'); 75 | devMiddleware.waitUntilValid(() => { 76 | console.log('> Listening at ' + uri + '\n'); 77 | // when env is testing, don't need open it 78 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 79 | opn(uri); 80 | } 81 | _resolve(); 82 | }); 83 | 84 | var server = app.listen(port); 85 | 86 | module.exports = { 87 | ready: readyPromise, 88 | close: () => { 89 | server.close(); 90 | } 91 | }; 92 | -------------------------------------------------------------------------------- /build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var config = require('../config'); 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'production' 7 | ? config.build.assetsSubDirectory 8 | : config.dev.assetsSubDirectory; 9 | return path.posix.join(assetsSubDirectory, _path); 10 | }; 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {}; 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV === 'production', 19 | sourceMap: options.sourceMap 20 | } 21 | }; 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader]; 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }); 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }); 42 | } else { 43 | return ['vue-style-loader'].concat(loaders); 44 | } 45 | } 46 | 47 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', {indentedSyntax: true}), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | }; 57 | }; 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = []; 62 | var loaders = exports.cssLoaders(options); 63 | for (var extension in loaders) { 64 | var loader = loaders[extension]; 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }); 69 | } 70 | return output; 71 | }; 72 | -------------------------------------------------------------------------------- /build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var config = require('../config'); 3 | var isProduction = process.env.NODE_ENV === 'production'; 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }), 12 | postcss: [ 13 | require('autoprefixer')({ 14 | browsers: ['last 7 versions'] 15 | }) 16 | ], 17 | transformToRequire: { 18 | video: 'src', 19 | source: 'src', 20 | img: 'src', 21 | image: 'xlink:href' 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var utils = require('./utils'); 3 | var config = require('../config'); 4 | var vueLoaderConfig = require('./vue-loader.conf'); 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir); 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'production' 18 | ? config.build.assetsPublicPath 19 | : config.dev.assetsPublicPath 20 | }, 21 | resolve: { 22 | extensions: ['.js', '.vue', '.json'], 23 | alias: { 24 | 'vue$': 'vue/dist/vue.esm.js', 25 | '@': resolve('src') 26 | } 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | test: /\.(js|vue)$/, 32 | loader: 'eslint-loader', 33 | enforce: 'pre', 34 | include: [resolve('src'), resolve('test')], 35 | options: { 36 | formatter: require('eslint-friendly-formatter') 37 | } 38 | }, 39 | { 40 | test: /\.vue$/, 41 | loader: 'vue-loader', 42 | options: vueLoaderConfig 43 | }, 44 | { 45 | test: /\.js$/, 46 | loader: 'babel-loader', 47 | include: [resolve('src'), resolve('test')] 48 | }, 49 | { 50 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 51 | loader: 'url-loader', 52 | options: { 53 | limit: 10000, 54 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 55 | } 56 | }, 57 | { 58 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 59 | loader: 'url-loader', 60 | options: { 61 | limit: 10000, 62 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 63 | } 64 | }, 65 | { 66 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 67 | loader: 'url-loader', 68 | options: { 69 | limit: 10000, 70 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 71 | } 72 | } 73 | ] 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /build/webpack.box.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var nodeModules = {}; 6 | fs.readdirSync('node_modules') 7 | .filter(function(x) { 8 | return ['.bin'].indexOf(x) === -1; 9 | }) 10 | .forEach(function(mod) { 11 | nodeModules[mod] = 'commonjs ' + mod; 12 | }); 13 | 14 | module.exports = { 15 | entry: ['../lib/index.js'], 16 | output: { 17 | path: path.resolve(__dirname, '../dist/box'), 18 | filename: 'gxb-box.js' 19 | }, 20 | target: 'node', 21 | externals: nodeModules, 22 | context: __dirname, 23 | module: { 24 | loaders: [{ 25 | test: /\.js$/, 26 | loader: 'babel-loader', 27 | exclude: [ 28 | path.resolve(__dirname, "../node_modules") 29 | ], 30 | query: { 31 | plugins: ['transform-runtime'], 32 | presets: ['es2015', 'stage-2'], 33 | } 34 | }, { 35 | test: /\.json$/, 36 | loader: 'json-loader' 37 | }] 38 | }, 39 | resolve: { 40 | extensions: ['.js', '.json'] 41 | }, 42 | plugins: [ 43 | // new webpack.optimize.UglifyJsPlugin({ 44 | // exclude: /\.min\.js$/, 45 | // mangle: true, 46 | // output: {comments: false}, 47 | // compress: {warnings: false} 48 | // }) 49 | ] 50 | } -------------------------------------------------------------------------------- /build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils'); 2 | var webpack = require('webpack'); 3 | var config = require('../config'); 4 | var merge = require('webpack-merge'); 5 | var baseWebpackConfig = require('./webpack.base.conf'); 6 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin'); 8 | 9 | // add hot-reload related code to entry chunks 10 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 11 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]); 12 | }); 13 | 14 | module.exports = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({sourceMap: config.dev.cssSourceMap}) 17 | }, 18 | // cheap-module-eval-source-map is faster for development 19 | devtool: '#cheap-module-eval-source-map', 20 | plugins: [ 21 | new webpack.DefinePlugin({ 22 | 'process.env': config.dev.env, 23 | '__witnesses__': JSON.stringify(config.dev.witnesses) 24 | }), 25 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 26 | new webpack.HotModuleReplacementPlugin(), 27 | new webpack.NoEmitOnErrorsPlugin(), 28 | // https://github.com/ampedandwired/html-webpack-plugin 29 | new HtmlWebpackPlugin({ 30 | filename: 'index.html', 31 | template: './src/template/index.ejs', 32 | inject: true 33 | }), 34 | new FriendlyErrorsPlugin() 35 | ] 36 | }); 37 | -------------------------------------------------------------------------------- /build/webpack.gui-server.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var nodeModules = {}; 6 | fs.readdirSync('node_modules') 7 | .filter(function (x) { 8 | return ['.bin'].indexOf(x) === -1; 9 | }) 10 | .forEach(function (mod) { 11 | nodeModules[mod] = 'commonjs ' + mod; 12 | }); 13 | 14 | module.exports = { 15 | entry: ['../server/index.js'], 16 | output: { 17 | path: path.resolve(__dirname, '../dist/gui-server'), 18 | filename: 'index.js' 19 | }, 20 | target: 'node', 21 | externals: nodeModules, 22 | context: __dirname, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.js$/, 27 | loader: 'eslint-loader', 28 | enforce: 'pre', 29 | exclude: [ 30 | path.resolve(__dirname, '../node_modules') 31 | ], 32 | options: { 33 | formatter: require('eslint-friendly-formatter') 34 | } 35 | }, 36 | { 37 | test: /\.js$/, 38 | loader: 'babel-loader', 39 | exclude: [ 40 | path.resolve(__dirname, '../node_modules') 41 | ], 42 | query: { 43 | plugins: ['transform-runtime'], 44 | presets: ['es2015', 'stage-2'] 45 | } 46 | }, 47 | { 48 | test: /\.json$/, 49 | loader: 'json-loader' 50 | } 51 | ] 52 | }, 53 | resolve: { 54 | extensions: ['.js', '.json'] 55 | }, 56 | plugins: [ 57 | new webpack.optimize.UglifyJsPlugin({ 58 | exclude: /\.min\.js$/, 59 | mangle: true, 60 | output: {comments: false}, 61 | compress: {warnings: false} 62 | }) 63 | ] 64 | }; 65 | -------------------------------------------------------------------------------- /build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var utils = require('./utils'); 3 | var webpack = require('webpack'); 4 | var config = require('../config'); 5 | var merge = require('webpack-merge'); 6 | var baseWebpackConfig = require('./webpack.base.conf'); 7 | var CopyWebpackPlugin = require('copy-webpack-plugin'); 8 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 9 | var ExtractTextPlugin = require('extract-text-webpack-plugin'); 10 | var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin'); 11 | 12 | var env = config.build.env; 13 | 14 | var webpackConfig = merge(baseWebpackConfig, { 15 | module: { 16 | rules: utils.styleLoaders({ 17 | sourceMap: config.build.productionSourceMap, 18 | extract: true 19 | }) 20 | }, 21 | devtool: config.build.productionSourceMap ? '#source-map' : false, 22 | output: { 23 | path: config.build.assetsRoot, 24 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 25 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 26 | }, 27 | plugins: [ 28 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 29 | new webpack.DefinePlugin({ 30 | 'process.env': env, 31 | '__witnesses__': JSON.stringify(config.build.witnesses) 32 | }), 33 | new webpack.optimize.UglifyJsPlugin({ 34 | compress: { 35 | warnings: false 36 | }, 37 | sourceMap: true 38 | }), 39 | // extract css into its own file 40 | new ExtractTextPlugin({ 41 | filename: utils.assetsPath('css/[name].[contenthash].css') 42 | }), 43 | // Compress extracted CSS. We are using this plugin so that possible 44 | // duplicated CSS from different components can be deduped. 45 | new OptimizeCSSPlugin({ 46 | cssProcessorOptions: { 47 | safe: true 48 | } 49 | }), 50 | // generate dist index.html with correct asset hash for caching. 51 | // you can customize output by editing /index.html 52 | // see https://github.com/ampedandwired/html-webpack-plugin 53 | new HtmlWebpackPlugin({ 54 | filename: config.build.index, 55 | template: './src/template/index.ejs', 56 | inject: true, 57 | minify: { 58 | removeComments: true, 59 | collapseWhitespace: true, 60 | removeAttributeQuotes: true 61 | // more options: 62 | // https://github.com/kangax/html-minifier#options-quick-reference 63 | }, 64 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 65 | chunksSortMode: 'dependency' 66 | }), 67 | // split vendor js into its own file 68 | new webpack.optimize.CommonsChunkPlugin({ 69 | name: 'vendor', 70 | minChunks: function (module, count) { 71 | // any required modules inside node_modules are extracted to vendor 72 | return ( 73 | module.resource && 74 | /\.js$/.test(module.resource) && 75 | module.resource.indexOf( 76 | path.join(__dirname, '../node_modules') 77 | ) === 0 78 | ); 79 | } 80 | }), 81 | // extract webpack runtime and module manifest to its own file in order to 82 | // prevent vendor hash from being updated whenever app bundle is updated 83 | new webpack.optimize.CommonsChunkPlugin({ 84 | name: 'manifest', 85 | chunks: ['vendor'] 86 | }), 87 | // copy custom static assets 88 | new CopyWebpackPlugin([ 89 | { 90 | from: path.resolve(__dirname, '../static'), 91 | to: config.build.assetsSubDirectory, 92 | ignore: ['.*'] 93 | } 94 | ]) 95 | ] 96 | }); 97 | 98 | if (config.build.productionGzip) { 99 | var CompressionWebpackPlugin = require('compression-webpack-plugin'); 100 | 101 | webpackConfig.plugins.push( 102 | new CompressionWebpackPlugin({ 103 | asset: '[path].gz[query]', 104 | algorithm: 'gzip', 105 | test: new RegExp( 106 | '\\.(' + 107 | config.build.productionGzipExtensions.join('|') + 108 | ')$' 109 | ), 110 | threshold: 10240, 111 | minRatio: 0.8 112 | }) 113 | ); 114 | } 115 | 116 | if (config.build.bundleAnalyzerReport) { 117 | var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 118 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()); 119 | } 120 | 121 | module.exports = webpackConfig; 122 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | 4 | module.exports = { 5 | build: { 6 | env: { 7 | NODE_ENV: '"production"' 8 | }, 9 | index: path.resolve(__dirname, '../dist/gui/index.html'), 10 | assetsRoot: path.resolve(__dirname, '../dist/gui'), 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | productionSourceMap: false, 14 | witnesses: [ 15 | "wss://node1.gxb.io", 16 | "wss://node5.gxb.io", 17 | "wss://node8.gxb.io", 18 | "wss://node11.gxb.io" 19 | ], 20 | faucet_url: "https://opengateway.gxb.io", 21 | referrer: "opengateway", 22 | // Gzip off by default as many popular static hosts such as 23 | // Surge or Netlify already gzip all static assets for you. 24 | // Before setting to `true`, make sure to: 25 | // npm install --save-dev compression-webpack-plugin 26 | productionGzip: false, 27 | productionGzipExtensions: ['js', 'css'], 28 | // Run the build command with an extra argument to 29 | // View the bundle analyzer report after build finishes: 30 | // `npm run build --report` 31 | // Set to `true` or `false` to always turn it on or off 32 | bundleAnalyzerReport: process.env.npm_config_report 33 | }, 34 | dev: { 35 | env: { 36 | NODE_ENV: '"development"' 37 | }, 38 | port: 8080, 39 | autoOpenBrowser: true, 40 | assetsSubDirectory: 'static', 41 | assetsPublicPath: '/', 42 | witnesses: [ 43 | "ws://47.96.164.78:28090" 44 | ], 45 | faucet_url: "http://47.96.164.78:8888", 46 | referrer: "nathan", 47 | // CSS Sourcemaps off by default because relative paths are "buggy" 48 | // with this option, according to the CSS-Loader README 49 | // (https://github.com/webpack/css-loader#sourcemaps) 50 | // In our experience, they generally work as expected, 51 | // just be aware of this issue when enabling this option. 52 | cssSourceMap: false 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /lib/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxchain/gxb-box/ec843dc6a92106477e8fa8fb1bfab1286eceeb33/lib/.DS_Store -------------------------------------------------------------------------------- /lib/handler/DataTransactionHandler.js: -------------------------------------------------------------------------------- 1 | import ConfigStore from '../utils/ConfigStore'; 2 | import MerchantTask from '../tasks/MerchantTask'; 3 | import DatasourceTask from '../tasks/DatasourceTask'; 4 | import {TRANSACTION_STATUS_MAP} from '../utils/constants'; 5 | 6 | export default { 7 | 8 | /** 9 | * 数据交易调度函数 10 | * @param data_transaction 11 | */ 12 | schedule (data_transaction) { 13 | let request_id = data_transaction.request_id; 14 | let product_id = data_transaction.product_id; 15 | let status = TRANSACTION_STATUS_MAP[data_transaction.status]; 16 | let config = ConfigStore.config; 17 | let is_product_subscribed = config.datasource && config.datasource.subscribed_data_product && config.datasource.subscribed_data_product.find(function (prod) { 18 | return prod == product_id; 19 | }); 20 | let is_request_in_queue = MerchantTask.exist(request_id); 21 | 22 | // 已确认的交易, 数据源可以回传数据 23 | if (status == TRANSACTION_STATUS_MAP.CONFIRMED && is_product_subscribed) { 24 | DatasourceTask.deal_with_data_transaction(data_transaction); 25 | } 26 | // 已确认的交易, 商户根据状态进行处理 27 | if (status == TRANSACTION_STATUS_MAP.CONFIRMED && is_request_in_queue) { 28 | MerchantTask.deal_with_data_transaction(data_transaction); 29 | } 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import logger from 'morgan'; 3 | import bodyParser from 'body-parser'; 4 | import http from 'http'; 5 | import Promise from 'bluebird'; 6 | import {Apis, Manager} from 'gxbjs-ws'; 7 | import {ChainStore} from 'gxbjs'; 8 | import DataTransactionHandler from './handler/DataTransactionHandler'; 9 | import MerchantTask from './tasks/MerchantTask'; 10 | import DatasourceTask from './tasks/DatasourceTask'; 11 | import GXChainService from './services/GXChainService'; 12 | import LevelDBService from './services/LevelDBService'; 13 | import ConfigStore from './utils/ConfigStore'; 14 | 15 | import figlet from 'figlet'; 16 | import colors from 'colors/safe'; 17 | 18 | let app = express(); 19 | let connected = false; 20 | 21 | app.use(logger('dev')); 22 | app.use(bodyParser.json()); 23 | app.use(bodyParser.urlencoded({extended: false})); 24 | 25 | app.use(function (req, res, next) { 26 | res.header('Access-Control-Allow-Origin', '*'); 27 | res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); 28 | next(); 29 | }); 30 | 31 | const connectedCheck = function (req, res, next) { 32 | if (connected) { 33 | next(); 34 | } else { 35 | res.status(500).send({ 36 | code: 'UNKNOWN_ERROR', 37 | message: '正在连接网络,请稍后再试' 38 | }); 39 | } 40 | }; 41 | 42 | app.use('/rpc', connectedCheck, require('./routes/rpc')); 43 | app.use('/demo', connectedCheck, require('./routes/demo')); 44 | app.use('/api', connectedCheck, require('./routes/api')); 45 | 46 | app.use(function (req, res, next) { 47 | var err = new Error('Not Found'); 48 | err.status = 404; 49 | next(err); 50 | }); 51 | 52 | if (app.get('env') === 'development') { 53 | app.use(function (err, req, res, next) { 54 | res.status(err.status || 500); 55 | res.send({ 56 | message: err.message, 57 | error: err 58 | }); 59 | }); 60 | } 61 | 62 | app.use(function (err, req, res, next) { 63 | res.status(err.status || 500); 64 | res.send({ 65 | message: err.message, 66 | error: {} 67 | }); 68 | }); 69 | 70 | const filterAndSortURLs = (latencies, witnesses) => { 71 | let us = witnesses 72 | .filter(a => { 73 | /* Only keep the nodes we were able to connect to */ 74 | return !!latencies[a]; 75 | }) 76 | .sort((a, b) => { 77 | return latencies[a] - latencies[b]; 78 | }); 79 | return us; 80 | }; 81 | 82 | Promise.all([ 83 | ConfigStore.init(), 84 | MerchantTask.init(), 85 | DatasourceTask.init() 86 | ]).then((results) => { 87 | let config = results[0]; 88 | let witnesses = (config && config.common && config.common.witnesses) || []; 89 | let connectionManager = new Manager({url: witnesses[0], urls: witnesses}); 90 | if (witnesses.length == 0) { 91 | console.error('未配置启动节点,请先在config.json文件中配置common.witnesses'); 92 | return; 93 | } 94 | /** 95 | * 连接witness 96 | * @param callback 97 | */ 98 | let connect = function (callback) { 99 | connectionManager.checkConnections().then((resp) => { 100 | console.log('延迟\n', JSON.stringify(resp, null, '\t')); 101 | let urls = filterAndSortURLs(resp, witnesses); 102 | if (urls.length == 0) { 103 | // setTimeout(function () { 104 | // connect(callback); 105 | // }, 3000); 106 | console.log('witnesses', witnesses); 107 | connectionManager.url = witnesses[0]; 108 | connectionManager.urls = witnesses; 109 | 110 | connectionManager.connectWithFallback(true).then(() => { 111 | console.log('已连接'); 112 | connected = true; 113 | callback && callback(); 114 | }).catch((ex) => { 115 | console.error('连接失败,3秒后重试', ex.message); 116 | setTimeout(function () { 117 | connect(callback); 118 | }, 3000); 119 | }); 120 | } else { 121 | connectionManager.urls = urls; 122 | connectionManager.connectWithFallback(true).then(() => { 123 | console.log('已连接'); 124 | connected = true; 125 | callback && callback(); 126 | }).catch((ex) => { 127 | console.error('连接失败,3秒后重试', ex.message); 128 | setTimeout(function () { 129 | connect(callback); 130 | }, 3000); 131 | }); 132 | } 133 | }).catch((ex) => { 134 | console.error('检查连接失败,3秒后重试', ex.message); 135 | setTimeout(function () { 136 | connect(callback); 137 | }, 3000); 138 | }); 139 | }; 140 | 141 | /** 142 | * 启动web服务 143 | */ 144 | let serverStarted = false; 145 | let startServer = function () { 146 | if (serverStarted) { 147 | return; 148 | } 149 | serverStarted = true; 150 | let port = parseInt(config.common.port || '3000'); 151 | app.set('port', port); 152 | let server = http.createServer(app); 153 | server.listen(port); 154 | server.on('error', onError); 155 | server.on('listening', () => { 156 | var addr = server.address(); 157 | var bind = typeof addr === 'string' 158 | ? 'pipe ' + addr 159 | : 'port ' + addr.port; 160 | console.log('Listening on ' + bind); 161 | }); 162 | figlet('GXB-BOX', 'ANSI Shadow', function (err, text) { 163 | if (err) { 164 | console.error(err); 165 | } 166 | console.log(colors.rainbow('\n=*=*=*=*=*=*=*=*=*==*=*= 公信宝数据交易客户端已启动 =*=*=*==*=*=*=*=*=*=*=*=\n')); 167 | console.log(colors.cyan(`${(text || '').split('\n').map(function (line) { 168 | return `\t${line}`; 169 | }).join('\n')}`)); 170 | console.log(colors.rainbow('=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=**=*=*=*=*=*=*==*=*=*=\n')); 171 | }); 172 | }; 173 | 174 | /** 175 | * 数据交易消息订阅 176 | * @param data_transactions 177 | */ 178 | let subscriber = function (data_transactions) { 179 | data_transactions.forEach(function (data_transaction) { 180 | DataTransactionHandler.schedule(data_transaction); 181 | }); 182 | }; 183 | 184 | /** 185 | * 初始化连接 186 | */ 187 | let initConnection = function () { 188 | console.log('初始化数据缓存'); 189 | let promises = [ 190 | ChainStore.init() 191 | ]; 192 | if (config.merchant && config.merchant.account_name) { 193 | promises.push(GXChainService.fetch_account(config.merchant.account_name)); 194 | } 195 | if (config.datasource && config.datasource.account_name) { 196 | promises.push(GXChainService.fetch_account(config.datasource.account_name)); 197 | } 198 | Promise.all(promises).then(function () { 199 | // 订阅数据交易广播 200 | // Apis.instance().db_api().exec('unsubscribe_from_transaction', [subscriber, true]) 201 | Apis.instance().db_api().exec('set_data_transaction_subscribe_callback', [subscriber, true]); 202 | console.log('已订阅数据交易事件'); 203 | startServer(); 204 | MerchantTask.resume(); 205 | }).catch((ex) => { 206 | let isNotSync = /ChainStore sync error/.test(ex.message); 207 | if (isNotSync) { 208 | console.error('获取初始信息失败,请检查:\n1. 节点数据是否同步 \n2. 系统时钟是否正确\n', ex); 209 | } else { 210 | console.error('获取初始信息失败,请检查:账号(merchant或者datasource)是否正确配置', ex); 211 | } 212 | }); 213 | }; 214 | // websocket 状态处理 215 | Apis.setRpcConnectionStatusCallback(function (status) { 216 | var statusMap = { 217 | open: '开启', 218 | closed: '关闭', 219 | error: '错误', 220 | reconnect: '重新连接' 221 | }; 222 | 223 | console.log('witness当前状态:', statusMap[status] || status); 224 | 225 | if (status === 'reconnect') { 226 | console.log('断开重连'); 227 | ChainStore.resetCache(); 228 | } else if (connected && (status == 'closed' || status == 'error')) { // 出错重连 229 | connected = false; 230 | console.log('重新连接其他witness'); 231 | connect(function () { 232 | ChainStore.subscribed = false; 233 | ChainStore.subError = null; 234 | ChainStore.clearCache(); 235 | ChainStore.head_block_time_string = null; 236 | initConnection(); 237 | }); 238 | } 239 | }); 240 | // 首次连接 241 | connect(function () { 242 | initConnection(); 243 | }); 244 | }).catch((ex) => { 245 | console.error('加载配置失败,请检查config.json', ex); 246 | }); 247 | 248 | /** 249 | * Event listener for HTTP server "error" event. 250 | */ 251 | function onError (error) { 252 | if (error.syscall !== 'listen') { 253 | throw error; 254 | } 255 | 256 | // handle specific listen errors with friendly messages 257 | switch (error.code) { 258 | case 'EACCES': 259 | console.error('port requires elevated privileges'); 260 | process.exit(1); 261 | break; 262 | case 'EADDRINUSE': 263 | console.error('port is already in use'); 264 | process.exit(1); 265 | break; 266 | default: 267 | throw error; 268 | } 269 | } 270 | 271 | process.stdin.resume(); 272 | 273 | function exitHandler (reason, err) { 274 | if (err) console.log(err.stack); 275 | console.log('程序退出:', reason); 276 | Promise.all([MerchantTask.store(), LevelDBService.put('last-close', new Date().getTime())]).then(function () { 277 | process.exit(); 278 | }).catch((ex) => { 279 | process.exit(); 280 | }); 281 | } 282 | 283 | // do something when app is closing 284 | process.on('exit', exitHandler.bind(null, 'exit')); 285 | 286 | // catches ctrl+c event 287 | process.on('SIGINT', exitHandler.bind(null, 'SIGINT')); 288 | 289 | // catches uncaught exceptions 290 | process.on('uncaughtException', exitHandler.bind(null, 'uncaughtException')); 291 | -------------------------------------------------------------------------------- /lib/routes/api.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import LevelDBService from '../services/LevelDBService'; 3 | import GXChainService from '../services/GXChainService'; 4 | 5 | let router = express.Router(); 6 | 7 | router.get('/request/:request_id', function (req, res) { 8 | GXChainService.get_data_transaction_by_request_id(req.params.request_id).then(function (data_transaction) { 9 | res.send(data_transaction); 10 | }).catch(ex => { 11 | res.status(500).send(ex.message); 12 | }); 13 | }); 14 | 15 | router.get('/request/:request_id/data', function (req, res) { 16 | let request_id = req.params.request_id; 17 | // 默认最多返回20条 18 | LevelDBService.find({prefix: `request-${request_id}-`}).then(function (results) { 19 | results = (results || []).map((result) => { 20 | return JSON.parse(result.value); 21 | }).reverse(); 22 | res.send(results); 23 | }).catch(ex => { 24 | res.status(404).send({ 25 | request_id: request_id, 26 | message: '未查询到结果' 27 | }); 28 | }); 29 | }); 30 | 31 | router.get('/request/:request_id/delete', function (req, res) { 32 | let request_id = req.params.request_id; 33 | LevelDBService.find({prefix: `request-${request_id}-`}).then(function (results) { 34 | (results || []).forEach((result) => { 35 | console.log(result); 36 | LevelDBService.del(result.key); 37 | }); 38 | res.send({}); 39 | }).catch(ex => { 40 | console.error(ex); 41 | res.status(500).send({ 42 | request_id: request_id, 43 | message: '删除失败' 44 | }); 45 | }); 46 | }); 47 | 48 | module.exports = router; 49 | -------------------------------------------------------------------------------- /lib/routes/demo.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | let router = express.Router(); 4 | 5 | /** 6 | * 商户-数据回调示例 7 | */ 8 | router.all('/callback', function (req, res) { 9 | let params = Object.assign({}, req.body); 10 | console.log('数据回调:'); 11 | console.log(JSON.stringify(params, null, '\t')); 12 | res.send({}); 13 | }); 14 | 15 | /** 16 | * 数据源-服务接口示例 17 | */ 18 | router.all('/call', function (req, res) { 19 | console.log(Object.assign({}, req.query, req.body)); 20 | res.send({ 21 | code: 0, 22 | data: { 23 | result: true 24 | } 25 | }); 26 | }); 27 | 28 | module.exports = router; 29 | -------------------------------------------------------------------------------- /lib/services/DatasourceService.js: -------------------------------------------------------------------------------- 1 | import superagent from 'superagent'; 2 | import Promise from 'bluebird'; 3 | import ConfigStore from '../utils/ConfigStore'; 4 | 5 | export default { 6 | fetch_data (params) { 7 | let config = ConfigStore.config; 8 | return new Promise((resolve, reject) => { 9 | superagent.post(config.datasource.service).send(params).end((err, resp) => { 10 | if (err) { 11 | console.error('获取数据失败', resp && resp.text); 12 | reject(new Error('offline')); 13 | } else { 14 | try { 15 | let result = JSON.parse(resp.text); 16 | resolve(result); 17 | } catch (ex) { 18 | console.error('返回数据格式错误:', ex); 19 | reject(new Error('invalid_format')); 20 | } 21 | } 22 | }); 23 | }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/services/FaucetService.js: -------------------------------------------------------------------------------- 1 | import ConfigStore from '../utils/ConfigStore'; 2 | import superagent from 'superagent'; 3 | import Promise from 'bluebird'; 4 | import GXChainService from './GXChainService'; 5 | 6 | import {Signature} from 'gxbjs'; 7 | 8 | const sortJSON = function (json) { 9 | var keys = Object.keys(json); 10 | keys.sort(); 11 | var result = {}; 12 | keys.forEach(function (k) { 13 | result[k] = json[k]; 14 | }); 15 | return result; 16 | }; 17 | 18 | const sign = function (body = '', pKey) { 19 | if (!pKey) { 20 | pKey = ConfigStore.get_merchant_private_key(); 21 | } 22 | return Signature.sign(body, pKey).toHex(); 23 | }; 24 | 25 | export default { 26 | 27 | /** 28 | * 商户|数据源方法 - 联盟成员请求数据交换授权令牌 29 | * @param name 姓名 30 | * @param idcard 身份证号 31 | * @param mobile 手机号 32 | * @param request_id 请求id 33 | */ 34 | request_for_a_token (requester, league_id, request_id, account_name, private_key) { 35 | let params = { 36 | league_id, 37 | request_id, 38 | requester 39 | }; 40 | let config = ConfigStore.config; 41 | 42 | return new Promise((resolve, reject) => { 43 | GXChainService.fetch_account(account_name).then((account) => { 44 | params.account_id = account.get('id'); 45 | params.signature = sign(JSON.stringify(sortJSON(params)), private_key); 46 | superagent.post(`${config.common.faucet_url}/chain/auth_token`).send(params).end(function (err, resp) { 47 | if (err) { 48 | reject(new Error(resp.body && (resp.body.message || resp.body.base[0]))); 49 | } else { 50 | resolve(resp.body); 51 | } 52 | }); 53 | }).catch((err) => { 54 | reject(err); 55 | }); 56 | }); 57 | }, 58 | 59 | /** 60 | * 商户方法 - 发送短信授权验证 61 | * @param name 62 | * @param idcard 63 | * @param mobile 64 | * @param request_id 65 | */ 66 | send_auth_msg (name, idcard, mobile, request_id) { 67 | let params = { 68 | name, 69 | idcard, 70 | mobile, 71 | request_id: request_id 72 | }; 73 | let config = ConfigStore.config; 74 | 75 | return new Promise((resolve, reject) => { 76 | GXChainService.fetch_account(config.merchant.account_name).then((account) => { 77 | params.account_id = account.get('id'); 78 | params.signature = sign(JSON.stringify(sortJSON(params))); 79 | superagent.post(`${config.common.faucet_url}/chain/auth_msg`).send(params).end(function (err, resp) { 80 | if (err) { 81 | console.error('发送授权短信失败:', resp.body && (resp.body.message || resp.body.base[0])); 82 | reject(new Error(resp && resp.body ? resp.body.message : '网络故障')); 83 | } else { 84 | resolve(resp.body); 85 | } 86 | }); 87 | }).catch((err) => { 88 | reject(err); 89 | }); 90 | }); 91 | }, 92 | 93 | /** 94 | * 数据源方法 - 保存数据hash 95 | * @param hash 96 | */ 97 | store_data: function (data, request_id, data_hash) { 98 | let params = { 99 | data, 100 | request_id, 101 | data_hash 102 | }; 103 | let config = ConfigStore.config; 104 | let datasource_private_key = ConfigStore.get_datasource_private_key(); 105 | 106 | return new Promise((resolve, reject) => { 107 | GXChainService.fetch_account(config.datasource.account_name).then((account) => { 108 | params.account_id = account.get('id'); 109 | params.signature = sign(JSON.stringify(sortJSON(params)), datasource_private_key); 110 | superagent.post(`${config.common.faucet_url}/chain/store_data`).send(params).end(function (err, resp) { 111 | if (err) { 112 | console.error('保存数据hash失败:', resp.body && (resp.body.message || resp.body.base[0])); 113 | reject(new Error(resp && resp.body ? resp.body.message || resp.body.base[0] : '网络故障')); 114 | } else { 115 | resolve(resp.body); 116 | } 117 | }); 118 | }).catch((err) => { 119 | reject(err); 120 | }); 121 | }); 122 | }, 123 | 124 | /** 125 | * 商户方法-通过request_id获取hash 126 | * @param request_id 127 | */ 128 | fetch_ipfs_hash (request_id, datasource) { 129 | let params = { 130 | request_id, 131 | datasource 132 | }; 133 | let config = ConfigStore.config; 134 | 135 | return new Promise((resolve, reject) => { 136 | GXChainService.fetch_account(config.merchant.account_name).then((account) => { 137 | params.account_id = account.get('id'); 138 | params.signature = sign(JSON.stringify(sortJSON(params))); 139 | superagent.get(`${config.common.faucet_url}/chain/get_hash`).query(params).end(function (err, resp) { 140 | if (err) { 141 | console.error('获取ipfs_hash失败:', err.body); 142 | reject(new Error(resp.body && (resp.body.message || resp.body.base[0]))); 143 | } else { 144 | resolve(resp.body); 145 | } 146 | }); 147 | }).catch((err) => { 148 | reject(err); 149 | }); 150 | }); 151 | } 152 | }; 153 | -------------------------------------------------------------------------------- /lib/services/GXChainService.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import {Apis} from 'gxbjs-ws'; 3 | import {Aes, ChainStore, FetchChain, hash, TransactionBuilder, TransactionHelper} from 'gxbjs'; 4 | import ConfigStore from '../utils/ConfigStore'; 5 | import Immutable from 'immutable'; 6 | 7 | /** 8 | * 获取账户信息 9 | * @param account_name 10 | * @returns {*} 11 | */ 12 | let account_cache = {}; 13 | const fetch_account = function (account_name) { 14 | return new Promise(function (resolve, reject) { 15 | if (account_cache[account_name]) { 16 | return resolve(account_cache[account_name]); 17 | } 18 | if (!account_name) { 19 | resolve(); 20 | } 21 | return FetchChain('getAccount', account_name).then((account) => { 22 | account_cache[account_name] = account; 23 | account_cache[account.get('id')] = account; 24 | resolve(account); 25 | }).catch((ex) => { 26 | reject(ex); 27 | }); 28 | }); 29 | }; 30 | 31 | /** 32 | * 根据账号获取该账号active public_key 33 | * @param account_id 34 | */ 35 | const get_public_key_by_id = function (account_id) { 36 | return new Promise(function (resolve, reject) { 37 | fetch_account(account_id).then(function (account) { 38 | let pubKey = account.toJS().active.key_auths[0][0]; 39 | resolve(pubKey); 40 | }).catch((ex) => { 41 | reject(ex); 42 | }); 43 | }); 44 | }; 45 | 46 | /** 47 | * 获取产品信息 48 | * @param prod_id 49 | */ 50 | const fetch_data_product = function (prod_id) { 51 | // let start = new Date(); 52 | return new Promise(function (resolve, reject) { 53 | let prod = ChainStore.objects_by_id.get(prod_id); 54 | if (prod) { 55 | prod = prod.toJS(); 56 | prod.schema_contexts = prod.schema_contexts.map(function (schema) { 57 | if (typeof schema.schema_context == 'string') { 58 | schema.schema_context = JSON.parse(schema.schema_context); 59 | } 60 | return schema; 61 | }); 62 | resolve(prod); 63 | } else { 64 | return Apis.instance().db_api().exec('get_objects', [[prod_id]]).then(function (resp) { 65 | // console.log('获取产品信息耗时:',new Date()-start); 66 | if (!resp || resp.length == 0) { 67 | reject(new Error('product not found')); 68 | } else { 69 | let prod = Object.assign({schema_contexts: []}, resp[0]); 70 | prod.schema_contexts = prod.schema_contexts.map(function (schema) { 71 | if (typeof schema.schema_context == 'string') { 72 | schema.schema_context = JSON.parse(schema.schema_context); 73 | } 74 | return schema; 75 | }); 76 | ChainStore.objects_by_id.set(prod_id, Immutable.fromJS(prod)); 77 | resolve(prod); 78 | } 79 | }).catch(function (ex) { 80 | reject(ex); 81 | }); 82 | } 83 | }); 84 | }; 85 | 86 | /** 87 | * 通过request_id获取data transaction 88 | * @param request_id 89 | */ 90 | const get_data_transaction_by_request_id = function (request_id) { 91 | return new Promise(function (resolve, reject) { 92 | return Apis.instance().db_api().exec('get_data_transaction_by_request_id', [request_id]).then(function (resp) { 93 | resolve(resp); 94 | }).catch(function (ex) { 95 | reject(ex); 96 | }); 97 | }); 98 | }; 99 | 100 | /** 101 | * 加密Json 102 | * @param params 103 | * @param private_key 104 | * @param public_key 105 | * @returns {Buffer} 106 | */ 107 | const encrypt_params = function (params, private_key, public_key) { 108 | let msg = JSON.stringify(params, null, 0); 109 | return Aes.encrypt_with_checksum(private_key, public_key, null, new Buffer(msg).toString('base64')).toString('base64'); 110 | }; 111 | 112 | /** 113 | * 解密消息体 114 | * @param msg 115 | * @param private_key 116 | */ 117 | const decrypt_msg = function (msg, private_key, public_key_string) { 118 | let base64Str = Aes.decrypt_with_checksum(private_key, public_key_string || private_key.toPublicKey().toPublicKeyString(), null, new Buffer(msg, 'base64')).toString('utf-8'); 119 | // let base64Str = Aes.decrypt_with_checksum(private_key, public_key_string?public_key_string:private_key.toPublicKey().toPublicKeyString(), null, msg).toString("utf-8"); 120 | return new Buffer(base64Str, 'base64').toString(); 121 | }; 122 | 123 | /** 124 | * 生成request_id 125 | */ 126 | const generate_request_id = function () { 127 | let merchant_private_key = ConfigStore.get_merchant_private_key(); 128 | let nonce = TransactionHelper.unique_nonce_uint64(); 129 | let request_id = hash.sha256(merchant_private_key.toPublicKey().toPublicKeyString() + nonce).toString('hex'); 130 | return request_id; 131 | }; 132 | 133 | /** 134 | * 创建数据交易请求 135 | * @param league_id 136 | * @param prod_id 137 | * @param version 138 | * @param params 139 | * @param account_id 140 | * @param token 141 | */ 142 | const create_data_transaction = function (request_id, league_id, prod_id, version, encrypted_params, account_id) { 143 | let merchant_private_key = ConfigStore.get_merchant_private_key(); 144 | return new Promise(function (resolve, reject) { 145 | let tr = new TransactionBuilder(); 146 | 147 | let base64Str = encrypted_params.toString('base64'); 148 | // let start = new Date(); 149 | let operation = { 150 | request_id: request_id, 151 | product_id: prod_id, 152 | version: version, 153 | params: base64Str, 154 | fee: { 155 | amount: 0, 156 | asset_id: '1.3.0' 157 | }, 158 | requester: account_id, 159 | create_date_time: new Date().toISOString().split('.')[0] 160 | }; 161 | if (league_id) { 162 | operation.league_id = league_id; 163 | } 164 | tr.add_type_operation('data_transaction_create', operation); 165 | // start = new Date(); 166 | tr.set_required_fees().then(() => { 167 | // console.log('获取费用耗时',new Date()-start); 168 | tr.add_signer(merchant_private_key); 169 | // start = new Date(); 170 | tr.broadcast(function (result) { 171 | // console.log('广播耗时:',new Date()-start); 172 | resolve(request_id); 173 | }).catch((ex) => { 174 | console.error('transaction create broadcast:', ex); 175 | reject(ex); 176 | }); 177 | }).catch(function (ex) { 178 | reject(ex); 179 | }); 180 | }); 181 | }; 182 | 183 | /** 184 | * 支付数据交易 185 | * @param request_id 186 | * @param from 187 | * @param to 188 | * @param amount 189 | */ 190 | const pay_data_transaction = function (request_id, from, to, amount) { 191 | let merchant_private_key = ConfigStore.get_merchant_private_key(); 192 | return new Promise(function (resolve, reject) { 193 | let tr = new TransactionBuilder(); 194 | 195 | tr.add_type_operation('data_transaction_pay', { 196 | request_id: request_id, 197 | from: from, 198 | to: to, 199 | amount: { 200 | amount: amount, 201 | asset_id: '1.3.0' 202 | }, 203 | fee: { 204 | amount: 0, 205 | asset_id: '1.3.0' 206 | } 207 | }); 208 | 209 | tr.set_required_fees().then(() => { 210 | tr.add_signer(merchant_private_key); 211 | tr.broadcast(function (result) { 212 | resolve(result); 213 | }).catch((ex) => { 214 | console.error('transaction pay broadcast:', ex); 215 | reject(ex); 216 | }); 217 | }, (ex) => { 218 | console.error('transaction pay set required fees:', ex); 219 | reject(ex); 220 | }); 221 | }); 222 | }; 223 | 224 | /** 225 | * 数据返回错误 226 | * @param request_id 227 | * @param datasource 228 | */ 229 | const data_transaction_datasource_validate_error = function (request_id, datasource) { 230 | let datasource_private_key = ConfigStore.get_datasource_private_key(); 231 | return new Promise(function (resolve, reject) { 232 | let tr = new TransactionBuilder(); 233 | tr.add_type_operation('data_transaction_datasource_validate_error', { 234 | request_id: request_id, 235 | datasource: datasource, 236 | fee: { 237 | amount: 0, 238 | asset_id: '1.3.0' 239 | } 240 | }); 241 | 242 | tr.set_required_fees().then(() => { 243 | tr.add_signer(datasource_private_key); 244 | tr.broadcast(function (result) { 245 | resolve(result); 246 | }).catch((ex) => { 247 | reject(ex); 248 | }); 249 | }, (ex) => { 250 | console.error('transaction datasource validate error set required fees:', ex); 251 | reject(ex); 252 | }); 253 | }); 254 | }; 255 | 256 | /** 257 | * 获取联盟信息 258 | * @param prod_id 259 | */ 260 | const fetch_league = function (league_id) { 261 | return new Promise(function (resolve, reject) { 262 | return Apis.instance().db_api().exec('get_objects', [[league_id]]).then(function (resp) { 263 | if (!resp || resp.length == 0) { 264 | reject(new Error('league not found')); 265 | } else { 266 | resolve(resp[0]); 267 | } 268 | }).catch(function (ex) { 269 | reject(ex); 270 | }); 271 | }); 272 | }; 273 | 274 | export default { 275 | fetch_league, 276 | fetch_account, 277 | get_public_key_by_id, 278 | fetch_data_product, 279 | encrypt_params, 280 | decrypt_msg, 281 | generate_request_id, 282 | create_data_transaction, 283 | pay_data_transaction, 284 | get_data_transaction_by_request_id, 285 | data_transaction_datasource_validate_error 286 | }; 287 | -------------------------------------------------------------------------------- /lib/services/IPFSService.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import ipfsAPI from 'ipfs-api'; 3 | 4 | export default { 5 | /** 6 | * 上传数据到ipfs 7 | * @param data 8 | */ 9 | upload: function (data, addrs) { 10 | if (!(addrs instanceof Array)) { 11 | addrs = [addrs]; 12 | } 13 | return new Promise(function (resolve, reject) { 14 | const inner = (d, index) => { 15 | let ipfs_api = ipfsAPI(addrs[index]); 16 | let obj = { 17 | Data: new Buffer(d), 18 | Links: [] 19 | }; 20 | ipfs_api.object.put(obj, function (err, node) { 21 | if (err) { 22 | reject(err); 23 | } else { 24 | let nodeJSON = node.toJSON(); 25 | resolve(nodeJSON.multihash); 26 | } 27 | }); 28 | }; 29 | inner(data, 0); 30 | }); 31 | }, 32 | /** 33 | * 通过hash从ipfs下载数据 34 | * @param hash 35 | */ 36 | download: function (hash, addrs) { 37 | if (!(addrs instanceof Array)) { 38 | addrs = [addrs]; 39 | } 40 | console.log('downloading:', hash, addrs); 41 | return new Promise(function (resolve, reject) { 42 | const inner = (h, index) => { 43 | let ipfs_api = ipfsAPI(addrs[index]); 44 | ipfs_api.object.data(h, function (err, data) { 45 | if (err) { 46 | console.error('download failed:', err); 47 | if (index == addrs.length - 1) { 48 | reject(err); 49 | } else { 50 | inner(h, index + 1); 51 | } 52 | } else { 53 | console.log('downloaded', data); 54 | resolve(data.toString()); 55 | } 56 | }); 57 | }; 58 | inner(hash, 0); 59 | }); 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /lib/services/LevelDBService.js: -------------------------------------------------------------------------------- 1 | import level from 'level'; 2 | import Promise from 'bluebird'; 3 | import async from 'async'; 4 | 5 | let db = level('./.data'); 6 | 7 | let tmp = {}; 8 | 9 | export default { 10 | 11 | put (key, val) { 12 | tmp[key] = val; 13 | return new Promise(function (resolve, reject) { 14 | let keys = Object.keys(tmp); 15 | async.map(keys, function (k, cb) { 16 | db.put(k, tmp[k], function (err) { 17 | if (err) { 18 | cb(err); 19 | } else { 20 | delete tmp[k]; 21 | cb(null, k); 22 | } 23 | }); 24 | }, function (err) { 25 | if (err) { 26 | reject(err); 27 | } else { 28 | resolve(1); 29 | } 30 | }); 31 | }); 32 | }, 33 | 34 | get (key) { 35 | return new Promise(function (resolve, reject) { 36 | db.get(key, function (err, result) { 37 | if (err) { 38 | reject(err); 39 | } else { 40 | resolve(result); 41 | } 42 | }); 43 | }); 44 | }, 45 | 46 | del (key) { 47 | return new Promise(function (resolve, reject) { 48 | db.del(key, function (err) { 49 | if (err) { 50 | reject(err); 51 | } else { 52 | delete tmp[key]; 53 | resolve(true); 54 | } 55 | }); 56 | }); 57 | }, 58 | 59 | find (options) { 60 | return new Promise((resolve, reject) => { 61 | options = Object.assign({keys: true, values: true, limit: 20, fillCache: true}, options); 62 | if (options.prefix) { 63 | options.start = options.prefix; 64 | options.end = options.prefix.substring(0, options.prefix.length - 1) + String.fromCharCode(options.prefix[options.prefix.length - 1].charCodeAt() + 1); 65 | } 66 | 67 | let results = []; 68 | db.createReadStream(options).on('data', function (data) { 69 | results.push(data); 70 | }).on('error', function (err) { 71 | console.log('leveldb find error', err); 72 | return reject([]); 73 | }).on('close', function () { 74 | }).on('end', function () { 75 | return resolve(results); 76 | }); 77 | }); 78 | } 79 | }; 80 | -------------------------------------------------------------------------------- /lib/services/MerchantService.js: -------------------------------------------------------------------------------- 1 | import superagent from 'superagent'; 2 | import ConfigStore from '../utils/ConfigStore'; 3 | import MerchantTask from '../tasks/MerchantTask'; 4 | import LevelDBService from '../services/LevelDBService'; 5 | import {SYSTEM_ERROR_CODE} from '../utils/constants'; 6 | 7 | export default { 8 | notify: function (params) { 9 | let config = ConfigStore.config; 10 | let isTimeout = params.body && params.body.code == SYSTEM_ERROR_CODE.DATASOURCE_OFFLINE; 11 | if (isTimeout) { 12 | MerchantTask.dequeue(params.request_id); 13 | } 14 | LevelDBService.put(`request-${params.request_id}-${new Date().getTime()}`, JSON.stringify(params)); 15 | if (config.merchant.callback_url) { 16 | superagent.post(config.merchant.callback_url).send(params).end((err, resp) => { 17 | if (err) { 18 | console.error(err); 19 | } 20 | console.log('callback已提交'); 21 | // MerchantTask.dequeue(params.request_id); 22 | }); 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /lib/services/TimeoutService.js: -------------------------------------------------------------------------------- 1 | import MerchantService from './MerchantService'; 2 | import {SYSTEM_ERROR_CODE} from '../utils/constants'; 3 | 4 | let timeoutCollection = {}; 5 | export default { 6 | /** 7 | * 添加超时通知任务 8 | * @param request_id 9 | * @param timeout 10 | */ 11 | add (request_id, timeout) { 12 | if (!timeoutCollection[request_id]) { 13 | timeoutCollection[request_id] = setTimeout(function () { 14 | MerchantService.notify({ 15 | request_id, 16 | body: { 17 | code: SYSTEM_ERROR_CODE.DATASOURCE_OFFLINE, 18 | message: `${timeout / 1000}秒内无响应,可能原因:\n1. 数据源离线了\n2. 涉及个人隐私数据交易未得到本人授权` 19 | } 20 | }); 21 | }, timeout); 22 | } 23 | }, 24 | 25 | /** 26 | * 取消超时通知任务 27 | * @param request_id 28 | */ 29 | remove (request_id) { 30 | if (timeoutCollection[request_id]) { 31 | clearTimeout(timeoutCollection[request_id]); 32 | delete timeoutCollection[request_id]; 33 | } 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /lib/utils/ConfigStore.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import {PrivateKey} from 'gxbjs'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | 6 | export default { 7 | init () { 8 | let self = this; 9 | return new Promise((resolve, reject) => { 10 | try { 11 | self.config = {}; 12 | let configPath = path.resolve(process.cwd(), './config/config.json'); 13 | if (!fs.existsSync(configPath)) { 14 | console.log('不存在'); 15 | configPath = path.resolve(process.cwd(), './dist/config/config.json'); 16 | } 17 | self.config = JSON.parse(fs.readFileSync(configPath, 'utf-8')); 18 | resolve(self.config); 19 | } catch (ex) { 20 | reject(ex); 21 | } 22 | }); 23 | }, 24 | get_merchant_private_key () { 25 | let config = this.config; 26 | return config.merchant && config.merchant.private_key ? PrivateKey.fromWif(config.merchant.private_key) : ''; 27 | }, 28 | get_datasource_private_key () { 29 | let config = this.config; 30 | return config.datasource && config.datasource.private_key ? PrivateKey.fromWif(config.datasource.private_key) : ''; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /lib/utils/constants.js: -------------------------------------------------------------------------------- 1 | // 数据交易状态 2 | const TRANSACTION_STATUS_MAP = { 3 | '0': 'INITIAL', 4 | INITIAL: 'INITIAL', 5 | '1': 'CONFIRMED', 6 | CONFIRMED: 'CONFIRMED', 7 | '99': 'PRIVACY_REJECTED', 8 | PRIVACY_REJECTED: 'PRIVACY_REJECTED' 9 | }; 10 | // 数据源状态 11 | const DATA_SOURCE_STATUS_MAP = { 12 | '0': 'INITIAL', 13 | INITIAL: 'INITIAL', 14 | '1': 'UPLOADED', 15 | UPLOADED: 'UPLOADED', 16 | '2': 'PAYED', 17 | PAYED: 'PAYED', 18 | '3': 'NO_DATA', 19 | NO_DATA: 'NO_DATA', 20 | '99': 'VALIDATE_FAIL', 21 | VALIDATE_FAIL: 'VALIDATE_FAIL' 22 | }; 23 | // 支付状态 24 | const PAY_STATUS = { 25 | NOT_PAYED: 'NOT_PAYED', 26 | PAYING: 'PAYING', 27 | PAYED: 'PAYED', 28 | PAY_FAILED: 'PAY_FAILED' 29 | }; 30 | 31 | // 下载状态 32 | const DOWNLOAD_STATUS = { 33 | NOT_DOWNLOADED: 'NOT_DOWNLOADED', 34 | DOWNLOADING: 'DOWNLOADING', 35 | DOWNLOADED: 'DOWNLOADED', 36 | DOWNLOAD_FAILED: 'DOWNLOAD_FAILED' 37 | }; 38 | 39 | // 系统错误 40 | const SYSTEM_ERROR_CODE = { 41 | NOT_FOUND: 'NOT_FOUND', 42 | INVALID_PARAMS: 'INVALID_PARAMS', 43 | DATASOURCE_OFFLINE: 'DATASOURCE_OFFLINE', 44 | FORBIDDEN: 'FORBIDDEN', 45 | BALANCE_NOT_ENOUGH: 'BALANCE_NOT_ENOUGH', 46 | PRIVACY_REJECTED: 'PRIVACY_REJECTED', 47 | UNKNOWN_ERROR: 'UNKNOWN_ERROR' 48 | }; 49 | 50 | export { 51 | TRANSACTION_STATUS_MAP, 52 | DATA_SOURCE_STATUS_MAP, 53 | PAY_STATUS, 54 | DOWNLOAD_STATUS, 55 | SYSTEM_ERROR_CODE 56 | }; 57 | -------------------------------------------------------------------------------- /lib/utils/validator.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 3 | validate (params, schema) { 4 | let self = this; 5 | if (typeof params != 'object') { 6 | throw new Error(`params ${params} should be an object`); 7 | } 8 | 9 | if (typeof schema != 'object') { 10 | throw new Error(`schema ${schema} should be an object`); 11 | } 12 | 13 | let result = {}; 14 | 15 | for (var key in schema) { 16 | let type = schema[key].type; 17 | let value = params[key]; 18 | let defaultValue = schema[key].defaultsTo; 19 | 20 | // assign a default value if the value is not assigned 21 | if (typeof value == 'undefined' && typeof defaultValue != 'undefined') { 22 | value = defaultValue; 23 | result[key] = value; 24 | } 25 | 26 | // throw an error if value is required but not assigned 27 | if (typeof value == 'undefined' && schema[key].required) { 28 | throw new Error(`${key} in ${JSON.stringify(params, null, '\t')} is required`); 29 | } 30 | 31 | switch (type) { 32 | case 'integer': 33 | if (!/^\d+$/.test(value)) { 34 | throw new Error(`${key} should be a type of ${type}, but get a value of ${value}`); 35 | } else { 36 | result[key] = value; 37 | } 38 | break; 39 | case 'string': 40 | case 'boolean': 41 | case 'number': 42 | if (typeof value != type) { 43 | throw new Error(`In case of schema definition:${JSON.stringify(schema, null, '\t')},${key} in ${JSON.stringify(params, null, '\t')} should be a type of ${type}, but get a value of ${value} which indicated as type of ${typeof value}`); 44 | } else { 45 | result[key] = value; 46 | } 47 | break; 48 | case 'json': 49 | if (!(value instanceof Object)) { 50 | throw new Error(`${key} should be json, but get a value of ${value} which indicated as instance of ${value.constructor.name}`); 51 | } 52 | result[key] = self.validate(value, schema.fields || {}); 53 | break; 54 | case 'array': 55 | if (!(value instanceof Array)) { 56 | throw new Error(`${key} should be an instance of array, but get a value of ${value} which indicated as instance of ${value.constructor.name}`); 57 | } else if (!schema[key].columns) { 58 | throw new Error(`columns definition should be assigned for ${key} since schema ${schema}[${key}] is type of array`); 59 | } else if (typeof schema[key].columns != 'object') { 60 | throw new Error(`${key} should be an instance of array, but get a value of ${value} which indicated as instance of ${value.constructor.name}`); 61 | } else { 62 | result[key] = []; 63 | value = value.forEach(function (item) { 64 | result[key].push(self.validate(item, schema[key].columns)); 65 | }); 66 | } 67 | break; 68 | case 'raw': 69 | if (!(value instanceof Object)) { 70 | throw new Error(`${key} should be json, but get a value of ${value} which indicated as instance of ${value.constructor.name}`); 71 | } 72 | result[key] = value; 73 | break; 74 | default: 75 | console.warn(`unknown type ${type} found in ${JSON.stringify(params, null, '\t')}, which is not supported at this time, will be ignored`); 76 | 77 | } 78 | } 79 | return result; 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gxb-box", 3 | "version": "1.0.1", 4 | "description": "gxchain box", 5 | "main": "index.js", 6 | "scripts": { 7 | "preinstall": "npm install pm2 -g", 8 | "start":"npm run gui:dev", 9 | "dev":"npm run gui:dev", 10 | "gui:dev": "npm run build && NODE_ENV=development nodemon server/index.js --exec babel-node --watch server", 11 | "box:dev": "nodemon lib/index.js --exec babel-node", 12 | "clear": "rm -rf dist/box", 13 | "prebuild": "npm run clear", 14 | "build": "webpack --progress --hide-modules --config build/webpack.box.config.js", 15 | "build:gui": "webpack --progress --hide-modules --config build/webpack.gui-server.config.js && prod=1 node build/build.js", 16 | "box": "pm2 start ./box/gxb-box.js --name gxb-box", 17 | "gui": "NODE_ENV=production pm2 start ./gui-server/index.js --name gxb-gui" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/gxchain/gxb-box.git" 22 | }, 23 | "engines": { 24 | "node": ">= 6.0.0" 25 | }, 26 | "keywords": [ 27 | "gxchain", 28 | "box" 29 | ], 30 | "author": "", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/gxchain/gxb-box/issues" 34 | }, 35 | "homepage": "https://github.com/gxchain/gxb-box#readme", 36 | "dependencies": { 37 | "archiver": "^2.0.0", 38 | "async": "^2.5.0", 39 | "axios": "^0.15.3", 40 | "bluebird": "^3.5.0", 41 | "body-parser": "~1.13.2", 42 | "colors": "^1.1.2", 43 | "connect-history-api-fallback": "^1.3.0", 44 | "debug": "~2.2.0", 45 | "express": "~4.13.1", 46 | "figlet": "^1.2.0", 47 | "gxbjs": "^1.2.59", 48 | "gxbjs-ws": "^1.1.17", 49 | "immutable": "^3.8.1", 50 | "ipfs-api": "^14.0.4", 51 | "iview": "^2.0.0-rc.13", 52 | "jdenticon": "^1.7.2", 53 | "js-sha256": "^0.7.1", 54 | "level": "^1.7.0", 55 | "morgan": "~1.6.1", 56 | "pm2": "^2.7.1", 57 | "qs": "^6.5.1", 58 | "require-ensure": "^1.0.2", 59 | "socket.io": "^2.0.4", 60 | "socket.io-client": "^2.0.4", 61 | "superagent": "^3.5.2", 62 | "vue": "^2.4.4", 63 | "vue-router": "^2.7.0", 64 | "vue-timeago": "^3.3.6", 65 | "vue-websocket": "^0.2.2", 66 | "vuex": "^2.2.1" 67 | }, 68 | "devDependencies": { 69 | "@types/express": "^4.0.39", 70 | "autoprefixer": "^7.1.2", 71 | "babel-cli": "^6.24.1", 72 | "babel-core": "^6.22.1", 73 | "babel-eslint": "^7.1.1", 74 | "babel-loader": "^7.1.1", 75 | "babel-plugin-transform-runtime": "^6.22.0", 76 | "babel-preset-env": "^1.3.2", 77 | "babel-preset-stage-2": "^6.22.0", 78 | "babel-register": "^6.22.0", 79 | "chalk": "^2.0.1", 80 | "copy-webpack-plugin": "^4.0.1", 81 | "css-loader": "^0.28.0", 82 | "cssnano": "^3.10.0", 83 | "eslint": "^3.19.0", 84 | "eslint-config-standard": "^6.2.1", 85 | "eslint-friendly-formatter": "^3.0.0", 86 | "eslint-loader": "^1.7.1", 87 | "eslint-plugin-html": "^3.0.0", 88 | "eslint-plugin-promise": "^3.4.0", 89 | "eslint-plugin-standard": "^2.0.1", 90 | "eslint-plugin-vue": "^2.1.0", 91 | "eventsource-polyfill": "^0.9.6", 92 | "extract-text-webpack-plugin": "^2.0.0", 93 | "file-loader": "^0.11.1", 94 | "friendly-errors-webpack-plugin": "^1.1.3", 95 | "html-webpack-plugin": "^2.28.0", 96 | "nodemon": "^1.11.0", 97 | "opn": "^5.1.0", 98 | "optimize-css-assets-webpack-plugin": "^2.0.0", 99 | "ora": "^1.2.0", 100 | "rimraf": "^2.6.0", 101 | "semver": "^5.3.0", 102 | "shelljs": "^0.7.6", 103 | "url-loader": "^0.5.8", 104 | "vue-loader": "^13.0.5", 105 | "vue-style-loader": "^3.0.3", 106 | "vue-template-compiler": "^2.4.4", 107 | "webpack": "^2.6.1", 108 | "webpack-bundle-analyzer": "^2.2.1", 109 | "webpack-dev-middleware": "^1.10.0", 110 | "webpack-hot-middleware": "^2.18.0", 111 | "webpack-merge": "^4.1.0" 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /script/start.cmd: -------------------------------------------------------------------------------- 1 | npm install -production 2 | pm2 stop gxb-box 3 | pm2 start ./box/gxb-box.js --name gxb-box 4 | -------------------------------------------------------------------------------- /script/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | npm install -production 3 | pm2 stop gxb-box 4 | pm2 start ./box/gxb-box.js --name gxb-box 5 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import io from 'socket.io'; 3 | import logger from 'morgan'; 4 | import bodyParser from 'body-parser'; 5 | import http from 'http'; 6 | import path from 'path'; 7 | import fs from 'fs'; 8 | import ConnectService from './services/ConnectService'; 9 | import gui_config from '../config'; 10 | 11 | require('debug')('gxb-box:server'); 12 | let app = express(); 13 | let devMiddleware = null; 14 | let hotMiddleware = null; 15 | let autoOpenBrowser = gui_config.dev.autoOpenBrowser; 16 | 17 | app.use(require('connect-history-api-fallback')({ 18 | index: '/', 19 | rewrites: [ 20 | { 21 | from: '/^abc$/', 22 | to: '/' 23 | }, 24 | { 25 | from: '/api/*', 26 | to: function (options) { 27 | return options.parsedUrl.href; 28 | } 29 | } 30 | ] 31 | })); 32 | 33 | if (app.get('env') === 'development') { 34 | let webpackConfig = require('../build/webpack.dev.conf'); 35 | let compiler = require('webpack')(webpackConfig); 36 | 37 | devMiddleware = require('webpack-dev-middleware')(compiler, { 38 | publicPath: webpackConfig.output.publicPath, 39 | quiet: true 40 | }); 41 | hotMiddleware = require('webpack-hot-middleware')(compiler, { 42 | log: console.log, 43 | heartbeat: 2000 44 | }); 45 | compiler.plugin('compilation', function (compilation) { 46 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 47 | hotMiddleware.publish({action: 'reload'}); 48 | cb(); 49 | }); 50 | }); 51 | app.use(logger('dev')); 52 | app.use(devMiddleware); 53 | app.use(hotMiddleware); 54 | 55 | let staticPath = path.posix.join(gui_config.dev.assetsPublicPath, gui_config.dev.assetsSubDirectory); 56 | app.use(staticPath, express.static('./static')); 57 | } else { 58 | app.use(logger('combined')); 59 | app.use(express.static('./gui')); 60 | } 61 | 62 | app.use(bodyParser.json({limit: '20mb'})); 63 | app.use(bodyParser.urlencoded({ 64 | extended: false, 65 | limit: '20mb' 66 | })); 67 | 68 | const connectedCheck = function (req, res, next) { 69 | let witnesses = req.query.env === 'production' ? gui_config.build.witnesses : gui_config.dev.witnesses; 70 | ConnectService.connect(witnesses, false, function (connected) { 71 | if (connected) { 72 | next(); 73 | } else { 74 | res.status(500).send({message: '正在初始化数据,请稍后再试'}); 75 | } 76 | }); 77 | }; 78 | 79 | app.use('/api', connectedCheck, require('./routes/api')); 80 | 81 | app.use(function (err, req, res, next) { 82 | res.status(err.status || 500); 83 | res.send({ 84 | message: err.message, 85 | error: {} 86 | }); 87 | }); 88 | 89 | /** 90 | * 启动web服务 91 | */ 92 | let serverStarted = false; 93 | let port = parseInt(process.env.port || '3031'); 94 | let startServer = function () { 95 | if (serverStarted) { 96 | return; 97 | } 98 | serverStarted = true; 99 | app.set('port', port); 100 | let server = http.createServer(app); 101 | let websocket = io(server); 102 | websocket.on('connection', function (socket) { 103 | socket.on('message', function (type, data) { 104 | websocket.emit('message', type, data); 105 | }); 106 | socket.on('system', function (data) { 107 | websocket.emit('message', data); 108 | }); 109 | }); 110 | server.listen(port); 111 | server.on('error', onError); 112 | server.on('listening', () => { 113 | devMiddleware && devMiddleware.waitUntilValid(() => { 114 | let uri = `http://localhost:${port}`; 115 | let opn = require('opn'); 116 | if (app.get('env') === 'development' && autoOpenBrowser) { 117 | opn(uri); 118 | } 119 | }); 120 | ConnectService.get_ip_address().then((local_ip) => { 121 | console.log('公信宝数据盒子配置系统已启动'); 122 | console.log('> 请使用浏览器访问:' + 'http://' + local_ip + ':' + port); 123 | }); 124 | }); 125 | }; 126 | 127 | const mkdir = function (dirpath, dirname) { 128 | if (typeof dirname === 'undefined') { 129 | if (fs.existsSync(dirpath)) { 130 | return; 131 | } else { 132 | mkdir(dirpath, path.dirname(dirpath)); 133 | } 134 | } else { 135 | if (dirname !== path.dirname(dirpath)) { 136 | mkdir(dirpath); 137 | return; 138 | } 139 | if (fs.existsSync(dirname)) { 140 | fs.mkdirSync(dirpath); 141 | } else { 142 | mkdir(dirname, path.dirname(dirname)); 143 | fs.mkdirSync(dirpath); 144 | } 145 | } 146 | }; 147 | 148 | /** 149 | * 初始化连接 150 | */ 151 | let initConnection = function () { 152 | console.log('检查配置文件...'); 153 | // 检查配置文件 154 | mkdir(path.resolve(process.cwd(), './dist/config')); 155 | let config_path = path.resolve(process.cwd(), './dist/config/config.json'); 156 | fs.exists(config_path, function (exists) { 157 | if (exists) { 158 | startServer(); 159 | } else { 160 | try { 161 | let _config = {}; 162 | fs.writeFileSync(config_path, JSON.stringify(_config)); 163 | startServer(); 164 | } catch (ex) { 165 | console.error('获取配置信息失败,请检查:\n 请确认配置文件以及读写权限 \n', ex); 166 | } 167 | } 168 | }); 169 | }; 170 | 171 | /** 172 | * 首次连接 173 | */ 174 | initConnection(); 175 | 176 | /** 177 | * Event listener for HTTP server "error" event. 178 | */ 179 | function onError (error) { 180 | if (error.syscall !== 'listen') { 181 | throw error; 182 | } 183 | 184 | let bind = typeof port === 'string' 185 | ? 'Pipe ' + port 186 | : 'Port ' + port; 187 | 188 | // handle specific listen errors with friendly messages 189 | switch (error.code) { 190 | case 'EACCES': 191 | console.error(bind + ' requires elevated privileges'); 192 | process.exit(1); 193 | break; 194 | case 'EADDRINUSE': 195 | console.error(bind + ' is already in use'); 196 | process.exit(1); 197 | break; 198 | default: 199 | throw error; 200 | } 201 | } 202 | 203 | process.stdin.resume(); 204 | 205 | function exitHandler (reason, err) { 206 | if (err) console.log(err.stack); 207 | console.log('程序退出:', reason); 208 | process.exit(); 209 | } 210 | 211 | // do something when app is closing 212 | process.on('exit', exitHandler.bind(null, 'exit')); 213 | 214 | // catches ctrl+c event 215 | process.on('SIGINT', exitHandler.bind(null, 'SIGINT')); 216 | 217 | // catches uncaught exceptions 218 | process.on('uncaughtException', exitHandler.bind(null, 'uncaughtException')); 219 | -------------------------------------------------------------------------------- /server/routes/api.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import BoxService from '../services/BoxService'; 3 | import AccountService from '../services/AccountService'; 4 | import DataService from '../services/DataService'; 5 | import ConfigStore from '../services/ConfigStore'; 6 | import {get_box_prod_zip} from '../services/ZipArchive'; 7 | import ConnectService from '../services/ConnectService'; 8 | import fs from 'fs'; 9 | 10 | let router = express.Router(); 11 | 12 | /** 13 | * 读取配置文件 14 | */ 15 | 16 | router.get('/fetch_config', function (req, res) { 17 | ConfigStore.init().then((config) => { 18 | res.send(config); 19 | }).catch((err) => { 20 | console.error(err); 21 | res.status(400).send(err); 22 | }); 23 | }); 24 | 25 | /** 26 | * 写入配置文件 27 | */ 28 | 29 | router.post('/save_config', function (req, res) { 30 | ConfigStore.set(req.body.config).then((result) => { 31 | res.send(result); 32 | }).catch((err) => { 33 | console.error(err); 34 | res.status(400).send(err); 35 | }); 36 | }); 37 | 38 | /** 39 | * 切换配置环境 40 | */ 41 | 42 | router.post('/change_config_env', function (req, res) { 43 | ConfigStore.change_config_env(req.query.env, req.body.config).then((result) => { 44 | res.send(result); 45 | }).catch((err) => { 46 | console.error(err); 47 | res.status(400).send(err); 48 | }); 49 | }); 50 | 51 | /** 52 | * 获取本机IP 53 | */ 54 | 55 | router.get('/get_ip_address', function (req, res) { 56 | ConnectService.get_ip_address().then((box_ip) => { 57 | res.send(box_ip); 58 | }).catch((err) => { 59 | console.error(err); 60 | res.status(400).send(err); 61 | }); 62 | }); 63 | 64 | /** 65 | * 账户信息 66 | */ 67 | 68 | router.get('/fetch_account/:account_id_or_name', function (req, res) { 69 | AccountService.fetch_account(req.params.account_id_or_name).then((account) => { 70 | res.send(account); 71 | }).catch(err => { 72 | console.error(err); 73 | res.status(400).send(err); 74 | }); 75 | }); 76 | 77 | /** 78 | * 创建账号 79 | */ 80 | 81 | router.post('/create_account', function (req, res) { 82 | AccountService.create_account(req.query.env, req.body.type, req.body.name, req.protocol).then((account) => { 83 | res.send(account); 84 | }).catch((err) => { 85 | console.error(err); 86 | res.status(400).send(err); 87 | }); 88 | }); 89 | 90 | /** 91 | * 导入账号 92 | */ 93 | 94 | router.post('/import_account', function (req, res) { 95 | AccountService.import_account(req.body.type, req.body.private_key).then((account) => { 96 | res.send(account); 97 | }).catch((err) => { 98 | console.error(err); 99 | res.status(400).send(err); 100 | }); 101 | }); 102 | 103 | /** 104 | * 申请认证商户 105 | */ 106 | 107 | router.post('/apply_merchant', function (req, res) { 108 | AccountService.apply_merchant(req.query.env, req.body.apply_info, req.body.account_name, req.body.account_type, req.protocol).then((result) => { 109 | res.send(result); 110 | }).catch((err) => { 111 | console.error(err); 112 | res.status(400).send(err); 113 | }); 114 | }); 115 | 116 | /** 117 | * 申请认证数据源 118 | */ 119 | 120 | router.post('/apply_datasource', function (req, res) { 121 | AccountService.apply_datasource(req.query.env, req.body.apply_info, req.body.account_name, req.body.account_type, req.protocol).then((result) => { 122 | res.send(result); 123 | }).catch((err) => { 124 | console.error(err); 125 | res.status(400).send(err); 126 | }); 127 | }); 128 | 129 | /** 130 | * 查询认证状态 131 | */ 132 | 133 | router.get('/is_applying/:account_name', function (req, res) { 134 | AccountService.is_applying(req.query.env, req.params.account_name, req.protocol).then((result) => { 135 | res.send(result); 136 | }).catch((err) => { 137 | console.error(err); 138 | res.status(400).send(err); 139 | }); 140 | }); 141 | 142 | /** 143 | * 获取认证商户信息 144 | */ 145 | 146 | router.get('/fetch_merchant/:account_name/:account_type', function (req, res) { 147 | AccountService.fetch_merchant(req.query.env, req.params.account_name, req.params.account_type, req.protocol).then((result) => { 148 | res.send(result); 149 | }).catch((err) => { 150 | console.error(err); 151 | res.status(400).send(err); 152 | }); 153 | }); 154 | 155 | /** 156 | * 获取数据市场二级栏目 157 | */ 158 | 159 | router.get('/fetch_data_market_categories/:data_market_type', function (req, res) { 160 | DataService.fetch_data_market_categories(req.params.data_market_type).then((result) => { 161 | res.send(result); 162 | }).catch((err) => { 163 | console.error(err); 164 | res.status(400).send(err); 165 | }); 166 | }); 167 | 168 | /** 169 | * 获取栏目信息 170 | */ 171 | 172 | router.get('/fetch_data_market_categories_info/:category_id', function (req, res) { 173 | DataService.fetch_data_market_categories_info(req.params.category_id).then((result) => { 174 | res.send(result); 175 | }).catch((err) => { 176 | console.error(err); 177 | res.status(400).send(err); 178 | }); 179 | }); 180 | 181 | /** 182 | * 获取自由市场产品列表 183 | */ 184 | 185 | router.get('/fetch_free_data_products/:category_id/:page/:pageSize', function (req, res) { 186 | DataService.fetch_free_data_products(req.params.category_id, req.params.page, req.params.pageSize, req.params.keywords || '').then((result) => { 187 | res.send(result); 188 | }).catch((err) => { 189 | console.error(err); 190 | res.status(400).send(err); 191 | }); 192 | }); 193 | 194 | /** 195 | * 获取自由市场产品详情 196 | */ 197 | 198 | router.get('/fetch_free_data_product_details/:product_id', function (req, res) { 199 | DataService.fetch_free_data_product_details(req.params.product_id).then((result) => { 200 | res.send(result); 201 | }).catch((err) => { 202 | console.error(err); 203 | res.status(400).send(err); 204 | }); 205 | }); 206 | 207 | /** 208 | * 获取联盟市场联盟列表 209 | */ 210 | 211 | router.get('/fetch_league_list/:category_id/:page/:pageSize', function (req, res) { 212 | DataService.fetch_league_list(req.params.category_id, req.params.page, req.params.pageSize, req.params.keywords || '').then((result) => { 213 | res.send(result); 214 | }).catch((err) => { 215 | console.error(err); 216 | res.status(400).send(err); 217 | }); 218 | }); 219 | 220 | /** 221 | * 获取联盟信息 222 | */ 223 | 224 | router.get('/fetch_league_info/:league_id', function (req, res) { 225 | DataService.fetch_league_info(req.params.league_id).then((result) => { 226 | res.send(result); 227 | }).catch((err) => { 228 | console.error(err); 229 | res.status(400).send(err); 230 | }); 231 | }); 232 | 233 | /** 234 | * 获取联盟成员列表 235 | */ 236 | 237 | router.get('/fetch_league_members/:league_id', function (req, res) { 238 | AccountService.fetch_league_members(req.query.env, req.params.league_id, req.protocol).then((members) => { 239 | res.send(members); 240 | }).catch(err => { 241 | console.error(err); 242 | res.status(400).send(err); 243 | }); 244 | }); 245 | 246 | /** 247 | * 获取联盟产品列表 248 | */ 249 | 250 | router.get('/fetch_league_data_products/:data_product_ids', function (req, res) { 251 | DataService.fetch_league_data_products(req.params.data_product_ids).then((result) => { 252 | res.send(result); 253 | }).catch((err) => { 254 | console.error(err); 255 | res.status(400).send(err); 256 | }); 257 | }); 258 | 259 | /** 260 | * 数据盒子服务 - 启动 261 | */ 262 | 263 | router.get('/box_start', function (req, res) { 264 | BoxService.box_start().then((pm2) => { 265 | res.send(pm2); 266 | }).catch((err) => { 267 | console.error(err); 268 | res.status(400).send(err); 269 | }); 270 | }); 271 | 272 | /** 273 | * 数据盒子服务 - 停止 274 | */ 275 | 276 | router.get('/box_stop', function (req, res) { 277 | BoxService.box_stop().then((pm2) => { 278 | res.send(pm2); 279 | }).catch((err) => { 280 | console.error(err); 281 | res.status(400).send(err); 282 | }); 283 | }); 284 | 285 | /** 286 | * 数据盒子服务 - 删除 287 | */ 288 | 289 | router.get('/box_delete', function (req, res) { 290 | BoxService.box_delete().then((pm2) => { 291 | res.send(pm2); 292 | }).catch((err) => { 293 | console.error(err); 294 | res.status(400).send(err); 295 | }); 296 | }); 297 | 298 | /** 299 | * 数据盒子服务 - 重启 300 | */ 301 | 302 | router.get('/box_restart', function (req, res) { 303 | BoxService.box_restart().then((pm2) => { 304 | res.send(pm2); 305 | }).catch((err) => { 306 | console.error(err); 307 | res.status(400).send(err); 308 | }); 309 | }); 310 | 311 | /** 312 | * 数据盒子服务 - 查询 313 | */ 314 | 315 | router.get('/fetch_box', function (req, res) { 316 | BoxService.fetch_box().then((pm2) => { 317 | res.send(pm2); 318 | }).catch((err) => { 319 | console.error(err); 320 | res.status(400).send(err); 321 | }); 322 | }); 323 | 324 | /** 325 | * 生产环境 - 打包 326 | */ 327 | 328 | router.get('/get_box_prod_zip/:visual', function (req, res) { 329 | get_box_prod_zip(req.params.visual).then((zip) => { 330 | res.send(zip); 331 | }).catch((err) => { 332 | console.error(err); 333 | res.status(400).send(err); 334 | }); 335 | }); 336 | 337 | router.get('/download/:filename', function (req, res) { 338 | let filename = req.params.filename; 339 | let path = 'archive/' + req.params.filename; 340 | fs.exists(path, function (exists) { 341 | if (exists) { 342 | res.download(path, filename); 343 | } else { 344 | res.status(404).send({ 345 | status: 404, 346 | message: '请求错误' 347 | }); 348 | } 349 | }); 350 | }); 351 | 352 | module.exports = router; 353 | -------------------------------------------------------------------------------- /server/services/AccountService.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import {key, PrivateKey, Signature} from 'gxbjs'; 3 | import {Apis} from 'gxbjs-ws'; 4 | import Immutable from 'immutable'; 5 | import ConfigStore from './ConfigStore'; 6 | import dictionary from '../utils/dictionary_en'; 7 | import request from 'superagent'; 8 | import gui_config from '../../config'; 9 | 10 | /** 11 | * 获取账户信息 12 | */ 13 | const fetch_account = function (account_name) { 14 | return Apis.instance().db_api().exec('get_account_by_name', [account_name]); 15 | }; 16 | 17 | /** 18 | * 创建账号 19 | */ 20 | const create_account = function (env, account_type, new_account_name, protocol) { 21 | let faucetAddress = env === 'production' ? gui_config.build.faucet_url : gui_config.dev.faucet_url; 22 | let referrer = env === 'production' ? gui_config.build.referrer : gui_config.dev.referrer; 23 | let brainkey = key.suggest_brain_key(dictionary.en); 24 | let private_key = key.get_brainPrivateKey(brainkey); 25 | let owner_pubkey = private_key.toPublicKey().toPublicKeyString(); 26 | let active_pubkey = private_key.toPublicKey().toPublicKeyString(); 27 | let body = { 28 | 'account': { 29 | 'name': new_account_name, 30 | 'owner_key': owner_pubkey, 31 | 'active_key': active_pubkey, 32 | 'memo_key': active_pubkey, 33 | 'refcode': '', 34 | 'referrer': referrer 35 | } 36 | }; 37 | let account_config = { 38 | 'account_name': new_account_name, 39 | 'private_key': private_key.toWif() 40 | }; 41 | faucetAddress = protocol === 'https:' ? faucetAddress.replace(/http:\/\//, 'https://') : faucetAddress; 42 | return new Promise(function (resolve, reject) { 43 | request 44 | .post(faucetAddress + '/account/register') 45 | .send(body) 46 | .set('Accpet', 'application/json') 47 | .set('Content-Type', 'application/json') 48 | .end(function (err) { 49 | if (err) { 50 | reject(err); 51 | } else { 52 | resolve(account_config); 53 | } 54 | }); 55 | }); 56 | }; 57 | 58 | /** 59 | * 导入账号 60 | */ 61 | const import_account = function (account_type, private_key) { 62 | let public_key = PrivateKey.fromWif(private_key).toPublicKey().toPublicKeyString(); 63 | return new Promise(function (resolve, reject) { 64 | Apis.instance().db_api().exec('get_key_references', [[public_key]]) 65 | .then(function (vec_account_id) { 66 | let refs = Immutable.Set(); 67 | vec_account_id = vec_account_id[0]; 68 | refs = refs.withMutations(function (r) { 69 | for (let i = 0; i < vec_account_id.length; ++i) { 70 | r.add(vec_account_id[i]); 71 | } 72 | }); 73 | return Apis.instance().db_api().exec('get_objects', [refs]); 74 | }) 75 | .then((account) => { 76 | let account_config = { 77 | 'account_name': account[0].name, 78 | 'private_key': private_key 79 | }; 80 | resolve(account_config); 81 | }) 82 | .catch((err) => { 83 | reject(err); 84 | }); 85 | }); 86 | }; 87 | 88 | /** 89 | * 获取序列 90 | */ 91 | const sortJSON = function (json) { 92 | let keys = Object.keys(json); 93 | keys.sort(); 94 | let result = {}; 95 | keys.forEach(function (k) { 96 | result[k] = json[k]; 97 | }); 98 | return result; 99 | }; 100 | 101 | /** 102 | * 获取签名 103 | */ 104 | const getSign = function (body = '', type) { 105 | return new Promise(function (resolve, reject) { 106 | try { 107 | let private_key; 108 | if (type === 'merchant') { 109 | private_key = ConfigStore.get_merchant_private_key(); 110 | } else { 111 | private_key = ConfigStore.get_datasource_private_key(); 112 | } 113 | let signature = Signature.sign(body, private_key).toHex(); 114 | resolve(signature); 115 | } catch (ex) { 116 | reject(ex); 117 | } 118 | }); 119 | }; 120 | 121 | /** 122 | * 获取商户信息 123 | */ 124 | const fetch_merchant = function (env, account_name, account_type, protocol) { 125 | let body = {}; 126 | let faucetAddress = env === 'production' ? gui_config.build.faucet_url : gui_config.dev.faucet_url; 127 | faucetAddress = protocol === 'https:' ? faucetAddress.replace(/http:\/\//, 'https://') : faucetAddress; 128 | return new Promise(function (resolve, reject) { 129 | fetch_account(account_name) 130 | .then((account) => { 131 | body.account_id = account.id; 132 | body = sortJSON(body); 133 | return getSign(JSON.stringify(body), account_type); 134 | }) 135 | .then(function (signature) { 136 | body.signature = signature; 137 | request 138 | .get(faucetAddress + '/merchant/info') 139 | .query(body) 140 | .set('Accpet', 'application/json') 141 | .set('Content-Type', 'application/json') 142 | .end(function (err, res) { 143 | if (err) { 144 | reject(err); 145 | } else { 146 | resolve(res.body); 147 | } 148 | }); 149 | }) 150 | .catch(err => reject(err)); 151 | }); 152 | }; 153 | 154 | /** 155 | * 申请认证商户 156 | */ 157 | const apply_merchant = function (env, body, account_name, account_type, protocol) { 158 | let faucetAddress = env === 'production' ? gui_config.build.faucet_url : gui_config.dev.faucet_url; 159 | faucetAddress = protocol === 'https:' ? faucetAddress.replace(/http:\/\//, 'https://') : faucetAddress; 160 | return new Promise(function (resolve, reject) { 161 | fetch_account(account_name) 162 | .then((account) => { 163 | body.account_id = account.id; 164 | body = sortJSON(body); 165 | return getSign(JSON.stringify(body), account_type); 166 | }) 167 | .then(function (signature) { 168 | body.signature = signature; 169 | request 170 | .post(faucetAddress + '/merchant/create') 171 | .send(body) 172 | .set('Accpet', 'application/json') 173 | .set('Content-Type', 'application/json') 174 | .end(function (err, res) { 175 | if (err) { 176 | reject(err); 177 | } else { 178 | resolve(res.body); 179 | } 180 | }); 181 | }) 182 | .catch(err => reject(err)); 183 | }); 184 | }; 185 | 186 | /** 187 | * 申请认证数据源 188 | */ 189 | const apply_datasource = function (env, body, account_name, account_type, protocol) { 190 | let faucetAddress = env === 'production' ? gui_config.build.faucet_url : gui_config.dev.faucet_url; 191 | faucetAddress = protocol === 'https:' ? faucetAddress.replace(/http:\/\//, 'https://') : faucetAddress; 192 | return new Promise(function (resolve, reject) { 193 | fetch_account(account_name) 194 | .then((account) => { 195 | body.account_id = account.id; 196 | body = sortJSON(body); 197 | return getSign(JSON.stringify(body), account_type); 198 | }) 199 | .then(function (signature) { 200 | body.signature = signature; 201 | request 202 | .post(faucetAddress + '/dataSource/create') 203 | .send(body) 204 | .set('Accpet', 'application/json') 205 | .set('Content-Type', 'application/json') 206 | .end(function (err, res) { 207 | if (err) { 208 | reject(err); 209 | } else { 210 | resolve(res.body); 211 | } 212 | }); 213 | }) 214 | .catch(err => reject(err)); 215 | }); 216 | }; 217 | 218 | /** 219 | * 获取申请状态 220 | */ 221 | const is_applying = function (env, account_name, protocol) { 222 | let faucetAddress = env === 'production' ? gui_config.build.faucet_url : gui_config.dev.faucet_url; 223 | faucetAddress = protocol === 'https:' ? faucetAddress.replace(/http:\/\//, 'https://') : faucetAddress; 224 | return new Promise(function (resolve, reject) { 225 | fetch_account(account_name) 226 | .then((account) => { 227 | request 228 | .get(faucetAddress + '/account/apply_status') 229 | .query({account_id: account.id}) 230 | .set('Accpet', 'application/json') 231 | .set('Content-Type', 'application/json') 232 | .end(function (err, res) { 233 | if (err) { 234 | reject(err); 235 | } else { 236 | resolve(res.body); 237 | } 238 | }); 239 | }) 240 | .catch(err => reject(err)); 241 | }); 242 | }; 243 | 244 | const fetch_league_members = function (env, league_id, protocol) { 245 | let faucetAddress = env === 'production' ? gui_config.build.faucet_url : gui_config.dev.faucet_url; 246 | faucetAddress = protocol === 'https:' ? faucetAddress.replace(/http:\/\//, 'https://') : faucetAddress; 247 | return new Promise(function (resolve, reject) { 248 | request 249 | .get(faucetAddress + '/leagueDataSource/memberInfo') 250 | .query({league_id: league_id}) 251 | .set('Accpet', 'application/json') 252 | .set('Content-Type', 'application/json') 253 | .end(function (err, res) { 254 | if (err) { 255 | reject(err); 256 | } else { 257 | resolve(res.body); 258 | } 259 | }); 260 | }); 261 | }; 262 | 263 | export default { 264 | create_account, 265 | import_account, 266 | fetch_account, 267 | fetch_merchant, 268 | fetch_league_members, 269 | apply_merchant, 270 | apply_datasource, 271 | is_applying 272 | }; 273 | -------------------------------------------------------------------------------- /server/services/BoxService.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import path from 'path'; 3 | import pm2 from 'pm2'; 4 | import io from 'socket.io-client'; 5 | 6 | let is_started = false; 7 | let port = parseInt(process.env.port || '3031'); 8 | let url = 'http://localhost:' + port; 9 | 10 | /** 11 | * 数据盒子服务 - 启动 12 | */ 13 | const box_start = function () { 14 | return new Promise(function (resolve, reject) { 15 | pm2.connect(function (err) { 16 | if (err) { 17 | reject(err); 18 | process.exit(2); 19 | } 20 | pm2.start({ 21 | name: 'gxb-box-pm2', 22 | script: path.join(process.cwd(), 'dist/box/gxb-box.js'), 23 | exec_mode: 'fork', 24 | max_memory_restart: '100M' 25 | }, function (err, apps) { 26 | if (err) { 27 | console.log(path.join(process.cwd(), 'box/gxb-box.js')); 28 | pm2.start({ 29 | name: 'gxb-box-pm2', 30 | script: path.join(process.cwd(), 'box/gxb-box.js'), 31 | exec_mode: 'fork', 32 | max_memory_restart: '100M' 33 | }, function (err, apps) { 34 | if (err) { 35 | reject(err); 36 | } else { 37 | resolve(apps); 38 | } 39 | }); 40 | } else { 41 | if (apps.length == 0) { 42 | pm2.start({ 43 | name: 'gxb-box-pm2', 44 | script: path.join(process.cwd(), 'box/gxb-box.js'), 45 | exec_mode: 'fork', 46 | max_memory_restart: '100M' 47 | }, function (err, apps) { 48 | if (err) { 49 | reject(err); 50 | } else { 51 | resolve(apps); 52 | } 53 | }); 54 | } else { 55 | resolve(apps); 56 | } 57 | } 58 | }); 59 | }); 60 | }); 61 | }; 62 | 63 | /** 64 | * 数据盒子服务 - 停止 65 | */ 66 | const box_stop = function () { 67 | return new Promise(function (resolve, reject) { 68 | pm2.connect(function (err) { 69 | if (err) { 70 | reject(err); 71 | process.exit(2); 72 | } 73 | pm2.stop('gxb-box-pm2', function (err) { 74 | if (err) { 75 | reject(err); 76 | } else { 77 | pm2.describe('gxb-box-pm2', function (err, processDescription) { 78 | if (err) { 79 | reject(err); 80 | } else { 81 | resolve(processDescription); 82 | } 83 | }); 84 | } 85 | }); 86 | }); 87 | }); 88 | }; 89 | 90 | /** 91 | * 数据盒子服务 - 删除 92 | */ 93 | const box_delete = function () { 94 | return new Promise(function (resolve, reject) { 95 | pm2.connect(function (err) { 96 | if (err) { 97 | reject(err); 98 | process.exit(2); 99 | } 100 | pm2.describe('gxb-box-pm2', function (err, processDescription) { 101 | if (err) { 102 | reject(err); 103 | } else { 104 | if (processDescription.length === 0) { 105 | resolve(); 106 | } else { 107 | pm2.delete('gxb-box-pm2', function (err) { 108 | if (err) { 109 | reject(err); 110 | } else { 111 | resolve(); 112 | } 113 | }); 114 | } 115 | } 116 | }); 117 | }); 118 | }); 119 | }; 120 | 121 | /** 122 | * 数据盒子服务 - 重启 123 | */ 124 | const box_restart = function () { 125 | return new Promise(function (resolve, reject) { 126 | pm2.connect(function (err) { 127 | if (err) { 128 | reject(err); 129 | process.exit(2); 130 | } 131 | pm2.restart('gxb-box-pm2', function (err) { 132 | if (err) { 133 | reject(err); 134 | } else { 135 | pm2.describe('gxb-box-pm2', function (err, processDescription) { 136 | if (err) { 137 | reject(err); 138 | } else { 139 | resolve(processDescription); 140 | } 141 | }); 142 | } 143 | }); 144 | }); 145 | }); 146 | }; 147 | 148 | /** 149 | * 数据盒子服务 - 查询 150 | */ 151 | const fetch_box = function () { 152 | return new Promise(function (resolve, reject) { 153 | pm2.connect(function (err) { 154 | if (err) { 155 | reject(err); 156 | process.exit(2); 157 | } 158 | pm2.describe('gxb-box-pm2', function (err, processDescription) { 159 | if (err) { 160 | reject(err); 161 | } else { 162 | // 启动消息BUS,监听日志 163 | if ((!is_started) && (processDescription && processDescription.length > 0)) { 164 | let websocket = io.connect(url); 165 | 166 | setInterval(function () { 167 | pm2.describe('gxb-box-pm2', function (processDescription) { 168 | websocket.emit('system', processDescription); 169 | }); 170 | }, 1000); 171 | 172 | pm2.launchBus(function (err, bus) { 173 | if (err) return reject(err); 174 | is_started = true; 175 | bus.on('log:out', function (packet) { 176 | websocket.emit('message', 'out', packet.data); 177 | }); 178 | bus.on('log:err', function (packet) { 179 | websocket.emit('message', 'err', packet.data); 180 | }); 181 | return resolve(processDescription); 182 | }); 183 | } else { 184 | resolve(processDescription); 185 | } 186 | } 187 | }); 188 | }); 189 | }); 190 | }; 191 | 192 | export default { 193 | box_start, 194 | box_stop, 195 | box_delete, 196 | box_restart, 197 | fetch_box 198 | }; 199 | -------------------------------------------------------------------------------- /server/services/ConfigStore.js: -------------------------------------------------------------------------------- 1 | import Promise from 'bluebird'; 2 | import {PrivateKey} from 'gxbjs'; 3 | import BoxService from './BoxService'; 4 | import ConnectService from './ConnectService'; 5 | import fs from 'fs'; 6 | import path from 'path'; 7 | import gui_config from '../../config'; 8 | 9 | let config_path = path.resolve(process.cwd(), './dist/config/config.json'); 10 | 11 | export default { 12 | init () { 13 | let self = this; 14 | return new Promise((resolve, reject) => { 15 | try { 16 | self.config = JSON.parse(fs.readFileSync(config_path, 'utf-8')) || {}; 17 | resolve(self.config); 18 | } catch (ex) { 19 | reject(ex); 20 | } 21 | }); 22 | }, 23 | set (config) { 24 | let self = this; 25 | return new Promise((resolve, reject) => { 26 | try { 27 | fs.writeFileSync(config_path, JSON.stringify(config)); 28 | self.config = config; 29 | resolve({ 30 | 'message': '系统配置保存成功' 31 | }); 32 | } catch (ex) { 33 | reject(ex); 34 | } 35 | }); 36 | }, 37 | change_config_env (env, config) { 38 | let self = this; 39 | let witnesses = env === 'production' ? gui_config.build.witnesses : gui_config.dev.witnesses; 40 | return new Promise((resolve, reject) => { 41 | // 停止GXB-BOX-PM2 42 | BoxService.box_delete().then(() => { 43 | ConnectService.connect(witnesses, true, function () { 44 | try { 45 | let _config = JSON.parse(fs.readFileSync(config_path, 'utf-8')); 46 | fs.writeFileSync(config_path, JSON.stringify(config)); 47 | self.config = config; 48 | resolve({ 49 | 'message': '系统切换配置成功', 50 | 'data': { 51 | 'old_config': _config, 52 | 'new_config': config 53 | } 54 | }); 55 | } catch (ex) { 56 | reject(ex); 57 | } 58 | }); 59 | }).catch((ex) => { 60 | reject(ex); 61 | }); 62 | }); 63 | }, 64 | get_merchant_private_key () { 65 | let config = this.config; 66 | return config.merchant && config.merchant.private_key ? PrivateKey.fromWif(config.merchant.private_key) : ''; 67 | }, 68 | get_datasource_private_key () { 69 | let config = this.config; 70 | return config.datasource && config.datasource.private_key ? PrivateKey.fromWif(config.datasource.private_key) : ''; 71 | } 72 | }; 73 | -------------------------------------------------------------------------------- /server/services/ConnectService.js: -------------------------------------------------------------------------------- 1 | import {Apis, Manager} from 'gxbjs-ws'; 2 | import {ChainStore} from 'gxbjs'; 3 | import os from 'os'; 4 | 5 | let connected = false; 6 | let connectionManager = null; 7 | let _witnesses; 8 | 9 | /** 10 | * 连接witness 11 | * @param callback 12 | */ 13 | let connect = function (witnesses, reconnect, callback) { 14 | _witnesses = witnesses || []; 15 | if (reconnect) { 16 | connected = false; 17 | connectionManager.url = witnesses[0]; 18 | connectionManager.urls = witnesses; 19 | } 20 | if (connected) { 21 | return callback(connected); 22 | } 23 | if (!connectionManager) { 24 | connectionManager = new Manager({url: witnesses[0], urls: witnesses}); 25 | } 26 | connectionManager.connectWithFallback(true).then(() => { 27 | ChainStore.subscribed = false; 28 | ChainStore.subError = null; 29 | ChainStore.clearCache(); 30 | ChainStore.head_block_time_string = null; 31 | ChainStore.init().then(() => { 32 | callback && callback(connected); 33 | }).catch(ex => { 34 | console.error(ex); 35 | callback && callback(connected); 36 | }); 37 | }).catch((ex) => { 38 | console.error(ex); 39 | }); 40 | }; 41 | 42 | const get_ip_address = function () { 43 | return new Promise((resolve, reject) => { 44 | try { 45 | let interfaces = os.networkInterfaces(); 46 | for (let devName in interfaces) { 47 | if (interfaces.hasOwnProperty(devName)) { 48 | let iface = interfaces[devName]; 49 | for (let i = 0; i < iface.length; i++) { 50 | let alias = iface[i]; 51 | if (alias.family === 'IPv4' && alias.address !== '127.0.0.1' && !alias.internal) { 52 | return resolve(alias.address); 53 | } 54 | } 55 | } 56 | } 57 | } catch (ex) { 58 | reject(ex); 59 | } 60 | }); 61 | }; 62 | 63 | /** 64 | * websocket 状态处理 65 | * @param status 66 | */ 67 | Apis.setRpcConnectionStatusCallback(function (status) { 68 | let statusMap = { 69 | open: '开启', 70 | closed: '关闭', 71 | error: '错误', 72 | reconnect: '重新连接' 73 | }; 74 | console.log('witness当前状态:', statusMap[status] || status); 75 | if (!connected && status === 'open') { 76 | connected = true; 77 | } 78 | if (status === 'reconnect') { 79 | console.log('断开重连'); 80 | ChainStore.clearCache(); 81 | } else if (connected && (status === 'closed' || status === 'error')) { // 出错重连 82 | connected = false; 83 | console.log('重新连接其他witness'); 84 | connect(_witnesses, false, function () {}); 85 | } 86 | }); 87 | 88 | export default { 89 | connect, 90 | get_ip_address 91 | }; 92 | -------------------------------------------------------------------------------- /server/services/DataService.js: -------------------------------------------------------------------------------- 1 | import {Apis} from 'gxbjs-ws'; 2 | 3 | /** 4 | * 获取数据市场二级栏目 5 | */ 6 | const fetch_data_market_categories = function (data_market_type) { 7 | return Apis.instance().db_api().exec('list_data_market_categories', [data_market_type]).then(function (res) { 8 | let result = (res || []).filter(function (cate) { 9 | return cate.status === 1; 10 | }); 11 | return result; 12 | }); 13 | }; 14 | 15 | /** 16 | * 获取栏目信息 17 | */ 18 | const fetch_data_market_categories_info = function (category_id) { 19 | return Apis.instance().db_api().exec('get_data_market_categories', [[category_id]]).then(function (res) { 20 | let result = res && res.length > 0 ? res[0] : {}; 21 | return result; 22 | }); 23 | }; 24 | 25 | /** 26 | * 获取自由市场产品列表 27 | */ 28 | const fetch_free_data_products = function (category_id, page, pageSize, keywords) { 29 | return Apis.instance().db_api().exec('list_free_data_products', [category_id, page * pageSize, pageSize, '', keywords, false]).then(function (res) { 30 | let result = { 31 | list: res.data, 32 | total: res.filtered_total 33 | }; 34 | return result; 35 | }); 36 | }; 37 | 38 | /** 39 | * 获取自由市场产品详情 40 | */ 41 | const fetch_free_data_product_details = function (product_id) { 42 | return Apis.instance().db_api().exec('get_free_data_products', [[product_id]]).then(function (res) { 43 | let result = res && res.length > 0 ? res[0] : {}; 44 | let schema_contexts = []; 45 | result.schema_contexts.forEach(function (schema) { 46 | let schemaJSON = JSON.parse(schema.schema_context); 47 | schema_contexts.push({ 48 | version: schema.version, 49 | input: schemaJSON.input, 50 | output: schemaJSON.output, 51 | code: schemaJSON.code, 52 | privacy: schemaJSON.privacy 53 | }); 54 | }); 55 | let latestVersion = ''; 56 | if (schema_contexts.length > 0) { 57 | latestVersion = schema_contexts[schema_contexts.length - 1].version; 58 | } 59 | let data = { 60 | product_name: result.product_name, 61 | brief_desc: result.brief_desc, 62 | datasource: result.datasource, 63 | status: result.status, 64 | total: result.total, 65 | version: latestVersion, 66 | latestVersion: latestVersion, 67 | category_id: result.category_id, 68 | schema_contexts: schema_contexts, 69 | price: result.price, 70 | icon: result.icon 71 | }; 72 | return data; 73 | }); 74 | }; 75 | 76 | /** 77 | * 获取联盟市场联盟列表 78 | */ 79 | const fetch_league_list = function (category_id, page, pageSize, keywords) { 80 | return Apis.instance().db_api().exec('list_leagues', [category_id, page * pageSize, pageSize, '', keywords, false]).then(function (res) { 81 | let result = { 82 | list: res.data, 83 | total: res.filtered_total 84 | }; 85 | return result; 86 | }); 87 | }; 88 | 89 | /** 90 | * 获取联盟信息 91 | */ 92 | const fetch_league_info = function (league_id) { 93 | return Apis.instance().db_api().exec('get_leagues', [[league_id]]).then(function (res) { 94 | let result = res && res.length > 0 ? res[0] : {}; 95 | return result; 96 | }); 97 | }; 98 | 99 | /** 100 | * 获取联盟市场产品列表 101 | */ 102 | const fetch_league_data_products = function (data_product_ids) { 103 | return Apis.instance().db_api().exec('get_league_data_products', [JSON.parse(data_product_ids)]); 104 | }; 105 | 106 | export default { 107 | fetch_data_market_categories, 108 | fetch_data_market_categories_info, 109 | fetch_free_data_products, 110 | fetch_free_data_product_details, 111 | fetch_league_list, 112 | fetch_league_info, 113 | fetch_league_data_products 114 | }; 115 | -------------------------------------------------------------------------------- /server/services/ZipArchive.js: -------------------------------------------------------------------------------- 1 | import 'shelljs/global'; 2 | import Promise from 'bluebird'; 3 | import path from 'path'; 4 | import fs from 'fs'; 5 | import archiver from 'archiver'; 6 | 7 | /** 8 | * 检查archive目录是否创建,未创建则创建 9 | */ 10 | export const create_archive_folder = () => { 11 | return new Promise((resolve, reject) => { 12 | fs.exists(path.join(process.cwd(), './archive'), function (exists) { 13 | if (!exists) { 14 | fs.mkdir(path.join(process.cwd(), './archive'), function (err) { 15 | if (err) { 16 | reject(err); 17 | } else { 18 | resolve(); 19 | } 20 | }); 21 | } else { 22 | resolve(); 23 | } 24 | }); 25 | }); 26 | }; 27 | 28 | /** 29 | * 打包 30 | * @param visual 是否打包可视化模块 31 | */ 32 | export const get_box_prod_zip = () => { 33 | return new Promise((resolve, reject) => { 34 | create_archive_folder().then(() => { 35 | let archiveFileName = 'gxb-box-' + new Date().valueOf() + '.zip'; 36 | let archiveFilePath = path.join(process.cwd(), './archive/' + archiveFileName); 37 | let archive = archiver('zip'); 38 | let output = fs.createWriteStream(archiveFilePath); 39 | output.on('close', function () { 40 | let zip_info = { 41 | name: archiveFileName, 42 | size: archive.pointer() + ' total bytes', 43 | time: new Date().valueOf() 44 | }; 45 | resolve(zip_info); 46 | }); 47 | archive.on('error', function (err) { 48 | reject(err); 49 | }); 50 | archive.pipe(output); 51 | archive.file(path.join(process.cwd(), './script/start.sh'), { 52 | date: new Date(), 53 | name: 'start-box.sh' 54 | }); 55 | archive.file(path.join(process.cwd(), './script/start.cmd'), { 56 | date: new Date(), 57 | name: 'start-box.cmd' 58 | }); 59 | archive.directory(path.join(process.cwd(), './dist/box'), './box', {date: new Date()}); 60 | archive.directory(path.join(process.cwd(), './dist/config'), './config', {date: new Date()}); 61 | archive.file(path.join(process.cwd(), './package.json'), {date: new Date(), name: 'package.json'}); 62 | archive.finalize(); 63 | }).catch(reject); 64 | }); 65 | }; 66 | -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 8 | 35 | 46 | 60 | -------------------------------------------------------------------------------- /src/libs/china_regions/index.js: -------------------------------------------------------------------------------- 1 | import {province} from './province'; 2 | import {city} from './city'; 3 | import {area} from './area'; 4 | 5 | let areaList = []; 6 | 7 | for (let i = 0; i < province.length; i++) { 8 | areaList[i] = {}; 9 | areaList[i].value = province[i].id; 10 | areaList[i].label = province[i].name; 11 | areaList[i].children = []; 12 | 13 | for (let j = 0; j < city[areaList[i].value].length; j++) { 14 | areaList[i].children[j] = {}; 15 | areaList[i].children[j].value = city[areaList[i].value][j].id; 16 | areaList[i].children[j].label = city[areaList[i].value][j].name; 17 | areaList[i].children[j].children = []; 18 | for (let k = 0; k < area[areaList[i].children[j].value].length; k++) { 19 | areaList[i].children[j].children[k] = {}; 20 | areaList[i].children[j].children[k].value = area[areaList[i].children[j].value][k].id; 21 | areaList[i].children[j].children[k].label = area[areaList[i].children[j].value][k].name; 22 | } 23 | } 24 | } 25 | 26 | export {areaList}; 27 | -------------------------------------------------------------------------------- /src/libs/china_regions/province.js: -------------------------------------------------------------------------------- 1 | let province = [{ 2 | name: '北京市', 3 | id: '110000' 4 | }, { 5 | name: '天津市', 6 | id: '120000' 7 | }, { 8 | name: '河北省', 9 | id: '130000' 10 | }, { 11 | name: '山西省', 12 | id: '140000' 13 | }, { 14 | name: '内蒙古自治区', 15 | id: '150000' 16 | }, { 17 | name: '辽宁省', 18 | id: '210000' 19 | }, { 20 | name: '吉林省', 21 | id: '220000' 22 | }, { 23 | name: '黑龙江省', 24 | id: '230000' 25 | }, { 26 | name: '上海市', 27 | id: '310000' 28 | }, { 29 | name: '江苏省', 30 | id: '320000' 31 | }, { 32 | name: '浙江省', 33 | id: '330000' 34 | }, { 35 | name: '安徽省', 36 | id: '340000' 37 | }, { 38 | name: '福建省', 39 | id: '350000' 40 | }, { 41 | name: '江西省', 42 | id: '360000' 43 | }, { 44 | name: '山东省', 45 | id: '370000' 46 | }, { 47 | name: '河南省', 48 | id: '410000' 49 | }, { 50 | name: '湖北省', 51 | id: '420000' 52 | }, { 53 | name: '湖南省', 54 | id: '430000' 55 | }, { 56 | name: '广东省', 57 | id: '440000' 58 | }, { 59 | name: '广西壮族自治区', 60 | id: '450000' 61 | }, { 62 | name: '海南省', 63 | id: '460000' 64 | }, { 65 | name: '重庆市', 66 | id: '500000' 67 | }, { 68 | name: '四川省', 69 | id: '510000' 70 | }, { 71 | name: '贵州省', 72 | id: '520000' 73 | }, { 74 | name: '云南省', 75 | id: '530000' 76 | }, { 77 | name: '西藏自治区', 78 | id: '540000' 79 | }, { 80 | name: '陕西省', 81 | id: '610000' 82 | }, { 83 | name: '甘肃省', 84 | id: '620000' 85 | }, { 86 | name: '青海省', 87 | id: '630000' 88 | }, { 89 | name: '宁夏回族自治区', 90 | id: '640000' 91 | }, { 92 | name: '新疆维吾尔自治区', 93 | id: '650000' 94 | }]; 95 | 96 | export {province}; 97 | -------------------------------------------------------------------------------- /src/libs/handler.js: -------------------------------------------------------------------------------- 1 | let handler = { 2 | error: function (error) { 3 | let msg = '未知错误'; 4 | if (error.response) { 5 | console.error(error.response); 6 | 7 | if (error.response.data && error.response.data.message) { 8 | msg = error.response.data.message; 9 | } 10 | if (error.response.data && error.response.data.data && error.response.data.data.message) { 11 | msg = error.response.data.data.message; 12 | } 13 | // 认证接口报错信息 14 | if (error.response.data && error.response.data.response && error.response.data.response.text) { 15 | let err_msg = JSON.parse(error.response.data.response.text); 16 | if (err_msg.base) { 17 | msg = err_msg.base[0]; 18 | } 19 | } 20 | } else if (error.request) { 21 | console.error(error.request); 22 | msg = error.request; 23 | } else { 24 | console.error(error.message); 25 | msg = error.message; 26 | } 27 | return JSON.stringify(msg); 28 | } 29 | }; 30 | 31 | export default handler; 32 | -------------------------------------------------------------------------------- /src/libs/util.js: -------------------------------------------------------------------------------- 1 | let util = { 2 | isValidPhone: function (phone) { 3 | return /^1\d{10}$/.test(phone); 4 | }, 5 | 6 | /** 7 | *验证组织机构代码是否合法:组织机构代码为8位数字或者拉丁字母+“-”+1位校验码。 8 | *验证最后那位校验码是否与根据公式计算的结果相符。 9 | *编码规则请参看 10 | *http://wenku.baidu.com/view/d615800216fc700abb68fc35.html 11 | */ 12 | isValidOrgCode: function (orgCode) { 13 | var ret = false; 14 | var codeVal = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']; 15 | var intVal = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35]; 16 | var crcs = [3, 7, 9, 10, 5, 8, 4, 2]; 17 | if (orgCode && orgCode.length === 10) { 18 | var sum = 0; 19 | for (var i = 0; i < 8; i++) { 20 | var codeI = orgCode.substring(i, i + 1); 21 | var valI = -1; 22 | for (var j = 0; j < codeVal.length; j++) { 23 | if (codeI === codeVal[j]) { 24 | valI = intVal[j]; 25 | break; 26 | } 27 | } 28 | sum += valI * crcs[i]; 29 | } 30 | var crc = 11 - (sum % 11); 31 | switch (crc) { 32 | case 10: { 33 | crc = 'X'; 34 | break; 35 | } 36 | default: { 37 | break; 38 | } 39 | } 40 | if (crc === orgCode.substring(9)) { 41 | ret = true; 42 | } 43 | } 44 | return ret; 45 | }, 46 | 47 | /** 48 | *验证营业执照是否合法:营业执照长度须为15位数字,前14位为顺序码, 49 | *最后一位为根据GB/T 17710 1999(ISO 7064:1993)的混合系统校验位生成算法 50 | *计算得出。此方法即是根据此算法来验证最后一位校验位是否政正确。如果 51 | *最后一位校验位不正确,则认为此营业执照号不正确(不符合编码规则)。 52 | *以下说明来自于网络: 53 | *我国现行的营业执照上的注册号都是15位的,不存在13位的,从07年开始国 54 | *家进行了全面的注册号升级就全部都是15位的了,如果你看见的是13位的注 55 | *册号那肯定是假的。 56 | *15位数字的含义,代码结构工商注册号由14位数字本体码和1位数字校验码 57 | *组成,其中本体码从左至右依次为:6位首次登记机关码、8位顺序码。 58 | * 一、前六位代表的是工商行政管理机关的代码,国家工商行政管理总局用 59 | * “100000”表示,省级、地市级、区县级登记机关代码分别使用6位行 60 | * 政区划代码表示。设立在经济技术开发区、高新技术开发区和保税区 61 | * 的工商行政管理机关(县级或县级以上)或者各类专业分局应由批准 62 | * 设立的上级机关统一赋予工商行政管理机关代码,并报国家工商行政 63 | * 管理总局信息化管理部门备案。 64 | * 二、顺序码是7-14位,顺序码指工商行政管理机关在其管辖范围内按照先 65 | * 后次序为申请登记注册的市场主体所分配的顺序号。为了便于管理和 66 | * 赋码,8位顺序码中的第1位(自左至右)采用以下分配规则: 67 | * 1)内资各类企业使用“0”、“1”、“2”、“3”; 68 | * 2)外资企业使用“4”、“5”; 69 | * 3)个体工商户使用“6”、“7”、“8”、“9”。 70 | * 顺序码是系统根据企业性质情况自动生成的。 71 | * 三、校验码是最后一位,校验码用于检验本体码的正确性 72 | * 73 | *18位编码的校验依据GB 32100-2015 74 | * 《法人和其他组织统一社会信用代码编码规则》, 75 | * 统一代码由十八位阿拉伯数字或大写英文字母(不使用I、O、Z、S、V)组成, 76 | * 包括第1位登记管理部门代码、第2位机构类别代码、第3位~第8位登记管理机关行政区划码、 77 | * 第9位~第17位主体标识码(组织机构代码)、第18位校验码五个部份。 78 | */ 79 | isValidBusCode: function (busCode) { 80 | var ret = false; 81 | if (busCode.length === 15) { 82 | var sum = 0; 83 | var s = []; 84 | var p = []; 85 | var a = []; 86 | var m = 10; 87 | p[0] = m; 88 | for (var i = 0; i < busCode.length; i++) { 89 | a[i] = parseInt(busCode.substring(i, i + 1), m); 90 | s[i] = (p[i] % (m + 1)) + a[i]; 91 | if (s[i] % m === 0) { 92 | p[i + 1] = 10 * 2; 93 | } else { 94 | p[i + 1] = (s[i] % m) * 2; 95 | } 96 | } 97 | if ((s[14] % m) === 1) { 98 | // 营业执照编号正确! 99 | ret = true; 100 | } else { 101 | // 营业执照编号错误! 102 | ret = false; 103 | } 104 | } else if (busCode.length === 18) { 105 | var reg = /^[1-9A-GY]{1}[1239]{1}[1-5]{1}[0-9]{5}[0-9A-Z]{10}$/; 106 | if (!reg.test(busCode)) { 107 | ret = false; 108 | } else { 109 | var str = '0123456789ABCDEFGHJKLMNPQRTUWXY'; 110 | var ws = [1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28]; 111 | var codes = []; 112 | codes[0] = busCode.substr(0, busCode.length - 1); 113 | codes[1] = busCode.substr(busCode.length - 1, busCode.length); 114 | sum = 0; 115 | for (let i = 0; i < 17; i++) { 116 | sum += str.indexOf(codes[0].charAt(i)) * ws[i]; 117 | } 118 | var c18 = 31 - (sum % 31); 119 | if (c18 === 31) { 120 | c18 = 'Y'; 121 | } else if (c18 === 30) { 122 | c18 = '0'; 123 | } 124 | 125 | if (str.charAt(c18) !== codes[1]) { 126 | ret = false; 127 | } else { 128 | ret = true; 129 | } 130 | } 131 | } 132 | return ret; 133 | }, 134 | /** 135 | *验证国税税务登记号是否合法:税务登记证是6位区域代码+组织机构代码 136 | */ 137 | isValidTaxCode: function (taxCode) { 138 | var ret = false; 139 | if (taxCode.length === 15 && /\d{6}.test(taxCode.substr(0,6))/ && this.isValidOrgCode(taxCode.substr(5))) { 140 | ret = true; 141 | } 142 | return ret; 143 | }, 144 | 145 | formatDateTime: function (inputTime) { 146 | let date = new Date(inputTime); 147 | let y = date.getFullYear(); 148 | let m = date.getMonth() + 1; 149 | m = m < 10 ? ('0' + m) : m; 150 | let d = date.getDate(); 151 | d = d < 10 ? ('0' + d) : d; 152 | let h = date.getHours(); 153 | h = h < 10 ? ('0' + h) : h; 154 | let minute = date.getMinutes(); 155 | let second = date.getSeconds(); 156 | minute = minute < 10 ? ('0' + minute) : minute; 157 | second = second < 10 ? ('0' + second) : second; 158 | return y + '-' + m + '-' + d + ' ' + h + ':' + minute + ':' + second; 159 | }, 160 | 161 | title: function (title) { 162 | title = title ? title + ' - GXB-BOX' : 'GXB-BOX'; 163 | window.document.title = title; 164 | }, 165 | 166 | testEnvConfig: { 167 | 'port': 3000, 168 | 'ipfs_addr': ['/ip4/139.196.138.193/tcp/5001', '/ip4/106.14.194.229/tcp/5001'], 169 | 'witnesses': [ 170 | 'ws://47.96.164.78:28090' 171 | ], 172 | 'faucet_url': 'http://47.96.164.78:8888', 173 | 'referrer': 'nathan' 174 | }, 175 | 176 | prodEnvConfig: { 177 | 'port': 3000, 178 | 'ipfs_addr': ['/ip4/139.196.138.193/tcp/5001', '/ip4/106.14.194.229/tcp/5001'], 179 | 'witnesses': [ 180 | 'wss://node1.gxb.io', 181 | 'wss://node5.gxb.io', 182 | 'wss://node8.gxb.io', 183 | 'wss://node11.gxb.io' 184 | ], 185 | 'faucet_url': 'https://opengateway.gxb.io' 186 | } 187 | }; 188 | 189 | export default util; 190 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import VueRouter from 'vue-router'; 4 | import Routers from './router'; 5 | import Vuex from 'vuex'; 6 | import Util from './libs/util'; 7 | import App from './app.vue'; 8 | import axios from 'axios'; 9 | import VueWebsocket from 'vue-websocket'; 10 | import VueTimeago from 'vue-timeago'; 11 | import 'iview/dist/styles/iview.css'; 12 | 13 | Vue.prototype.$http = axios; 14 | Vue.use(VueRouter); 15 | Vue.use(Vuex); 16 | Vue.use(iView); 17 | Vue.use(VueWebsocket, '/', {reconnection: true}); 18 | Vue.use(VueTimeago, { 19 | name: 'timeago', 20 | locale: 'zh-CN', 21 | locales: { 22 | 'zh-CN': require('vue-timeago/locales/zh-CN.json') 23 | } 24 | }); 25 | 26 | const store = new Vuex.Store({ 27 | state: { 28 | env_type: '', 29 | account: null, 30 | account_type: null, 31 | init_step: 0, 32 | certified: false, 33 | active_nav: null, 34 | config: null 35 | }, 36 | getters: { 37 | env_type: state => state.env_type, 38 | account_type: state => state.account_type, 39 | account: state => state.account, 40 | init_step: state => state.init_step, 41 | certified: state => state.certified, 42 | active_nav: state => state.active_nav, 43 | config: state => state.config 44 | }, 45 | mutations: { 46 | setEnvType (state, payload) { 47 | state.env_type = payload.env_type; 48 | }, 49 | setAccountType (state, payload) { 50 | state.account_type = payload.account_type; 51 | }, 52 | setAccount (state, payload) { 53 | state.account = payload.account; 54 | }, 55 | setInitStep (state, payload) { 56 | state.init_step = payload.init_step; 57 | }, 58 | setCertified (state, payload) { 59 | state.certified = payload.certified; 60 | }, 61 | setActiveNav (state, payload) { 62 | state.active_nav = payload.active_nav; 63 | }, 64 | setConfig (state, payload) { 65 | state.config = payload.config; 66 | }, 67 | setReload (state) { 68 | state.env_type = ''; 69 | state.account_type = null; 70 | state.account = null; 71 | state.init_step = 0; 72 | state.certified = false; 73 | state.active_nav = null; 74 | state.config = null; 75 | } 76 | }, 77 | actions: { 78 | setEnvType ({commit}, payload) { 79 | commit('setEnvType', payload); 80 | }, 81 | setAccountType ({commit}, payload) { 82 | commit('setAccountType', payload); 83 | }, 84 | setAccount ({commit}, payload) { 85 | commit('setAccount', payload); 86 | }, 87 | setInitStep ({commit}, payload) { 88 | commit('setInitStep', payload); 89 | }, 90 | setCertified ({commit}, payload) { 91 | commit('setCertified', payload); 92 | }, 93 | setActiveNav ({commit}, payload) { 94 | commit('setActiveNav', payload); 95 | }, 96 | setConfig ({commit}, payload) { 97 | commit('setConfig', payload); 98 | }, 99 | setReload ({commit}) { 100 | commit('setReload'); 101 | } 102 | } 103 | }); 104 | 105 | // 路由配置 106 | const RouterConfig = { 107 | mode: 'history', 108 | routes: Routers 109 | }; 110 | const router = new VueRouter(RouterConfig); 111 | 112 | router.beforeEach((to, from, next) => { 113 | iView.LoadingBar.start(); 114 | Util.title(to.meta.title); 115 | 116 | switch (to.path.split('/')[1]) { 117 | case 'init': 118 | store.state.active_nav = '1'; 119 | break; 120 | case 'console': 121 | store.state.active_nav = '2'; 122 | break; 123 | case 'market': 124 | store.state.active_nav = '3'; 125 | break; 126 | case 'setting': 127 | store.state.active_nav = '4'; 128 | break; 129 | case '404': 130 | store.state.active_nav = null; 131 | break; 132 | default: 133 | store.state.active_nav = '2'; 134 | break; 135 | } 136 | 137 | // 初始化未完成,强制进入初始化 138 | if ((store.state.init_step !== 'finished') && (to.path !== '/init')) { 139 | next('/init'); 140 | } 141 | // 初始化已完成,禁止进入初始化 142 | if ((store.state.init_step === 'finished') && (to.path === '/init')) { 143 | next('/'); 144 | } 145 | next(); 146 | }); 147 | 148 | router.afterEach(() => { 149 | iView.LoadingBar.finish(); 150 | window.scrollTo(0, 0); 151 | }); 152 | 153 | if (localStorage.getItem('__gxbBox__env')) { 154 | store.state.env_type = localStorage.getItem('__gxbBox__env'); 155 | // 验证初始化是否完成 - 加载配置文件 156 | axios.get('/api/fetch_config?env=' + store.state.env_type).then((res) => { 157 | store.state.config = res.data; 158 | // 是否选择账户类型 159 | if (res.data['common'] && res.data['common'].account_type) { 160 | store.state.account_type = res.data['common'].account_type; 161 | if (res.data[store.state.account_type] && res.data[store.state.account_type].account_name) { 162 | store.state.account = { 163 | account_name: res.data[store.state.account_type].account_name, 164 | private_key: res.data[store.state.account_type].private_key 165 | }; 166 | // 是否已完成认证 167 | axios.get('/api/fetch_account/' + res.data[store.state.account_type].account_name).then((res) => { 168 | let account = res.data; 169 | if (store.state.account_type === 'merchant') { 170 | if (account.merchant_expiration_date !== '1970-01-01T00:00:00') { 171 | store.state.certified = true; 172 | } 173 | } else { 174 | if ((account.merchant_expiration_date !== '1970-01-01T00:00:00') && (account.datasource_expiration_date !== '1970-01-01T00:00:00')) { 175 | store.state.certified = true; 176 | } 177 | } 178 | if (store.state.certified) { 179 | axios.get('/api/fetch_box').then((res) => { 180 | if (res.data && res.data.length && res.data.length > 0) { 181 | // 状态:pm2已启动过 - init-finished 182 | store.state.init_step = 'finished'; 183 | } else { 184 | // 是否已完善配置 185 | if ((store.state.config.merchant && store.state.config.merchant.callback_url) || (store.state.config.datasource && store.state.config.datasource.service && store.state.config.datasource.subscribed_data_product)) { 186 | // 状态:pm2未启动过 - init-step5 187 | store.state.init_step = 5; 188 | } else { 189 | // 状态:账号已认证,配置未完善 - init-step4 190 | store.state.init_step = 4; 191 | } 192 | } 193 | new Vue({ 194 | el: '#app', 195 | router: router, 196 | store: store, 197 | render: h => h(App) 198 | }); 199 | }).catch((err) => { 200 | console.error(err); 201 | }); 202 | } else { 203 | store.state.init_step = 3; 204 | // 状态:账号未完成认证 - init-step3 205 | new Vue({ 206 | el: '#app', 207 | router: router, 208 | store: store, 209 | render: h => h(App) 210 | }); 211 | } 212 | }).catch((err) => { 213 | console.error(err); 214 | }); 215 | } else { 216 | store.state.init_step = 2; 217 | // 状态:尚未创建或导入账号 - init-step2 218 | new Vue({ 219 | el: '#app', 220 | router: router, 221 | store: store, 222 | render: h => h(App) 223 | }); 224 | } 225 | } else { 226 | store.state.init_step = 1; 227 | // 状态:首次访问 - init-step1 228 | new Vue({ 229 | el: '#app', 230 | router: router, 231 | store: store, 232 | render: h => h(App) 233 | }); 234 | } 235 | }).catch((err) => { 236 | console.error(err); 237 | }); 238 | } else { 239 | store.state.init_step = 0; 240 | // 状态:尚未选择环境 - init-step0 241 | new Vue({ 242 | el: '#app', 243 | router: router, 244 | store: store, 245 | render: h => h(App) 246 | }); 247 | } 248 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | const routers = [ 2 | { 3 | path: '/init', 4 | meta: { 5 | title: '使用引导' 6 | }, 7 | component: (resolve) => require(['./views/Init.vue'], resolve) 8 | }, 9 | { 10 | path: '/console', 11 | meta: { 12 | title: '控制台' 13 | }, 14 | component: (resolve) => require(['./views/Console.vue'], resolve) 15 | }, 16 | { 17 | path: '/market', 18 | meta: { 19 | title: '数据市场' 20 | }, 21 | component: (resolve) => require(['./views/Market.vue'], resolve) 22 | }, 23 | { 24 | path: '/market/product', 25 | meta: { 26 | title: '产品详情' 27 | }, 28 | component: (resolve) => require(['./views/Product.vue'], resolve) 29 | }, 30 | { 31 | path: '/market/league', 32 | meta: { 33 | title: '联盟详情' 34 | }, 35 | component: (resolve) => require(['./views/League.vue'], resolve) 36 | }, 37 | { 38 | path: '/setting', 39 | meta: { 40 | title: '系统设置' 41 | }, 42 | component: (resolve) => require(['./views/Setting.vue'], resolve) 43 | }, 44 | { 45 | path: '/', 46 | meta: { 47 | title: '控制台' 48 | }, 49 | component: (resolve) => require(['./views/Console.vue'], resolve) 50 | }, 51 | { 52 | path: '*', 53 | meta: { 54 | title: '404-页面不存在' 55 | }, 56 | component: (resolve) => require(['./views/404.vue'], resolve) 57 | } 58 | ]; 59 | export default routers; 60 | -------------------------------------------------------------------------------- /src/template/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GXB-BOX 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/vendors.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import iView from 'iview'; 3 | import VueRouter from 'vue-router'; 4 | import axios from 'axios'; 5 | import Vuex from 'vuex'; -------------------------------------------------------------------------------- /src/views/404.vue: -------------------------------------------------------------------------------- 1 | 69 | 82 | 83 | 98 | -------------------------------------------------------------------------------- /src/views/Console.vue: -------------------------------------------------------------------------------- 1 | 12 | 17 | 28 | -------------------------------------------------------------------------------- /src/views/Init.vue: -------------------------------------------------------------------------------- 1 | 55 | 79 | 137 | -------------------------------------------------------------------------------- /src/views/League.vue: -------------------------------------------------------------------------------- 1 | 129 | 187 | 264 | -------------------------------------------------------------------------------- /src/views/Setting.vue: -------------------------------------------------------------------------------- 1 | 11 | 42 | 96 | -------------------------------------------------------------------------------- /src/views/common/Footer.vue: -------------------------------------------------------------------------------- 1 | 13 | 23 | 28 | -------------------------------------------------------------------------------- /src/views/common/Header.vue: -------------------------------------------------------------------------------- 1 | 39 | 97 | 231 | -------------------------------------------------------------------------------- /src/views/components/AccountImage.vue: -------------------------------------------------------------------------------- 1 | 5 | 54 | -------------------------------------------------------------------------------- /src/views/components/AccountType.vue: -------------------------------------------------------------------------------- 1 | 6 | 18 | 63 | -------------------------------------------------------------------------------- /src/views/components/EnvType.vue: -------------------------------------------------------------------------------- 1 | 18 | 61 | -------------------------------------------------------------------------------- /src/views/components/GxbBoxStart.vue: -------------------------------------------------------------------------------- 1 | 11 | 26 | 63 | -------------------------------------------------------------------------------- /src/views/components/SettingAccount.vue: -------------------------------------------------------------------------------- 1 | 60 | 90 | 115 | -------------------------------------------------------------------------------- /src/views/components/SettingApi.vue: -------------------------------------------------------------------------------- 1 | 21 | 68 | 167 | -------------------------------------------------------------------------------- /src/views/components/SettingArchive.vue: -------------------------------------------------------------------------------- 1 | 68 | 121 | 161 | -------------------------------------------------------------------------------- /src/views/components/SettingConfig.vue: -------------------------------------------------------------------------------- 1 | 17 | 29 | 46 | -------------------------------------------------------------------------------- /static/img/gxb-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxchain/gxb-box/ec843dc6a92106477e8fa8fb1bfab1286eceeb33/static/img/gxb-box.png -------------------------------------------------------------------------------- /static/img/init.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gxchain/gxb-box/ec843dc6a92106477e8fa8fb1bfab1286eceeb33/static/img/logo.png -------------------------------------------------------------------------------- /test/connect.js: -------------------------------------------------------------------------------- 1 | var gxbjsws = require("gxbjs-ws"); 2 | 3 | var connectionManager = new gxbjsws.Manager({url:'wss://node1.gxb.io', urls: ['wss://node1.gxb.io']}); 4 | connectionManager.connectWithFallback(true).then(()=> { 5 | console.log('已连接'); 6 | }).catch((ex)=> { 7 | console.error('连接失败,3秒后重试', ex.message); 8 | }) 9 | 10 | gxbjsws.Apis.setRpcConnectionStatusCallback(function (status) { 11 | var statusMap = { 12 | open: '开启', 13 | closed: '关闭', 14 | error: '错误', 15 | reconnect: '重新连接' 16 | } 17 | 18 | console.log('witness当前状态:', statusMap[status] || status); 19 | }); -------------------------------------------------------------------------------- /test/data_product.js: -------------------------------------------------------------------------------- 1 | import GXChainService from '../lib/services/GXChainService' 2 | import {Apis} from "gxbjs-ws" 3 | import {ChainStore} from 'gxbjs' 4 | 5 | Apis.instance("ws://192.168.1.118:28090", true) 6 | .init_promise.then((res) => { 7 | 8 | ChainStore.init().then(() => { 9 | GXChainService.fetch_data_product('1.17.0').then(function (prod) { 10 | console.log(prod); 11 | }).catch((ex)=>{ 12 | console.error(ex); 13 | }) 14 | }).catch((ex)=>{ 15 | console.error(ex); 16 | }) 17 | 18 | }).catch((ex)=>{ 19 | console.error(ex); 20 | }); -------------------------------------------------------------------------------- /test/encrypt_decrypt.js: -------------------------------------------------------------------------------- 1 | import {PrivateKey, Aes} from 'gxbjs' 2 | import GXChainService from '../lib/services/GXChainService' 3 | 4 | let private_key = PrivateKey.fromWif(''); 5 | let pubKey = private_key.toPublicKey().toPublicKeyString(); 6 | 7 | let encrypted = GXChainService.encrypt_params({ 8 | name: '张三', 9 | idcard: '333301199012180000', 10 | photo: '6Ieq5ouN54WnYmFzZTY0' 11 | }, private_key, pubKey) 12 | console.log('encrypted:', encrypted); 13 | 14 | let decrypted = GXChainService.decrypt_msg(encrypted, private_key, ''); 15 | console.log('decrypted:', decrypted); 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/faucet.js: -------------------------------------------------------------------------------- 1 | import FaucetService from '../lib/services/FaucetService' 2 | import * as config from 'config' 3 | import {Apis} from "gxbjs-ws" 4 | import {ChainStore,PrivateKey} from 'gxbjs' 5 | 6 | let merchant_private_key = PrivateKey.fromWif(config.merchant.private_key); 7 | 8 | Apis.instance("ws://192.168.1.118:28090", true) 9 | .init_promise.then((res) => { 10 | 11 | ChainStore.init().then(() => { 12 | 13 | FaucetService.request_for_a_token('1.2.19', '1.19.0', 'foobar',config.merchant.account_name,merchant_private_key).then((resp)=> { 14 | console.log(resp); 15 | }).catch((ex)=> { 16 | console.error(ex); 17 | }) 18 | }).catch((ex)=>{ 19 | console.error(ex); 20 | }) 21 | 22 | }).catch((ex)=>{ 23 | console.error(ex); 24 | }); -------------------------------------------------------------------------------- /test/ipfs.js: -------------------------------------------------------------------------------- 1 | import IPFSService from '../lib/services/IPFSService' 2 | 3 | IPFSService.upload('bZMtv6paZIzApBhP50o1R6Y6MQwPE3o4YlxYM5n3MvO4yCDph93LzWK/Jvq72UJqYifPtGA+2PJRCKkMGu4FxYxNWSYIEhj11+3bOLYIqGo=', ["/ip4/139.196.138.193/tcp/5001", "/ip4/106.14.194.229/tcp/5001"]).then((hash) => { 4 | console.log('uploaded:', hash); 5 | IPFSService.download(hash, ["/ip4/139.196.138.193/tcp/5001", "/ip4/106.14.194.229/tcp/5001"]).then((data) => { 6 | console.log('data:', data); 7 | }).catch((ex) => { 8 | console.error(ex); 9 | }) 10 | }) 11 | 12 | -------------------------------------------------------------------------------- /test/league.js: -------------------------------------------------------------------------------- 1 | import GXChainService from '../lib/services/GXChainService' 2 | import {Apis} from "gxbjs-ws" 3 | import {ChainStore} from 'gxbjs' 4 | 5 | Apis.instance("ws://192.168.1.118:28090", true) 6 | .init_promise.then((res) => { 7 | 8 | ChainStore.init().then(() => { 9 | GXChainService.fetch_league('1.19.0').then(function (league) { 10 | console.log(league); 11 | }).catch((ex)=>{ 12 | console.error(ex); 13 | }) 14 | }).catch((ex)=>{ 15 | console.error(ex); 16 | }) 17 | 18 | }).catch((ex)=>{ 19 | console.error(ex); 20 | }); -------------------------------------------------------------------------------- /test/leveldb.js: -------------------------------------------------------------------------------- 1 | import LevelDBService from '../lib/services/LevelDBService' 2 | 3 | LevelDBService.put('testcase',JSON.stringify({ 4 | 'foobar':{ 5 | timestamp:new Date().getTime() 6 | } 7 | })).then(function () { 8 | LevelDBService.get('testcase').then(function (val) { 9 | console.log(JSON.parse(val)); 10 | }) 11 | }) -------------------------------------------------------------------------------- /upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git stash 3 | git pull 4 | git stash pop 5 | npm install -d 6 | npm run build --------------------------------------------------------------------------------