├── .gitignore ├── README.md ├── admin.js ├── babelconfig.js ├── choiceness.js ├── choiceness └── index.js ├── common.js ├── common ├── aes.js ├── driveWealth.js ├── everyDay.js ├── everyDays │ ├── competition.js │ ├── marketTime.js │ ├── profitRank.js │ └── totalAssets.js ├── index.js ├── singleton.js └── sqlStr.js ├── config.js ├── getDWData.js ├── getDWData └── index.js ├── getDWData2.js ├── getDWData2 └── index.js ├── getShSzHkRank └── index.js ├── getSinaData.js ├── getSinaData ├── getPrice.js ├── index.js ├── stockRank.js ├── stocksRef.js └── usStockRank.js ├── homePageGenerator.js ├── homePageGenerator ├── generators.js ├── index.js └── publishOnTime.js ├── index.js ├── package.json ├── paperTrade.js ├── paperTrade ├── deal.js └── index.js ├── priceNotify.js ├── priceNotify └── index.js ├── webapi.js ├── webapi ├── assets │ ├── image │ │ ├── bg1.jpg │ │ ├── bg1_1.jpg │ │ ├── bg2.jpg │ │ ├── bg3.jpg │ │ ├── bg4.jpg │ │ ├── bg5.jpg │ │ ├── bg6.jpg │ │ ├── bg7.jpg │ │ ├── main1_1_1.jpg │ │ ├── main1_1_2.jpg │ │ ├── main1_1_3.jpg │ │ ├── main1_1_4.jpg │ │ ├── main1_1_5.jpg │ │ ├── main1_1_6.jpg │ │ ├── main1_1_7.jpg │ │ ├── main1_1_8.jpg │ │ ├── main1_2.jpg │ │ ├── pic1.png │ │ ├── qxtbg.png │ │ ├── qxtqg.png │ │ ├── top.png │ │ └── up.png │ ├── js │ │ ├── TouchSlide.1.1.js │ │ ├── calculator.js │ │ ├── countUp.min.js │ │ ├── echarts.min.js │ │ ├── idangerous.swiper.min.js │ │ ├── idangerous.swiper.progress.min.js │ │ ├── index.js │ │ ├── jquery-1.8.3.min.js │ │ ├── jquery-2.1.1.min.js │ │ └── jquery.lazyload.js │ └── style │ │ ├── animate.css │ │ ├── dynamic.css │ │ ├── idangerous.swiper.css │ │ └── loading.css ├── config.js ├── index.js ├── middles │ ├── allowAccess.js │ ├── checkEmpty.js │ ├── checkNum.js │ └── checkToken.js ├── routes │ ├── admin.js │ ├── choiceness.js │ ├── drivewealth.js │ ├── h5.js │ ├── homePage.js │ ├── imageTalk.js │ ├── paperTrade.js │ ├── personal.js │ ├── statistics.js │ ├── stockcompetition.js │ ├── trade.js │ ├── user.js │ └── video.js ├── statistic.js ├── views │ ├── articleAndroid.jshtml │ ├── articleIOS.jshtml │ └── battleReport.jshtml └── web │ ├── dist │ ├── bundle.js │ ├── index.html │ ├── vendor-manifest.json │ └── vendor.dll.js │ ├── index.js │ ├── view │ ├── app.js │ ├── app.vue │ └── table.vue │ ├── webpack.config.js │ └── webpack.dll.config.js ├── wechat-info.json ├── wolfstreet.sql └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | node_modules 3 | *log.* 4 | .vs 5 | pconfig.js 6 | test* 7 | .vscode -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | |文件夹|说明 2 | |:---|--- 3 | |choiceness|精选 4 | |getSinaData|实时获取新浪数据存入redis中 5 | |homePageGenerator|首页生成服务 6 | |priceNotify|股价提醒系统 7 | |paperTrade|模拟交易 8 | |choiceness|精选列表数据实时计算 9 | |webapi|服务器API接口 10 | |common|通用文件夹,定时任务检测 11 | 如果文件夹中有config.js均可以放入pconfig.js来覆盖里面的配置项 -------------------------------------------------------------------------------- /admin.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | const singleton = require('./common/singleton').default 3 | const JPush = require('jpush-sdk'); 4 | const repl = require('repl'); 5 | const dwUrls = require('./common/driveWealth').default 6 | var replServer = repl.start({ prompt: '> ' }); 7 | replServer.defineCommand('resetDw', { 8 | help: '重置嘉维模拟账号', 9 | async action(code) { 10 | if (code != "all") { 11 | code.split(",").forEach(memberCode => singleton.CreateParactice(memberCode, "")) 12 | } else { 13 | let users = await singleton.selectMainDB("wf_stockcompetitionmember", { CompetitionId: 1 }) 14 | users.forEach(user => singleton.CreateParactice(user.MemberCode, "")) 15 | } 16 | } 17 | }); 18 | replServer.defineCommand('jpushMessage', { 19 | help: '发送jpushMessage', 20 | async action(arg) { 21 | let { MemberCode, apns_production = false, args } = JSON.parse(arg) 22 | let [result] = await singleton.knex("wf_im_jpush").select("JpushRegID").where({ MemberCode }).orderBy("JpushLastLoginTime", "desc").limit(1) 23 | if (result) { 24 | let { JpushRegID } = result 25 | singleton.jpushClient.push().setPlatform(JPush.ALL).setAudience(JPush.registration_id(JpushRegID)) 26 | .setOptions(null, null, null, Config.apns_production) 27 | .setMessage(...args) 28 | .send(async(err, res) => { 29 | if (err) { 30 | if (err instanceof JPush.APIConnectionError) { 31 | console.log(err.message) 32 | } else if (err instanceof JPush.APIRequestError) { 33 | console.log(err.message) 34 | } 35 | } else { 36 | console.log(res) 37 | } 38 | }) 39 | } 40 | } 41 | }); 42 | replServer.defineCommand('showdw', { 43 | help: "嘉维模拟账号", 44 | async action(arg) { 45 | let oldcode = arg.substr(0, 8) 46 | let { sessionKey, accounts } = await request({ 47 | uri: dwUrls.createSession, 48 | method: "POST", 49 | body: { 50 | "appTypeID": "2000", 51 | "appVersion": "0.1", 52 | username: arg, 53 | "emailAddress": oldcode + "@wolfstreet.tv", 54 | "ipAddress": "1.1.1.1", 55 | "languageID": "zh_CN", 56 | "osVersion": "iOS 9.1", 57 | "osType": "iOS", 58 | "scrRes": "1920x1080", 59 | password: "p" + oldcode 60 | }, 61 | timeout: 10000, 62 | json: true 63 | }) 64 | console.log(accounts) 65 | let [{ userID, cash, accountNo, accountID }] = accounts 66 | } 67 | }) -------------------------------------------------------------------------------- /babelconfig.js: -------------------------------------------------------------------------------- 1 | module.exports = { plugins: ['transform-es2015-modules-commonjs', ['transform-object-rest-spread', { useBuiltIns: true }]] } -------------------------------------------------------------------------------- /choiceness.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./choiceness/index.js'); -------------------------------------------------------------------------------- /choiceness/index.js: -------------------------------------------------------------------------------- 1 | import request from 'request' 2 | import Config from '../config' 3 | import amqp from 'amqplib' 4 | import singleton from '../common/singleton' 5 | const { mainDB, redisClient } = singleton 6 | async function startMQ() { 7 | var amqpConnection = await amqp.connect(Config.amqpConn) 8 | let channel = await amqpConnection.createChannel() 9 | await channel.assertExchange("broadcast", "fanout") 10 | let ok = await channel.assertQueue('sinaData_choiceness') 11 | await channel.assertQueue('getSinaData') 12 | ok = await channel.bindQueue('sinaData_choiceness', 'broadcast', 'fanout') 13 | 14 | channel.consume('sinaData_choiceness', msg => { 15 | switch (msg.content.toString()) { 16 | case "restart": //股票引擎重启信号 17 | console.log(new Date(), "sina restart") 18 | getAllChocieness(channel) 19 | break; 20 | } 21 | channel.ack(msg) 22 | }) 23 | ok = await channel.assertQueue('choiceGenerate') 24 | channel.consume('choiceGenerate', msg => { 25 | getAllChocieness(channel) 26 | channel.ack(msg) 27 | }) 28 | getAllChocieness(channel) 29 | //redisClient.mset() 30 | } 31 | /**从数据库里读取所有精选信息 */ 32 | async function getAllChocieness(channel) { 33 | console.log("获取所有精选") 34 | let [choiceness] = await mainDB.query('select a.Id,a.`Code`,a.Title,a.Details,a.content,a.CoverImage,a.BannerImage,a.Provenance,a.StocksCount,a.State,b.Id SecuritiesId,b.SecuritiesType,b.SecuritiesNo,b.SecuritiesName from wf_choiceness a, wf_choiceness_stock b where a.`Code` = b.ChoiceCode and a.`Status` = 1 order by a.Id desc') 35 | let choicenessMap = {} //按精选Id分组 36 | bannerChoice = [] 37 | normalChoice = [] 38 | let allStock = [] 39 | for (let c of choiceness) { 40 | if (!choicenessMap[c.Id]) { 41 | choicenessMap[c.Id] = { Id: c.Id, Code: c.Code, Title: c.Title, Details: c.Details, StocksCount: c.StocksCount, Stocks: [] } 42 | choicenessMap[c.Id].Image = Config.picBaseURL + (c.State == 1 ? c.BannerImage : c.CoverImage) 43 | if (c.State == 1) bannerChoice.push(choicenessMap[c.Id]) 44 | else normalChoice.push(choicenessMap[c.Id]) 45 | } 46 | let stock = Config.getQueryName(c) 47 | choicenessMap[c.Id].Stocks.push(stock) 48 | if (!allStock.contain(stock)) allStock.push(stock) 49 | } 50 | channel.sendToQueue("getSinaData", new Buffer(JSON.stringify({ type: "reset", listener: "choiceness", symbols: allStock }))) 51 | } 52 | var intervalId; 53 | var bannerChoice; 54 | var normalChoice; 55 | 56 | async function caculateAvgDelta(target) { 57 | let result = [] 58 | for (let c of target) { 59 | let allDelta = 0; 60 | for (let s of c.Stocks) { 61 | let sp = await singleton.getLastPrice(s) 62 | if (sp[5]) { 63 | allDelta += sp[5]; //涨跌幅 64 | } else if (sp[5] !== 0) { 65 | console.log(sp, s) 66 | } 67 | } 68 | if (c.Stocks.length) 69 | allDelta = allDelta / c.Stocks.length //平均涨跌幅 70 | let r = Object.assign({ AvgRiseFallRange: allDelta.toFixed(2) }, c) 71 | delete r.Stocks 72 | result.push(r) 73 | } 74 | result.sort() 75 | return result 76 | } 77 | 78 | function start() { 79 | intervalId = setInterval(async() => { //计算涨跌幅 80 | if (bannerChoice) { 81 | let r = await caculateAvgDelta(bannerChoice) 82 | redisClient.set("cacheResult:bannerChoice", JSON.stringify(r)) 83 | } 84 | if (normalChoice) { 85 | let r = await caculateAvgDelta(normalChoice) 86 | redisClient.set("cacheResult:normalChoice", JSON.stringify(r)) 87 | } 88 | }, 5000) 89 | } 90 | 91 | function stop() { 92 | clearInterval(intervalId) 93 | } 94 | 95 | startMQ() 96 | start() -------------------------------------------------------------------------------- /common.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./common/index.js'); -------------------------------------------------------------------------------- /common/aes.js: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto' 2 | const algorithm = 'aes-128-cbc'; 3 | const key = '5c9f3f4bb2f6c518'; 4 | const clearEncoding = 'utf8'; 5 | const iv = "0e868177a2513898"; 6 | //var cipherEncoding = 'hex'; 7 | //If the next line is uncommented, the final cleartext is wrong. 8 | const cipherEncoding = 'base64'; 9 | 10 | function encrypt(data) { 11 | let cipher = crypto.createCipheriv(algorithm, key, iv); 12 | let crypted = cipher.update(data, clearEncoding, 'binary'); 13 | crypted += cipher.final('binary'); 14 | crypted = new Buffer(crypted, 'binary').toString(cipherEncoding); 15 | return crypted; 16 | } 17 | 18 | function decrypt(crypted) { 19 | crypted = new Buffer(crypted, cipherEncoding).toString('binary'); 20 | let decipher = crypto.createDecipheriv(algorithm, key, iv); 21 | let decoded = decipher.update(crypted, 'binary', clearEncoding); 22 | decoded += decipher.final(clearEncoding); 23 | return decoded; 24 | } 25 | export { encrypt, decrypt } -------------------------------------------------------------------------------- /common/driveWealth.js: -------------------------------------------------------------------------------- 1 | import Config from '../config' 2 | var dwUrls = { 3 | apiHost: "", 4 | reportsHost: "", 5 | /** 创建会话 */ 6 | get createSession() { 7 | return this.apiHost + "/v1/userSessions" 8 | }, 9 | /** 持仓和未完成的订单 */ 10 | get position() { 11 | return this.reportsHost + "/DriveWealth" 12 | }, 13 | get createPractice() { 14 | return this.apiHost + "/v1/signups/practice" 15 | } 16 | } 17 | Object.assign(dwUrls, Config.driveWealthHost) 18 | export { dwUrls } -------------------------------------------------------------------------------- /common/everyDay.js: -------------------------------------------------------------------------------- 1 | class EveryDay { 2 | constructor(t, f) { 3 | this.callback = f 4 | this.time = t 5 | this.lastRun = null 6 | this.todayIsDone = null 7 | } 8 | checkTodayIsDone(now) { 9 | if (this.lastRun) { 10 | this.todayIsDone = this.lastRun.format("yyyy-MM-dd") == now.format("yyyy-MM-dd") 11 | } else { 12 | this.todayIsDone = false 13 | } 14 | } 15 | checkAndRun(now) { 16 | this.checkTodayIsDone(now) 17 | if (!this.todayIsDone && now > new Date(now.format("yyyy-MM-dd") + " " + this.time)) { 18 | this.callback() 19 | this.lastRun = now 20 | this.todayIsDone = true 21 | return true 22 | } 23 | return false 24 | } 25 | run() { 26 | this.callback() 27 | } 28 | } 29 | export default EveryDay -------------------------------------------------------------------------------- /common/everyDays/competition.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment-timezone' 2 | import EveryDay from '../everyDay' 3 | import marketTime from './marketTime' 4 | import Config from '../../config' 5 | import sqlstr from '../sqlStr' 6 | import singleton from '../singleton' 7 | const { mainDB, redisClient } = singleton 8 | class Competition extends EveryDay { 9 | constructor() { 10 | super(null, null) 11 | this.checkRange() 12 | } 13 | checkRange() { 14 | redisClient.get("teamCompetitionTimetable", result => { 15 | this.range = result ? result.split(',').map(x => new Date(x)) : null 16 | }) 17 | mainDB.query("select * from wf_competition_record order by Id desc limit 1", { type: "SELECT" }).then(result => { 18 | this.currentCompetition = result[0] 19 | }) 20 | } 21 | 22 | checkAndRun(now) { 23 | this.checkTodayIsDone(now) 24 | if (this.todayIsDone) return false 25 | if (this.currentCompetition) { 26 | if (now < new Date(this.currentCompetition.StartTime)) { 27 | //比赛前不做任何处理 28 | return false 29 | } 30 | } else { 31 | //没有任何比赛,不做处理 32 | return false 33 | } 34 | if (this.range) { 35 | if (now.format("yyyy-MM-dd") == this.range[0].format("yyyy-MM-dd")) { 36 | if (now > this.range[0]) { 37 | this.startCompetition() 38 | this.lastRun = now 39 | this.todayIsDone = true 40 | return true 41 | } 42 | } else if (now.format("yyyy-MM-dd") == this.range[1].format("yyyy-MM-dd")) { 43 | if (now > this.range[1]) { 44 | this.endCompetition() 45 | this.lastRun = now 46 | this.todayIsDone = true 47 | return true 48 | } 49 | } 50 | } else 51 | switch (now.getDay()) { 52 | case 1: 53 | if (marketTime.us) { 54 | this.startCompetition() 55 | this.lastRun = now 56 | this.todayIsDone = true 57 | return true 58 | } 59 | break; 60 | case 6: 61 | // if (!marketTime.us) { 62 | // this.endCompetition() 63 | // this.lastRun = now 64 | // this.todayIsDone = true 65 | // return true 66 | // } 67 | } 68 | return false 69 | } 70 | startCompetition() { 71 | mainDB.query("update wf_competition_apply set State=4") 72 | } 73 | endCompetition() { 74 | mainDB.query("update wf_competition_team_member set Status=0") 75 | mainDB.query("update wf_competition_team set Status=2") 76 | } 77 | run() { 78 | if (now.getDay() == 1) { 79 | this.startCompetition() 80 | } else if (now.getDay() == 6) { 81 | this.endCompetition() 82 | } 83 | } 84 | } 85 | export default new Competition() -------------------------------------------------------------------------------- /common/everyDays/marketTime.js: -------------------------------------------------------------------------------- 1 | /**开市时间 */ 2 | import EveryDay from '../everyDay' 3 | import singleton from '../singleton' 4 | const { mainDB, redisClient } = singleton 5 | export default new EveryDay("00:00:00", async function() { 6 | let usResult = await singleton.selectMainDB("wf_system_opendate_bak", { Type: "us" }, { DealDate: "CurDate()" }) 7 | let usResult2 = await singleton.selectMainDB("wf_system_opendate_bak", { Type: "us" }, { DealDate: "DATE_ADD(CURDATE(),INTERVAL 1 day)" }) 8 | let hsResult = await singleton.selectMainDB("wf_system_opendate_bak", { Type: "hs" }, { DealDate: "CurDate()" }) 9 | let hkResult = await singleton.selectMainDB("wf_system_opendate_bak", { Type: "hk" }, { DealDate: "CurDate()" }) 10 | let usTime = [usResult.length > 0 ? new Date(usResult[0].EndTimePM) : false, usResult2.length > 0 ? new Date(usResult2[0].StartTimeAM) : false] 11 | let hsTime = hsResult.length > 0 ? [new Date(hsResult[0].StartTimeAM), new Date(hsResult[0].EndTimeAM), new Date(hsResult[0].StartTimePM), new Date(hsResult[0].EndTimePM)] : false 12 | let hkTime = hkResult.length > 0 ? [new Date(hkResult[0].StartTimeAM), new Date(hkResult[0].EndTimeAM), new Date(hkResult[0].StartTimePM), new Date(hkResult[0].EndTimePM)] : false 13 | let isUsOpen = now => (usTime[0] && now < usTime[0]) || (usTime[1] && now > usTime[1]) 14 | let isHsOpen = now => hsTime && (now > hsTime[0] && now < hsTime[1] || now > hsTime[2] && now < hsTime[3]) 15 | let isHkOpen = now => hkTime && (now > hkTime[0] && now < hkTime[1] || now > hkTime[2] && now < hkTime[3]) 16 | 17 | let isUsOpen2 = now => (usTime[0] && now < usTime[0].getTime() + 60 * 1000) || (usTime[1] && now > usTime[1]) 18 | let isHsOpen2 = now => hsTime && (now > hsTime[0] && now < hsTime[1].getTime() + 60 * 1000 || now > hsTime[2] && now < hsTime[3].getTime() + 60 * 1000) 19 | let isHkOpen2 = now => hkTime && (now > hkTime[0] && now < hkTime[1].getTime() + 60 * 1000 || now > hkTime[2] && now < hkTime[3].getTime() + 60 * 1000) 20 | 21 | this.setRedis = now => { 22 | let hs = isHsOpen(now) 23 | let marketIsOpen = { us: isUsOpen(now), hk: isHkOpen(now), sh: hs, sz: hs } 24 | Object.assign(this, marketIsOpen) 25 | redisClient.set('marketIsOpen', JSON.stringify(marketIsOpen)) 26 | marketIsOpen = { us: isUsOpen2(now), hk: isHkOpen2(now), sh: isHsOpen2(now), sz: isHsOpen2(now) } 27 | redisClient.set('marketIsOpen2', JSON.stringify(marketIsOpen)) 28 | } 29 | }) -------------------------------------------------------------------------------- /common/everyDays/profitRank.js: -------------------------------------------------------------------------------- 1 | //自研模拟交易计算排行榜 2 | import moment from 'moment-timezone' 3 | import EveryDay from '../everyDay' 4 | import Config from '../../config' 5 | import sqlstr from '../sqlStr' 6 | import singleton from '../singleton' 7 | const { mainDB, redisClient } = singleton 8 | const weekDay = 2 9 | 10 | async function getProfitInfo(daysAgo, AccountNo, TotalAmount) { 11 | let record = await mainDB.query('select TotalAmount from wf_street_practice_asset where AccountNo=:AccountNo and EndDate< DATE_SUB(CurDate(),INTERVAL :daysAgo day) order by EndDate desc limit 1', { replacements: { AccountNo, daysAgo }, type: "SELECT" }) 12 | let amount = (record.length ? record[0].TotalAmount : Config.practiceInitFun) 13 | let profit = TotalAmount - amount 14 | return { profit, yield: profit * 100 / amount } 15 | } 16 | export default new EveryDay("05:00:00", async() => { 17 | let accounts = await singleton.selectMainDB("wf_street_practice_account", { Status: 1 }) 18 | for (let { MemberCode, AccountNo, Cash, TranAmount } 19 | of accounts) { 20 | try { 21 | let assets = await singleton.selectMainDB0("wf_street_practice_asset", { AccountNo }, { EndDate: "curDate()" }) 22 | if (singleton.isEMPTY(assets)) { 23 | let positions = await singleton.selectMainDB("wf_street_practice_positions", { AccountNo }) 24 | if (positions.length) { 25 | let MtmPL = 0 26 | let TotalAssets = 0 27 | for (let p of positions) { 28 | p.LastPrice = (await singleton.getLastPrice(Config.getQueryName(p)))[3] 29 | if (p.Type == 1) { 30 | p.Mtm = p.LastPrice * p.Positions 31 | p.Profit = (p.LastPrice - p.CostPrice) * p.Positions 32 | MtmPL += p.Profit 33 | TotalAssets += p.Mtm 34 | } else if (p.Type == 2) { //空单仓 35 | p.Profit = (p.CostPrice - p.LastPrice) * p.Positions 36 | MtmPL += p.Profit 37 | TotalAssets += p.Profit 38 | } 39 | } 40 | let TotalAmount = Cash + TotalAssets 41 | let newRecord = { 42 | MemberCode, 43 | AccountNo, 44 | Balance: Cash, 45 | TotalAmount, 46 | TotalYield: TotalAmount * 100 / TranAmount, 47 | TotalProfit: TotalAmount - Config.practiceInitFun, 48 | MtmPL, 49 | Positions: TotalAssets 50 | }; 51 | 52 | async function setProfit(daysAgo, type) { 53 | if (daysAgo) 54 | ({ profit: newRecord[type + "Profit"], yield: newRecord[type + "Yield"] } = await getProfitInfo(daysAgo, AccountNo, TotalAmount)); 55 | else 56 | ({ TodayProfit: newRecord[type + "Profit"], TodayYield: newRecord[type + "Yield"] } = newRecord); 57 | } 58 | ({ profit: newRecord.TodayProfit, yield: newRecord.TodayYield } = await getProfitInfo(0, AccountNo, TotalAmount)); 59 | let yestoday = moment().subtract(1, 'days'); //昨天相当于美股的今天 60 | let promises = [] 61 | promises.push(setProfit(yestoday.day() ? yestoday.day() - 1 : 6, "Week")) 62 | promises.push(setProfit(yestoday.date() - 1, "Month")) 63 | promises.push(setProfit(yestoday.dayOfYear() - 1, "Year")) 64 | await Promise.all(promises) 65 | // let daysAgo = yestoday.day() ? yestoday.day() - 1 : 6 66 | // if (daysAgo) 67 | // ({ profit: newRecord.WeekProfit, yield: newRecord.WeekYield } = getProfitInfo(daysAgo, AccountNo, TotalAmount)); 68 | // else 69 | // ({ TodayProfit: newRecord.WeekProfit, TodayYield: newRecord.WeekYield } = newRecord); 70 | // daysAgo = yestoday.date() - 1 71 | // if (daysAgo) 72 | // ({ profit: newRecord.MonthProfit, yield: newRecord.MonthYield } = getProfitInfo(daysAgo, AccountNo, TotalAmount)); 73 | // else 74 | // ({ TodayProfit: newRecord.MonthProfit, TodayYield: newRecord.MonthYield } = newRecord); 75 | // daysAgo = yestoday.dayOfYear() - 1 76 | // if (daysAgo) 77 | // ({ profit: newRecord.YearProfit, yield: newRecord.YearYield } = getProfitInfo(daysAgo, AccountNo, TotalAmount)); 78 | // else 79 | // ({ TodayProfit: newRecord.YearProfit, TodayYield: newRecord.YearYield } = newRecord); 80 | singleton.insertMainDB("wf_street_practice_asset", newRecord, { CreateTime: "now()", EndDate: "curDate()" }) 81 | } else { 82 | singleton.insertMainDB("wf_street_practice_asset", { MemberCode, AccountNo, Balance: Cash, TotalAmount: Cash }, { CreateTime: "now()", EndDate: "curDate()" }) 83 | } 84 | } 85 | } catch (ex) { 86 | console.error(new Date(), ex) 87 | continue; 88 | } 89 | } 90 | await mainDB.query('CALL PRC_WF_STREET_STATISTICS_RANK();') 91 | 92 | }) -------------------------------------------------------------------------------- /common/index.js: -------------------------------------------------------------------------------- 1 | import Config from '../config' 2 | import totalAssets from './everyDays/totalAssets' 3 | import profitRank from './everyDays/profitRank' 4 | import marketTime from './everyDays/marketTime' 5 | import competition from './everyDays/competition' 6 | import singleton from './singleton' 7 | const { redisClient } = singleton 8 | import amqp from 'amqplib' 9 | //每天执行函数 10 | let everyDayFuns = { 11 | totalAssets, 12 | marketTime, 13 | profitRank, 14 | competition 15 | } 16 | async function startMQ() { 17 | let amqpConnection = await amqp.connect(Config.amqpConn) 18 | let channel = await amqpConnection.createChannel() 19 | let ok = await channel.assertQueue('common') 20 | channel.consume('common', msg => { 21 | let data = JSON.parse(msg.content.toString()) 22 | switch (data.type) { 23 | case "call": 24 | let func = data.func.split('.') 25 | if (data.args) 26 | everyDayFuns[func[0]][func[1]](...data.args) 27 | else everyDayFuns[func[0]][func[1]]() 28 | break 29 | } 30 | channel.ack(msg) 31 | }) 32 | } 33 | startMQ() 34 | 35 | 36 | async function initEveryDayFuns() { 37 | for (let f in everyDayFuns) { 38 | let flag = await redisClient.hgetAsync('timeRunFlag', f) 39 | if (flag) { 40 | everyDayFuns[f].lastRun = new Date(flag) 41 | } 42 | } 43 | } 44 | initEveryDayFuns() 45 | setInterval(() => { 46 | let now = new Date() 47 | for (let f in everyDayFuns) { 48 | if (everyDayFuns[f].checkAndRun(now)) { 49 | redisClient.hset('timeRunFlag', f, now) 50 | } 51 | } 52 | if (marketTime.setRedis) marketTime.setRedis(now) 53 | else { 54 | marketTime.callback().then(() => { 55 | marketTime.setRedis(now) 56 | }) 57 | } 58 | }, 1000) -------------------------------------------------------------------------------- /common/singleton.js: -------------------------------------------------------------------------------- 1 | import Config from '../config' 2 | import Sequelize from 'sequelize' 3 | import redis from 'redis' 4 | import bluebird from 'bluebird' 5 | import JPush from 'jpush-sdk' 6 | import mongodb from 'mongodb' 7 | import rongcloudSDK from 'rongcloud-sdk' 8 | import getStockPrice from '../getSinaData/getPrice' 9 | import sqlStr from './sqlStr' 10 | import { dwUrls } from './driveWealth' 11 | import request from 'request-promise' 12 | bluebird.promisifyAll(redis.RedisClient.prototype); 13 | bluebird.promisifyAll(redis.Multi.prototype); 14 | var _mainDB = null; 15 | var _redisClient = null; 16 | var _jpushClient = null; 17 | var _realDB = null; 18 | const EMPTY = {}; 19 | rongcloudSDK.init(Config.Rong_Appkey, Config.Rong_Secret); 20 | let o = { 21 | knex: require('knex')({ client: 'mysql', connection: Config.mysqlConfig }), 22 | get mainDB() { 23 | if (!_mainDB) 24 | _mainDB = new Sequelize(Config.mysqlConfig.database, Config.mysqlConfig.user, Config.mysqlConfig.password, { 25 | dialect: "mysql", 26 | host: Config.mysqlConfig.host, 27 | port: Config.mysqlConfig.port, 28 | dialectOptions: { 29 | dateStrings: true, 30 | typeCast: Config.mysqlConfig.typeCast 31 | }, 32 | timezone: '+08:00', 33 | logging: (...arg) => console.log(new Date().format(), ...arg) 34 | }); 35 | return _mainDB 36 | }, 37 | get redisClient() { 38 | if (!_redisClient) 39 | _redisClient = redis.createClient(Config.redisConfig); 40 | return _redisClient 41 | }, 42 | get jpushClient() { 43 | if (!_jpushClient) 44 | _jpushClient = JPush.buildClient(Config.Jpush_Appkey, Config.Jpush_Secret) 45 | return _jpushClient 46 | }, 47 | get realDB() { 48 | return _realDB; 49 | }, 50 | async getRealDB() { 51 | if (!_realDB) 52 | _realDB = await mongodb.MongoClient.connect(Config.mongodbconn) 53 | return _realDB 54 | }, 55 | get rongcloud() { 56 | return rongcloudSDK; 57 | }, 58 | async marketIsOpen(market) { 59 | let marketIsOpen = await this.redisClient.getAsync("marketIsOpen") 60 | marketIsOpen = JSON.parse(marketIsOpen) 61 | return market ? marketIsOpen[market] : marketIsOpen 62 | }, 63 | async marketIsOpen2(market) { //延迟1分钟收盘 64 | let marketIsOpen = await this.redisClient.getAsync("marketIsOpen2") 65 | marketIsOpen = JSON.parse(marketIsOpen) 66 | return market ? marketIsOpen[market] : marketIsOpen 67 | }, 68 | async getLastPrice(sinaName) { 69 | let sp = await this.redisClient.hgetAsync("lastPrice", sinaName) 70 | if (sp) 71 | sp = JSON.parse("[" + sp + "]") 72 | else 73 | sp = (await getStockPrice(sinaName))[sinaName] 74 | let [, , , price, pre, chg] = sp 75 | if (!chg) sp[5] = pre ? (price - pre) * 100 / pre : 0 76 | return sp 77 | }, 78 | async insertMainDB(table, value, other, option) { 79 | let [result] = await this.mainDB.query(...sqlStr.insert2(table, value, other, option)) 80 | return result 81 | }, 82 | async updateMainDB(table, value, other, where = "", option) { 83 | let [result] = await this.mainDB.query(...sqlStr.update2(table, value, other, where, option)) 84 | return result 85 | }, 86 | selectMainDB(table, value, other, option) { 87 | return this.mainDB.query(...sqlStr.select2(table, value, other, option)) 88 | }, 89 | async selectMainDB0(table, value, other, option) { 90 | let [result = EMPTY] = await this.mainDB.query(...sqlStr.select2(table, value, other, option)) 91 | return result 92 | }, 93 | async deleteMainDB(table, value, other, option) { 94 | let [result] = await this.mainDB.query(...sqlStr.delete2(table, value, other, option)) 95 | return result 96 | }, 97 | callMainDB(name, ...replacements) { 98 | let arg = [] 99 | for (let i = 0; i < replacements.length;) { 100 | if (typeof replacements[i] == "string" && replacements[i][0] == "@") { 101 | arg.push(replacements[i]) 102 | replacements.splice(i, 1) 103 | } else { 104 | arg.push('?') 105 | i++ 106 | } 107 | } 108 | return this.mainDB.query(`CALL ${name}(${arg.join(",")})`, { replacements }) 109 | }, 110 | isEMPTY(value) { 111 | return value === EMPTY 112 | }, 113 | async transaction(action) { 114 | let transaction = await this.mainDB.transaction(); 115 | try { 116 | await action({ transaction }) 117 | await transaction.commit(); 118 | return 0 119 | } catch (ex) { 120 | await transaction.rollback() 121 | return ex 122 | } 123 | }, 124 | async transaction2(action) { 125 | let transaction = await this.mainDB.transaction(); 126 | try { 127 | action({ transaction }) 128 | await transaction.commit(); 129 | return 0 130 | } catch (ex) { 131 | await transaction.rollback() 132 | return ex 133 | } 134 | }, 135 | async sendJpushMessage(MemberCode, ...args) { 136 | let [result] = await this.knex("wf_im_jpush").select("JpushRegID").where({ MemberCode }).orderBy("JpushLastLoginTime", "desc").limit(1) 137 | if (result) { 138 | let { JpushRegID } = result 139 | this.jpushClient.push().setPlatform(JPush.ALL).setAudience(JPush.registration_id(JpushRegID)) 140 | .setOptions(null, null, null, Config.apns_production) 141 | .setMessage(...args) 142 | .send(async(err, res) => { 143 | if (err) { 144 | if (err instanceof JPush.APIConnectionError) { 145 | console.log(err.message) 146 | } else if (err instanceof JPush.APIRequestError) { 147 | console.log(err.message) 148 | } 149 | } else { 150 | console.log(res) 151 | } 152 | }) 153 | } 154 | }, 155 | async sendJpushNotify(MemberCode, title, msg, external) { 156 | let [result] = await this.knex("wf_im_jpush").select("JpushRegID").where({ MemberCode }).orderBy("JpushLastLoginTime", "desc").limit(1) 157 | if (result) { 158 | let { JpushRegID } = result 159 | this.jpushClient.push().setPlatform(JPush.ALL).setAudience(JPush.registration_id(JpushRegID)) 160 | .setOptions(null, null, null, Config.apns_production) 161 | .setNotification(title, JPush.ios(msg, 'sound', 0, false, external), JPush.android(msg, title, 1, external)) 162 | .send(async(err, res) => { 163 | if (err) { 164 | if (err instanceof JPush.APIConnectionError) { 165 | console.log(err.message) 166 | } else if (err instanceof JPush.APIRequestError) { 167 | console.log(err.message) 168 | } 169 | } else { 170 | console.log(res) 171 | } 172 | }) 173 | } 174 | }, 175 | async CreateParactice(memberCode, randNum) { 176 | 177 | let body = { 178 | wlpID: "DW", 179 | languageID: "zh_CN", 180 | firstName: "f" + memberCode, 181 | lastName: "l" + memberCode, 182 | emailAddress1: memberCode + "@wolfstreet.tv", 183 | username: memberCode + randNum, 184 | password: "p" + memberCode, 185 | transAmount: 10000 186 | } 187 | try { 188 | ({ userID: body.UserId } = await request({ 189 | uri: dwUrls.createPractice, 190 | method: "POST", 191 | body, 192 | json: true 193 | })) 194 | body.MemberCode = memberCode 195 | body.IsActivate = false 196 | body.username = memberCode + randNum 197 | body.tranAmount = 10000 198 | await this.mainDB.query("CALL PRC_WF_CLEAR_DW(:memberCode)", { replacements: { memberCode } }) 199 | let result = await this.insertMainDB("wf_drivewealth_practice_account", body, { PracticeId: null, CreateTime: "now()", transAmount: null }) 200 | this.sendJpushMessage(memberCode, '嘉维账号重置', '', '', { AlertType: "jpush111", UserId: body.userId, username: body.username, password: body.password }) 201 | return body 202 | } catch (ex) { 203 | return this.CreateParactice(memberCode, Math.floor(Math.random() * 1000 + 1)) 204 | } 205 | } 206 | } 207 | export default o -------------------------------------------------------------------------------- /common/sqlStr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 生成sql语句 3 | * @param value 要操作的对象 4 | * @param other 要覆盖的名称,键为字段名,值为要覆盖的sql参数名,值为空则删除操作对象中的属性名 5 | */ 6 | function getNames(value, other) { 7 | let names = Object.keys(value); 8 | let argNames = names.map(n => ':' + n); 9 | let others = [] 10 | if (other) { 11 | if (Array.isArray(other)) { 12 | others = other 13 | } else if (typeof other == "string") { 14 | others.push("(" + other + ")") 15 | } else 16 | for (let n in other) { 17 | let i = names.indexOf(n) 18 | if (other[n]) { 19 | if (i != -1) { 20 | argNames[i] = other[n] 21 | } else { 22 | names.push(n) 23 | argNames.push(other[n]) 24 | } 25 | } else { 26 | if (i != -1) { 27 | names.splice(i, 1) 28 | argNames.splice(i, 1) 29 | } 30 | } 31 | } 32 | } 33 | 34 | let equres = names.map((n, i) => n + "=" + argNames[i]).concat(others) 35 | names = names.join(",") 36 | argNames = argNames.join(",") 37 | return { names, argNames, equres } 38 | } 39 | export default { 40 | select2(table, value, other, option) { 41 | if (!value) value = {} 42 | let replacements = value 43 | if (!option) option = { replacements, type: "SELECT" } 44 | else Object.assign(option, { replacements, type: "SELECT" }) 45 | let { equres } = getNames(value, other) 46 | return [`select * from ${table} where ${equres.join(" and ")}`, option] 47 | }, 48 | delete2(table, value, other, option) { 49 | let replacements = value 50 | if (!option) option = { replacements } 51 | else Object.assign(option, { replacements }) 52 | let { equres } = getNames(value, other) 53 | return [`delete from ${table} where ${equres.join(" and ")}`, option] 54 | }, 55 | insert(table, value, other) { 56 | let { names, argNames } = getNames(value, other) 57 | return `insert into ${table}(${names}) values(${argNames}) ` 58 | }, 59 | insert2(table, value, other, option) { 60 | if (!option) option = { replacements: value } 61 | else Object.assign(option, { replacements: value }) 62 | return [this.insert(table, value, other), option] 63 | }, 64 | update(table, value, other, where = "") { 65 | let { equres } = getNames(value, other) 66 | return `update ${table} set ${equres.join(",")} ${where}` 67 | }, 68 | update2(table, value, other, where = "", option) { 69 | let replacements = value 70 | if (typeof where == 'object') { 71 | replacements = Object.assign(Object.assign({}, where), value) 72 | where = "where " + Object.keys(where).map(n => `${n}=:${n}`).join(" and ") 73 | } 74 | if (!option) option = { replacements } 75 | else Object.assign(option, { replacements }) 76 | return [this.update(table, value, other, where), option] 77 | } 78 | } -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | test: true, 3 | Jpush_Appkey: "2857872dca17b28541cde5f0", 4 | Jpush_Secret: "3a521e1803c5ce64fb226c74", 5 | Rong_Appkey: "82hegw5uhhjix", 6 | Rong_Secret: "zpKgSmbS28gUh", 7 | sina_realjs: "http://hq.sinajs.cn/list=", 8 | netease_realjs: "http://api.money.126.net/data/feed/", 9 | jssdk_appId: "wxa2598b6baa4cc41e", 10 | jssdk_appSecret: "e89d6dc75c6a7ea85e39c835da86af14", 11 | mysqlconn: "mysql://wftest:WfTestonlytest_20170222@rm-bp157512ad9bic209o.mysql.rds.aliyuncs.com:3306/wolfstreet_test", 12 | mysqlConfig: { 13 | host: "rm-bp157512ad9bic209o.mysql.rds.aliyuncs.com", 14 | port: "3306", 15 | user: "wftest", 16 | password: "WfTestonlytest_20170222", 17 | database: "wolfstreet_test", 18 | typeCast(field, next) { 19 | if (field.type === "BIT" && field.length === 1) { 20 | var bit = field.string(); 21 | return (bit === null) ? null : (bit.charCodeAt(0) === 1); 22 | } 23 | return next(); 24 | } 25 | }, 26 | // mysqlconn: "mysql://program_admin:!P%402%23dh%254%5e5Y%26g*4@rm-bp157512ad9bic209o.mysql.rds.aliyuncs.com:3306/wolfstreet", 27 | // mysqlConfig: { 28 | // host: "rm-bp157512ad9bic209o.mysql.rds.aliyuncs.com", 29 | // port: "3306", 30 | // user: "program_admin", 31 | // password: "!P@2#dh%4^5Y&g*4", 32 | // database: "wolfstreet", 33 | // typeCast(field, next) { 34 | // if (field.type === "BIT" && field.length === 1) { 35 | // var bit = field.string(); 36 | // return (bit === null) ? null : (bit.charCodeAt(0) === 1); 37 | // } 38 | // return next(); 39 | // } 40 | // }, 41 | amqpConn: "amqp://dexter:Wolfstreet%2A%2306%23@testmq.wolfstreet.tv:10001/test", 42 | redisConfig: { host: "testapi.wolfstreet.tv", port: 7788, password: "`1qaz2wsx3EDC", db: 1 }, 43 | mongodbconn: "mongodb://wftest:%23W%40f!1189747acd@106.14.155.223:22222/wolfstreet_test", 44 | //mongodbconn: "mongodb://wftest:%23W%40f!1189747acd@118.178.88.67:22222/wolfstreet_test", 45 | // redisConfig: { host: "api.wolfstreet.tv", port: 7788, password: "`1qaz2wsx3EDC" }, 46 | // amqpConn: "amqp://dexter:Wolfstreet%2A%2306%23@mq.wolfstreet.tv:10001", 47 | // mysqlconn: "mysql://program_admin:!P%402%23dh%254%5e5Y%26g*4@rm-bp157512ad9bic209o.mysql.rds.aliyuncs.com:3306/wolfstreet", 48 | lastPriceIndexMap: { hk: 6, sz: 3, sh: 3, gb_: 1 }, 49 | pricesIndexMap: { hk: [2, 4, 5, 6, 3], sz: [1, 4, 5, 3, 2], sh: [1, 4, 5, 3, 2], gb_: [5, 6, 7, 1, 26] }, //开,高,低,新,昨收 50 | //chgFunc: { gb_: x => x[2], hk: x => x[8], sh: x => (x[3] - x[2]) / x[2], sz: x => (x[3] - x[2]) / x[2] }, 51 | sina_qmap: { us: "gb_", hk: "hk", sh: "sh", sz: "sz" }, 52 | getQueryName({ SecuritiesType, SmallType, SecuritiesNo }) { 53 | return this.sina_qmap[SmallType || SecuritiesType] + SecuritiesNo.toLowerCase().replace(".", "$") 54 | }, 55 | stockPatten: /(gb_|hk|sh|sz).+/, 56 | jpushType: "jpush108", 57 | jpushType_paperTrade: "jpush110", 58 | jpushType_competition: "jpush112", 59 | jpushType_sendLetter: "jpush113", 60 | ajaxOrigin: "*", //跨域访问 61 | apns_production: false, //ios JPUSH配置 62 | picBaseURL: "http://apitest.wolfstreet.tv", 63 | adminHostURL: "http://admin.wolfstreet.tv", 64 | shareHostURL: "http://sharetest.wolfstreet.tv", 65 | defaultHeadImage: "/UploadFile/Default/default_headerimage.png", 66 | driveWealthHost: { 67 | apiHost: "https://api.drivewealth.net", 68 | appsHost: "https://apps.drivewealth.com", 69 | reportsHost: "https://reports.drivewealth.net" 70 | }, 71 | uploadFilePath: "/", 72 | practiceInitFun: 10000, //模拟交易起始资金 73 | randmax: 1, //每日增长系数 74 | getDWData: true, //是否获取嘉维数据 75 | calDWData: true, //是否监听嘉维数据获取情况,并计算排名 76 | ptTest: true //模拟交易测试,忽略是否开盘状态 77 | // mysqlconn: "mysql://wfadmin:123456@192.168.2.205:3306/wolfstreet_test", 78 | } 79 | console.log(process.env.NODE_ENV) 80 | if (process.env.NODE_ENV === "production") { 81 | Object.assign(config, require('./pconfig.js')) 82 | } 83 | export default config; 84 | /**过滤属性(只保留某些属性) */ 85 | Object.filterProperties = function(obj, ...args) { 86 | for (let n of Object.keys(obj)) { 87 | if (args.indexOf(n) == -1) delete obj[n] 88 | } 89 | return obj 90 | }; 91 | /**批量删除对象属性 */ 92 | Object.deleteProperties = function(obj, ...args) { 93 | for (let n of args) { 94 | if (obj.hasOwnProperty(n)) { 95 | delete obj[n] 96 | } 97 | } 98 | return obj 99 | }; 100 | Array.prototype.remove = function(...item) { 101 | let _this = this; 102 | item.forEach(i => { 103 | let index = _this.indexOf(i) 104 | if (index != -1) 105 | _this.splice(index, 1) 106 | }) 107 | } 108 | Array.prototype.contain = function(item) { 109 | return this.indexOf(item) != -1 110 | } 111 | Array.prototype.clear = function() { 112 | this.splice(0, this.length) 113 | } 114 | Array.prototype.__defineGetter__('last', function() { 115 | return this.length ? this[this.length - 1] : null 116 | }) 117 | Array.prototype.__defineSetter__('last', function(value) { 118 | if (this.length) this[this.length - 1] = value 119 | }) 120 | Date.prototype.format = function(fmt = "yyyy-MM-dd hh:mm:ss") { //author: meizz 121 | var o = { 122 | "M+": this.getMonth() + 1, //月份 123 | "d+": this.getDate(), //日 124 | "h+": this.getHours(), //小时 125 | "m+": this.getMinutes(), //分 126 | "s+": this.getSeconds(), //秒 127 | "q+": Math.floor((this.getMonth() + 3) / 3), //季度 128 | "S": this.getMilliseconds() //毫秒 129 | }; 130 | if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); 131 | for (var k in o) 132 | if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 133 | return fmt; 134 | }; 135 | /* 136 | 沪深股示例: 137 | 浦发银行(名称),16.520(今开),16.480(昨收),16.510(最新),16.550(最高),16.500(最低),16.510(买入),16.520(卖出),1072269(成交量),17717924.000(成交额),78694(买一手数),16.510(买一价),129637(买二手数),16.500(买二价),419400(买三手数),16.490(买三价),326300(买四手数),16.480(买四价),188800(买五手数),16.470(买五价),151545(卖一手数),16.520(卖一价),69357(卖二手数),16.530(卖二价),193950(卖三手数),16.540(卖三价),242522(卖四手数),16.550(卖四价),113570(卖五手数),16.560(卖五价),2016-09-29(日期),10:03:33,00(时间) 138 | 139 | 港股示例: 140 | CHEUNG KONG,长和(名称),98.000(今开),98.000(昨收),98.400(最高价),95.050(最低价),95.200(最新价),-2.800(涨跌额),-2.857(涨跌幅),95.200(买入),95.250(卖出),1015890698(成交额),10537356(成交量),11.615(市盈率),2.679,109.500(52周最高),80.600(52周最低),2016/10/11(日期),14:15(时间) 141 | 142 | 美股示例: 143 | 苹果(名称),116.05(最新价),1.74(涨跌幅),2016-10-11 08:19:28(时间),1.99(涨跌额),115.02(今开),116.75(最高价),114.72(最低价),123.82(52周最高),89.47(52周最低),36235956(成交量),28211541(成交额),625509500000(市值),8.58(每股收益),13.53(市盈率),0.00,1.55(贝塔系数),2.18(股息),1.90(收益率),5390000000(股本),58.00,116.36("盘后"),0.27("盘后涨跌幅"),0.31("盘后涨跌额"),Oct 10 08:00PM EDT("盘后日期"),Oct 10 04:00PM EDT("日期"),114.06(昨收),642204.00("盘后成交量") 144 | */ 145 | // var Notify = sequelize.define('wf_securities_remind', { 146 | // RemindId: Sequelize.BIGINT, 147 | // MemberCode: Sequelize.STRING, 148 | // SmallType: Sequelize.STRING, 149 | // SecuritiesNo: Sequelize.STRING, 150 | // LowerLimit: Sequelize.DECIMAL, 151 | // IsOpenLower: Sequelize.BOOLEAN, 152 | // UpperLimit: Sequelize.DECIMAL, 153 | // IsOpenUpper: Sequelize.BOOLEAN, 154 | // RiseFall: Sequelize.DECIMAL, 155 | // IsOpenRiseFall: Sequelize.BOOLEAN, 156 | // CreateTime: Sequelize.DATE 157 | // }) 158 | // var WfJPush = sequelize.define('wf_im_jpush', { 159 | // JpushID: Sequelize.BIGINT, 160 | // MemberCode: Sequelize.STRING, 161 | // JpushRegID: Sequelize.STRING, 162 | // JpushIMEI: Sequelize.STRING, 163 | // JpushDeviceID: Sequelize.STRING, 164 | // JpushVersion: Sequelize.STRING, 165 | // JpushPlatform: Sequelize.STRING, 166 | // JpushLastLoginTime: Sequelize.DATE, 167 | // CreateTime: Sequelize.DATE 168 | // }) -------------------------------------------------------------------------------- /getDWData.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./getDWData/index.js'); -------------------------------------------------------------------------------- /getDWData/index.js: -------------------------------------------------------------------------------- 1 | import redis from 'redis' 2 | import request from 'request-promise' 3 | import Config from '../config' 4 | import singleton from '../common/singleton' 5 | import { dwUrls } from '../common/driveWealth' 6 | import amqp from 'amqplib' 7 | const { mainDB, redisClient } = singleton 8 | var getDataTimeout1 = 10000 9 | var calculateTimeout = 10000 10 | var mdbData = new Map() 11 | const ranka = "wf_ussecurities_rank_a" 12 | const rankb = "wf_ussecurities_rank_b" 13 | var mqChannel = null 14 | var ignoreMarket = true 15 | var ignoreMarket2 = true 16 | 17 | function startGetData() { 18 | setTimeout(async() => { 19 | if (await singleton.marketIsOpen2('us') || ignoreMarket) { 20 | ignoreMarket = false 21 | //console.log(new Date() + "--------getDataTimeout1=" + getDataTimeout1 + "---------------") 22 | //console.log(new Date() + "--------getDWData1 begin---------------") 23 | await writetoredis() 24 | mqChannel.sendToQueue("calcuateUSStockData", new Buffer(JSON.stringify({ cmd: "getData" }))) 25 | //console.log(new Date() + "--------getDWData1 end---------------") 26 | } else { 27 | //console.log(new Date() + "--------getDataTimeout1=" + getDataTimeout1 + "---------------") 28 | //console.log(new Date() + "--------US STOCK NOT OPEN---------------") 29 | getDataTimeout1 = 10000 30 | } 31 | startGetData() 32 | }, getDataTimeout1); 33 | 34 | } 35 | 36 | function startcalculateData() { 37 | setTimeout(async() => { 38 | if (await singleton.marketIsOpen2('us') || ignoreMarket2) { 39 | ignoreMarket2 = false 40 | //console.log(new Date() + "--------calculateTimeout=" + calculateTimeout + "---------------") 41 | //console.log(new Date() + "--------calculateData begin---------------") 42 | await calculateData() 43 | //console.log(new Date() + "--------calculateData end---------------") 44 | } else { 45 | //console.log(new Date() + "--------calculateTimeout=" + calculateTimeout + "---------------") 46 | //console.log(new Date() + "--------US STOCK NOT OPEN---------------") 47 | calculateTimeout = 10000 48 | } 49 | startcalculateData() 50 | }, calculateTimeout); 51 | } 52 | 53 | (async() => { 54 | let connection = await amqp.connect(Config.amqpConn) 55 | mqChannel = await connection.createChannel() 56 | if (Config.calDWData) { 57 | let ok = await mqChannel.assertQueue('calcuateUSStockData') 58 | mqChannel.consume('calcuateUSStockData', msg => { 59 | console.log(new Date() + "getmqinfo and then start to calculateData") 60 | calculateData() 61 | console.log(new Date() + "calculateData end ") 62 | mqChannel.ack(msg) 63 | }) 64 | } 65 | if (Config.getDWData) { 66 | startGetData() 67 | } 68 | })() 69 | 70 | 71 | 72 | /** 73 | * 写入美股最新价格,计算涨跌幅 74 | */ 75 | async function calculateData() { 76 | console.time('calculateData cost time:'); 77 | addRankStock(await mainDB.query("select * from wf_securities_trade where Remark='DW'", { type: "SELECT" })) 78 | let key = await redisClient.getAsync("currentNewestUSPriceKey") 79 | let result = await redisClient.hgetallAsync(key); 80 | if (result) { 81 | await Promise.all(Object.keys(result).map(k => { 82 | return redisClient.hgetAsync("lastPrice", Config.getQueryName({ SecuritiesType: "us", SecuritiesNo: k })).then(a => { 83 | if (a) { 84 | a = JSON.parse("[" + a + "]") 85 | a[3] = result[k] 86 | let [, , , price, pre, chg] = a //开,高,低,新,昨收 87 | a[5] = pre ? (price - pre) * 100 / pre : 0 88 | redisClient.hset("usDWLastPrice", k, a.join(",")) 89 | if (mdbData.has(k)) { 90 | let info = mdbData.get(k); 91 | info.RiseFallRange = a[5]; 92 | info.NewPrice = price; 93 | info.hasNewPrice = 1 94 | } 95 | } 96 | }) 97 | })) 98 | 99 | let currentRankTable = await redisClient.getAsync("currentUSSRT") 100 | if (!currentRankTable) { 101 | currentRankTable = ranka 102 | redisClient.set("currentUSSRT", rankb) 103 | } else { 104 | await redisClient.setAsync("currentUSSRT", currentRankTable == ranka ? rankb : ranka) 105 | } 106 | let collection = (await singleton.getRealDB()).collection(currentRankTable); 107 | try { await collection.drop() } catch (ex) {} 108 | collection.insertMany(Array.from(mdbData.values())) 109 | console.timeEnd('calculateData cost time:'); 110 | } 111 | } 112 | 113 | /**获取查询股票的代码sina */ 114 | function getQueryName({ QueryUrlCode, SecuritiesNo }) { 115 | return SecuritiesNo.toUpperCase().replace(".", "$") 116 | } 117 | 118 | /** 119 | * 将数据插出map对象中,并以stockname为key值 120 | * @param {key值} ccss 121 | */ 122 | function addRankStock(ccss) { 123 | for (let ccs of ccss) { 124 | mdbData.set(getQueryName(ccs), ccs) 125 | } 126 | } 127 | 128 | /** 129 | * 将嘉维最新股票数据写入Redis 130 | */ 131 | async function writetoredis() { 132 | let start = new Date() 133 | console.time('get dwdata cost time: '); 134 | 135 | let result = await getDWLastPrice() 136 | if (result.length) { 137 | result.map(({ symbol, lastTrade }) => { 138 | if (symbol != "" && symbol != undefined && lastTrade != "" && lastTrade != undefined) redisClient.hmset("newestUSPriceA", symbol, lastTrade) 139 | }) 140 | } else { 141 | writetoredis() 142 | return 143 | } 144 | 145 | 146 | await redisClient.setAsync("currentNewestUSPriceKey", "newestUSPriceA"); 147 | let end = new Date() 148 | console.timeEnd('get dwdata cost time: '); 149 | getDataTimeout1 = 2000 150 | } 151 | 152 | /** 153 | * 从嘉维获取sessionkey 154 | */ 155 | async function getSessionKey() { 156 | let sessionKey = await redisClient.getAsync("sessionForGetDWDataA") 157 | if (!sessionKey) { 158 | try { 159 | ({ sessionKey } = await request({ 160 | uri: dwUrls.createSession, 161 | //uri: "http://api.drivewealth.io/v1/userSessions", 162 | method: "POST", 163 | body: { 164 | "appTypeID": "2000", 165 | "appVersion": "0.1", 166 | //"username": "36300888", 167 | "username": "16459847", 168 | //"emailAddress": "36300888@wolfstreet.tv", 169 | "emailAddress": "16459847@wolfstreet.tv", 170 | "ipAddress": "1.1.1.1", 171 | "languageID": "zh_CN", 172 | "osVersion": "iOS 9.1", 173 | "osType": "iOS", 174 | "scrRes": "1920x1080", 175 | //"password": "p36300888" 176 | "password": "p16459847" 177 | }, 178 | json: true 179 | })) 180 | await redisClient.setAsync("sessionForGetDWDataA", sessionKey); 181 | } catch (ex) { 182 | return getSessionKey() 183 | } 184 | } 185 | return sessionKey 186 | } 187 | 188 | /** 189 | * 从嘉维获取最新股票价格 190 | */ 191 | async function getDWLastPrice() { 192 | let sessionKey = await getSessionKey() 193 | let result = {} 194 | try { 195 | result = await request({ 196 | headers: { 'x-mysolomeo-session-key': sessionKey }, 197 | method: "GET", 198 | //encoding: null, 199 | uri: "http://api.drivewealth.net/v1/instruments", //所有股票 200 | //uri: "http://api.drivewealth.net/v1/instruments?symbols=" + postdata,//单个股票 201 | json: true, 202 | timeout: 5000 203 | }) 204 | } catch (ex) { 205 | console.log(ex) 206 | if (ex.statusCode == 401) { 207 | await redisClient.delAsync("sessionForGetDWDataA"); 208 | return getDWLastPrice() 209 | } else { 210 | return getDWLastPrice() 211 | } 212 | } 213 | return result 214 | } -------------------------------------------------------------------------------- /getDWData2.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./getDWData2/index.js'); -------------------------------------------------------------------------------- /getDWData2/index.js: -------------------------------------------------------------------------------- 1 | import redis from 'redis' 2 | import request from 'request-promise' 3 | import Config from '../config' 4 | import singleton from '../common/singleton' 5 | import { dwUrls } from '../common/driveWealth' 6 | import amqp from 'amqplib' 7 | const { mainDB, redisClient } = singleton 8 | var getDataTimeout1 = 10000 9 | var getDataTimeout2 = 15000 10 | var calculateTimeout = 10000 11 | var mdbData = new Map() 12 | const ranka = "wf_ussecurities_rank_a" 13 | const rankb = "wf_ussecurities_rank_b" 14 | 15 | var mqChannel = null 16 | 17 | 18 | function startGetData1() { 19 | setTimeout(async() => { 20 | let marketIsOpen = await singleton.marketIsOpen2() 21 | if (marketIsOpen.us) { 22 | console.log(new Date() + "--------getDataTimeout2=" + getDataTimeout2 + "---------------") 23 | console.log(new Date() + "--------getDWData2 begin---------------") 24 | await writetoredis2() 25 | mqChannel.sendToQueue("calcuateUSStockData", new Buffer(JSON.stringify({ cmd: "getData" }))) 26 | console.log(new Date() + "--------getDWData2 end---------------") 27 | } else { 28 | console.log(new Date() + "--------getDataTimeout2=" + getDataTimeout2 + "---------------") 29 | console.log(new Date() + "--------US STOCK NOT OPEN---------------") 30 | getDataTimeout2 = 15000 31 | } 32 | startGetData1() 33 | }, getDataTimeout2); 34 | 35 | } 36 | 37 | 38 | /*if (Config.calDWData) { 39 | //if (true) { 40 | (async() => { 41 | var amqpConnection = await amqp.connect(Config.amqpConn) 42 | let mqChannel = await amqpConnection.createChannel() 43 | let ok = await mqChannel.assertQueue('calcuateUSStockData') 44 | mqChannel.consume('calcuateUSStockData', msg => { 45 | console.log(new Date() + "getmqinfo and then start to calculateData") 46 | calculateData() 47 | console.log(new Date() + "calculateData end ") 48 | mqChannel.ack(msg) 49 | }) 50 | })() 51 | }*/ 52 | 53 | /** 54 | * 写入美股最新价格,计算涨跌幅 55 | */ 56 | async function calculateData() { 57 | console.time('calculateData cost time:'); 58 | addRankStock(await mainDB.query("select * from wf_securities_trade where Remark='DW'", { type: "SELECT" })) 59 | let key = await redisClient.getAsync("currentNewestUSPriceKey") 60 | let result = await redisClient.hgetallAsync(key); 61 | if (result) { 62 | await Promise.all(Object.keys(result).map(k => { 63 | return redisClient.hgetAsync("lastPrice", Config.getQueryName({ SecuritiesType: "us", SecuritiesNo: k })).then(a => { 64 | if (a) { 65 | a = JSON.parse("[" + a + "]") 66 | a[3] = result[k] 67 | let [, , , price, pre, chg] = a //开,高,低,新,昨收 68 | a[5] = pre ? (price - pre) * 100 / pre : 0 69 | redisClient.hset("usDWLastPrice", k, a.join(",")) 70 | if (mdbData.has(k)) { 71 | let info = mdbData.get(k); 72 | info.RiseFallRange = a[5]; 73 | info.NewPrice = price; 74 | info.hasNewPrice = 1 75 | } 76 | } 77 | }) 78 | })) 79 | 80 | let currentRankTable = await redisClient.getAsync("currentUSSRT") 81 | if (!currentRankTable) { 82 | currentRankTable = ranka 83 | redisClient.set("currentUSSRT", rankb) 84 | } else { 85 | await redisClient.setAsync("currentUSSRT", currentRankTable == ranka ? rankb : ranka) 86 | } 87 | let collection = (await singleton.getRealDB()).collection(currentRankTable); 88 | try { await collection.drop() } catch (ex) {} 89 | collection.insertMany(Array.from(mdbData.values())) 90 | console.timeEnd('calculateData cost time:'); 91 | } 92 | } 93 | 94 | 95 | 96 | 97 | /**获取查询股票的代码sina */ 98 | function getQueryName({ QueryUrlCode, SecuritiesNo }) { 99 | return SecuritiesNo.toUpperCase().replace(".", "$") 100 | } 101 | 102 | /** 103 | * 将数据插出map对象中,并以stockname为key值 104 | * @param {key值} ccss 105 | */ 106 | function addRankStock(ccss) { 107 | for (let ccs of ccss) { 108 | mdbData.set(getQueryName(ccs), ccs) 109 | } 110 | } 111 | 112 | /** 113 | * 将嘉维最新股票数据写入Redis 114 | */ 115 | async function writetoredis2() { 116 | let start = new Date() 117 | console.time('get dwdata cost time: '); 118 | 119 | let result = await getDWLastPrice2() 120 | result.map(({ symbol, lastTrade }) => { 121 | if (symbol != "" && symbol != undefined && lastTrade != "" && lastTrade != undefined) redisClient.hmset("newestUSPriceB", symbol, lastTrade) 122 | }) 123 | await redisClient.setAsync("currentNewestUSPriceKey", "newestUSPriceB"); 124 | let end = new Date() 125 | console.timeEnd('get dwdata cost time: '); 126 | getDataTimeout1 = 2000 127 | getDataTimeout2 = 2000 128 | } 129 | 130 | /** 131 | * 从嘉维获取sessionkey 132 | */ 133 | async function getSessionKey2() { 134 | let sessionKey = await redisClient.getAsync("sessionForGetDWDataB") 135 | if (!sessionKey) { 136 | try { 137 | ({ sessionKey } = await request({ 138 | uri: dwUrls.createSession, 139 | //uri: "http://api.drivewealth.io/v1/userSessions", 140 | method: "POST", 141 | body: { 142 | "appTypeID": "2000", 143 | "appVersion": "0.1", 144 | "username": "36522170", 145 | //"username": "12695763282", 146 | "emailAddress": "36522170@wolfstreet.tv", 147 | //"emailAddress": "12695763@wolfstreet.tv", 148 | "ipAddress": "1.1.1.1", 149 | "languageID": "zh_CN", 150 | "osVersion": "iOS 9.1", 151 | "osType": "iOS", 152 | "scrRes": "1920x1080", 153 | "password": "p36522170" 154 | //"password": "p12695763" 155 | }, 156 | json: true 157 | })) 158 | await redisClient.setAsync("sessionForGetDWDataB", sessionKey); 159 | } catch (ex) { 160 | return getSessionKey2() 161 | } 162 | } 163 | return sessionKey 164 | } 165 | 166 | /** 167 | * 从嘉维获取最新股票价格 168 | */ 169 | async function getDWLastPrice2() { 170 | let sessionKey = await getSessionKey2() 171 | let result = {} 172 | try { 173 | result = await request({ 174 | headers: { 'x-mysolomeo-session-key': sessionKey }, 175 | method: "GET", 176 | //encoding: null, 177 | uri: "http://api.drivewealth.net/v1/instruments", //所有股票 178 | //uri: "http://api.drivewealth.net/v1/instruments?symbols=" + postdata,//单个股票 179 | json: true, 180 | timeout: 2000 181 | }) 182 | } catch (ex) { 183 | console.log(ex) 184 | if (ex.statusCode == 401) { 185 | await redisClient.delAsync("sessionForGetDWDataB"); 186 | return getDWLastPrice2() 187 | } else { 188 | return getDWLastPrice2() 189 | } 190 | } 191 | return result 192 | } 193 | async function start() { 194 | let connection = await amqp.connect(Config.amqpConn) 195 | mqChannel = await connection.createChannel() 196 | startGetData1() 197 | } 198 | if (Config.getDWData) { 199 | start() 200 | //startGetData1() 201 | } 202 | //startGetData1() 203 | //startcalculateData() -------------------------------------------------------------------------------- /getShSzHkRank/index.js: -------------------------------------------------------------------------------- 1 | import redis from 'redis' 2 | import request from 'request-promise' 3 | import Config from '../config' 4 | import singleton from '../common/singleton' 5 | import { dwUrls } from '../common/driveWealth' 6 | import amqp from 'amqplib' 7 | const { mainDB, redisClient } = singleton 8 | var getShSzTimeout = 10000 9 | var getHkTimeout = 10000 10 | var HStockData = new Map() 11 | var ShSzStockData = new Map() 12 | const shszranka = "wf_shszsecurities_rank_a" 13 | const shszrankb = "wf_shszsecurities_rank_b" 14 | const hkranka = "wf_hksecurities_rank_a" 15 | const hkrankb = "wf_hksecurities_rank_b" -------------------------------------------------------------------------------- /getSinaData.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./getSinaData/index.js'); -------------------------------------------------------------------------------- /getSinaData/getPrice.js: -------------------------------------------------------------------------------- 1 | import request from 'request-promise' 2 | import Config from '../config' 3 | import Iconv from 'iconv-lite' 4 | 5 | export default async function(...stocks) { 6 | function getStockPrice(stockName, x) { 7 | let q = Config.stockPatten.exec(stockName)[1]; 8 | let price = Config.pricesIndexMap[q].map(y => { 9 | let p = Number(x[y]) 10 | if (Number.isNaN(p)) p = 0; 11 | return p 12 | }); 13 | price[5] = price[4] ? (price[3] - price[4]) * 100 / price[4] : 0; 14 | return price; 15 | } 16 | let stocks_name = stocks.join(",") 17 | let result = await request({ encoding: null, uri: Config.sina_realjs + stocks_name }) 18 | let rawData = Iconv.decode(result, 'gb2312') 19 | result = {} 20 | eval(rawData + 'stocks.forEach(stockName=>result[stockName]=getStockPrice(stockName,eval("hq_str_" + stockName).split(",")))') 21 | return result 22 | } -------------------------------------------------------------------------------- /getSinaData/index.js: -------------------------------------------------------------------------------- 1 | import redis from 'redis' 2 | import request from 'request-promise' 3 | import Config from '../config' 4 | import amqp from 'amqplib' 5 | import Iconv from 'iconv-lite' 6 | import StockRef from './stocksRef' 7 | import sqlstr from '../common/sqlStr' 8 | import stockRank from './stockRank' 9 | import singleton from '../common/singleton' 10 | import { dwUrls } from '../common/driveWealth' 11 | import moment from 'moment-timezone' 12 | const { mainDB, redisClient } = singleton 13 | var ignoreMarket = true 14 | var stockRef = new StockRef() 15 | var listenerSymbol = new Map() //订阅者关注的股票 16 | 17 | /**rabbitmq 通讯 */ 18 | async function startMQ() { 19 | var amqpConnection = await amqp.connect(Config.amqpConn) 20 | let channel = await amqpConnection.createChannel() 21 | await channel.assertExchange("broadcast", "fanout") 22 | //let ok = await channel.assertQueue('sinaData') 23 | let ok = await channel.assertQueue("getSinaData") 24 | channel.consume('getSinaData', async msg => { 25 | var { symbols, listener, type } = JSON.parse(msg.content.toString()) 26 | if (!symbols) symbols = [] 27 | if (!listenerSymbol.has(listener)) listenerSymbol.set(listener, []) 28 | let oldSymbols = listenerSymbol.get(listener) 29 | let needAdd = [] //需要增加的股票 30 | let needRemove = [] //需要删除的股票 31 | switch (type) { 32 | case "reset": //重置该订阅者所有股票 33 | console.log(listener, type) 34 | if (oldSymbols) { 35 | needRemove = oldSymbols.concat() //复制一份 36 | for (let s of symbols) { //查找已有的和没有的 37 | if (needRemove.contain(s)) { //已经存在 38 | needRemove.remove(s) //不进入移除列表 39 | } else { 40 | needAdd.push(s) 41 | } 42 | } 43 | } else { 44 | needAdd = symbols 45 | } 46 | listenerSymbol.set(listener, symbols) 47 | break 48 | case "add": 49 | while (symbols.length) { 50 | let symbol = symbols.pop() 51 | if (oldSymbols.contain(symbol)) { 52 | continue 53 | } else { 54 | needAdd.push(symbol) 55 | oldSymbols.push(symbol) 56 | } 57 | } 58 | break 59 | case "remove": 60 | while (symbols.length) { 61 | let symbol = symbols.pop() 62 | if (oldSymbols.contain(symbol)) { 63 | needRemove.remove(symbol) 64 | oldSymbols.remove(symbol) 65 | } else { 66 | continue 67 | } 68 | } 69 | break 70 | case "ignoreMarket": 71 | ignoreMarket = true 72 | break 73 | } 74 | if (needRemove.length) { 75 | stockRef.removeSymbols(needRemove) 76 | redisClient.hdel("lastPrice", ...needRemove) 77 | } 78 | if (needAdd.length) { 79 | stockRef.addSymbols(needAdd) 80 | } 81 | channel.ack(msg) 82 | }) 83 | redisClient.del("lastPrice") 84 | //ok = await channel.bindQueue('sinaData', 'broadcast', 'fanout') 85 | console.log("广播restart:", channel.publish('broadcast', 'sinaData', new Buffer("restart"))) 86 | } 87 | 88 | var intervalId; 89 | var pageSize = 1000; 90 | 91 | function start() { 92 | intervalId = setInterval(async() => { 93 | let stocks = [] 94 | let updateRank = true 95 | //筛选出当前在开盘的股票 96 | if (ignoreMarket) { 97 | ignoreMarket = false 98 | stocks = stockRef.array 99 | } else { 100 | let marketIsOpen = await singleton.marketIsOpen2() 101 | for (var market in marketIsOpen) { 102 | if (marketIsOpen[market]) 103 | stocks.push(...stockRef[market]) 104 | } 105 | updateRank = marketIsOpen.us //只对美股进行计算涨跌幅排行榜 106 | } 107 | let l = stocks.length; 108 | if (l && redisClient.connected) { 109 | let i = 0 110 | let stocks_name = "" 111 | let currentStocks = [] 112 | while (l) { 113 | if (l > pageSize) { 114 | currentStocks = stocks.slice(i, i + pageSize) 115 | l -= pageSize 116 | i += pageSize 117 | } else { 118 | currentStocks = stocks.slice(i, i + l) 119 | l = 0 120 | } 121 | stocks_name = currentStocks.join(",") 122 | let rawData = Iconv.decode(await request({ encoding: null, uri: Config.sina_realjs + stocks_name }), 'gb2312') 123 | 124 | function getStockPrice(stockName, x) { 125 | let q = Config.stockPatten.exec(stockName)[1]; 126 | let price = Config.pricesIndexMap[q].map(y => { 127 | let p = Number(x[y]) 128 | if (Number.isNaN(p)) p = 0; 129 | return p 130 | }); 131 | price[5] = price[4] ? (price[3] - price[4]) * 100 / price[4] : 0; 132 | redisClient.hset("lastPrice", stockName, price.join(",")); 133 | if (updateRank) stockRank.updatePrice(stockName, ...price) 134 | } 135 | eval(rawData + 'currentStocks.forEach(stockName=>getStockPrice(stockName,eval("hq_str_" + stockName).split(",")))') 136 | if (updateRank) stockRank.insertRank() 137 | } 138 | } 139 | }, 5000) 140 | } 141 | 142 | function startGetDWLastPrice() { 143 | setInterval(async() => { 144 | let marketIsOpen = await singleton.marketIsOpen2() 145 | if (marketIsOpen.us) { 146 | 147 | } 148 | }) 149 | } 150 | async function getsession() { 151 | let { sessionKey, accounts } = await request({ 152 | uri: dwUrls.createSession, 153 | method: "POST", 154 | body: { 155 | "appTypeID": "2000", 156 | "appVersion": "0.1", 157 | "username": "16459847", 158 | "emailAddress": "16459847@wolfstreet.tv", 159 | "ipAddress": "1.1.1.1", 160 | "languageID": "zh_CN", 161 | "osVersion": "iOS 9.1", 162 | "osType": "iOS", 163 | "scrRes": "1920x1080", 164 | "password": "p16459847" 165 | }, 166 | json: true 167 | }) 168 | console.log(sessionKey) 169 | return sessionKey 170 | } 171 | 172 | function stop() { 173 | clearInterval(intervalId) 174 | } 175 | stockRank.init(stockRef) 176 | startMQ() 177 | start() -------------------------------------------------------------------------------- /getSinaData/stockRank.js: -------------------------------------------------------------------------------- 1 | import singleton from '../common/singleton' 2 | const { mainDB, redisClient } = singleton 3 | var stockInfo = new Map() 4 | const ranka = "wf_securities_rank_a" 5 | const rankb = "wf_securities_rank_b" 6 | /**获取查询股票的代码sina */ 7 | function getQueryName({ QueryUrlCode, SecuritiesNo }) { 8 | return QueryUrlCode + SecuritiesNo.toLowerCase().replace(".", "$") 9 | } 10 | 11 | export default { 12 | async init(stockRef) { 13 | function addRankStock(ccss) { 14 | stockRef.addSymbols(ccss.map(ccs => getQueryName(ccs))) 15 | for (let ccs of ccss) { 16 | //css.SecuritiesName = css.SecuritiesName.replace(/'/g, "’"); 17 | stockInfo.set(getQueryName(ccs), ccs) 18 | } 19 | } 20 | // //获取中概股 21 | // let [ccss] = await mainDB.query("select * from wf_securities_trade where ShowType='CCS' and Remark='DW'") 22 | // addRankStock(ccss) 23 | // //获取明星股 24 | // let [gss] = await mainDB.query("select * from wf_securities_trade where ShowType='GS' and Remark='DW'") 25 | // addRankStock(gss) 26 | // /**获取ETF */ 27 | // let [etf] = await mainDB.query("select * from wf_securities_trade where ShowType='ETF' and Remark='DW'") 28 | // addRankStock(etf) 29 | addRankStock(await mainDB.query("select * from wf_securities_trade where Remark='DW'", { type: "SELECT" })) 30 | }, 31 | updatePrice(stockName, Open, High, Low, LastPrice, Pre, RiseFallRange) { 32 | if (stockInfo.has(stockName)) { 33 | let info = stockInfo.get(stockName); 34 | info.RiseFallRange = RiseFallRange; 35 | info.NewPrice = LastPrice; 36 | } 37 | }, 38 | async insertRank() { 39 | //获取和设置当前使用的表 40 | let currentRankTable = await redisClient.getAsync("currentSRT") 41 | if (!currentRankTable) { 42 | currentRankTable = ranka 43 | redisClient.set("currentSRT", rankb) 44 | } else { 45 | await redisClient.setAsync("currentSRT", currentRankTable == ranka ? rankb : ranka) 46 | } 47 | let collection = (await singleton.getRealDB()).collection(currentRankTable); 48 | try { await collection.drop() } catch (ex) {} 49 | await collection.insertMany(Array.from(stockInfo.values())) 50 | return collection.createIndex("RiseFallRange") 51 | } 52 | } -------------------------------------------------------------------------------- /getSinaData/stocksRef.js: -------------------------------------------------------------------------------- 1 | class StockRef { 2 | constructor() { 3 | this.ref = {} 4 | this.us = new Set() 5 | this.sh = new Set() 6 | this.sz = new Set() 7 | this.hk = new Set() 8 | } 9 | get array() { 10 | return Object.keys(this.ref) 11 | } 12 | addSymbol(symbol) { 13 | if (this.ref.hasOwnProperty(symbol)) { 14 | this.ref[symbol]++; 15 | return false 16 | } else { 17 | this.ref[symbol] = 1 18 | if (symbol.substr(0, 3) == "gb_") this.us.add(symbol) 19 | else this[symbol.substr(0, 2)].add(symbol) 20 | return true 21 | } 22 | } 23 | removeSymbol(symbol) { 24 | if (this.ref.hasOwnProperty(symbol)) { 25 | if (--this.ref[symbol] == 0) { 26 | delete this.ref[symbol] 27 | if (symbol.substr(0, 3) == "gb_") this.us.delete(symbol) 28 | else this[symbol.substr(0, 2)].delete(symbol) 29 | return true 30 | } 31 | } 32 | } 33 | removeSymbols(symbols) { 34 | for (let symbol of symbols) { 35 | this.removeSymbol(symbol) 36 | } 37 | } 38 | 39 | addSymbols(symbols) { 40 | for (let symbol of symbols) { 41 | this.addSymbol(symbol) 42 | } 43 | } 44 | clear() { 45 | this.ref = {} 46 | } 47 | } 48 | export default StockRef -------------------------------------------------------------------------------- /getSinaData/usStockRank.js: -------------------------------------------------------------------------------- 1 | const { mainDB, redisClient } = singleton -------------------------------------------------------------------------------- /homePageGenerator.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./homePageGenerator/index.js'); -------------------------------------------------------------------------------- /homePageGenerator/generators.js: -------------------------------------------------------------------------------- 1 | class ArrayGenerator { 2 | constructor(array) { 3 | this.data = array 4 | } 5 | getOne() { 6 | if (!this.data || !this.data.length) return null 7 | let current = this.data[0] 8 | this.data.push(this.data.shift()) 9 | return current 10 | } 11 | } 12 | /** 13 | * 专栏生成器 14 | * @constructor 15 | * @param {Object} allColumns - 所有专栏 */ 16 | class ColumnGenerator extends ArrayGenerator { 17 | constructor(allColumns) { 18 | super([]) 19 | this.originColumns = allColumns 20 | this.currentIndex = 0 21 | this.done = false 22 | } 23 | get currentColumn() { 24 | return this.originColumns[this.currentIndex] 25 | } 26 | 27 | /**下一轮 */ 28 | gotoNext() { 29 | if (this.originColumns.length) { 30 | //if (!this.currentColumn.News.length) { 31 | if (this.currentColumn.News.length < 4) { 32 | this.originColumns.splice(this.currentIndex, 1) 33 | if (!this.originColumns.length) { 34 | this.done = true 35 | return 36 | } 37 | } else { 38 | this.currentIndex++; 39 | } 40 | //循环获取 41 | if (this.currentIndex >= this.originColumns.length) { 42 | this.currentIndex = 0 43 | } 44 | } else this.done = true 45 | }; 46 | /**获取一个专栏 */ 47 | getOne() { 48 | if (this.done) return super.getOne() 49 | if (!this.currentColumn) return null 50 | let l = this.currentColumn.News.length 51 | let c = l >= 4 ? 4 : l 52 | if (c == 4) { 53 | var result = Object.assign({}, this.currentColumn) 54 | result.News = this.currentColumn.News.slice(0, c) 55 | this.currentColumn.News.splice(0, c) 56 | this.gotoNext() 57 | this.data.push(result) 58 | return result 59 | } 60 | this.gotoNext() 61 | return this.getOne() 62 | } 63 | } 64 | /**普通资讯生成器 */ 65 | class NewsGenerator extends ArrayGenerator { 66 | 67 | constructor(news) { 68 | super(news) 69 | console.log("total news:", news.length) 70 | this.newsPos = 0 71 | this.empty = false 72 | } 73 | 74 | getOne() { 75 | if (this.empty) return null 76 | /**随机数生成,3~5 */ 77 | let randNo = Math.round(Math.random() * 2 + 3) 78 | 79 | if (this.data.length < this.newsPos + randNo) { 80 | randNo = this.data.length - this.newsPos 81 | this.empty = true 82 | if (!randNo) return null 83 | } 84 | //let result = { Type: 0, News: this.news.slice(this.newsPos, this.newsPos + randNo) } 85 | let result = this.data.slice(this.newsPos, this.newsPos + randNo) 86 | this.newsPos += randNo 87 | console.log("剩余:", this.data.length - this.newsPos) 88 | return result 89 | } 90 | } 91 | /**书籍生成器 */ 92 | class BookGenerator extends ArrayGenerator { 93 | constructor(books) { 94 | super(books) 95 | this.turn = 0 96 | } 97 | getOne() { 98 | this.turn++; 99 | if (this.turn == 2 && this.data.length) { 100 | let result = Object.assign({ 101 | Books: this.data.map(d => { 102 | let r = Object.assign({}, d) 103 | r.Pic = d.Pic2 104 | delete r.Pic2 105 | return r 106 | }) 107 | }, this.data[0]); 108 | delete result.Pic2 109 | delete result.Author 110 | delete result.BookName 111 | return result; 112 | } 113 | return null 114 | // if (this.data.length > 4) { 115 | // let child = new Map() 116 | // let childnum = Math.min(20, this.data.length - 1) 117 | // while (child.size < childnum) { 118 | // let c = Object.assign({}, this.data[(Math.random() * this.data.length) >> 0]) 119 | // c.Pic = c.Pic2 120 | // delete c.Pic2 121 | // child.set(c.Id, c) 122 | // } 123 | // let result = Object.assign({ Books: Array.from(child.values()) /*去重*/ }, super.getOne()) 124 | // delete result.Pic2 125 | // delete result.Author 126 | // delete result.BookName 127 | // return result 128 | // } 129 | // return null 130 | } 131 | } 132 | export { ArrayGenerator, ColumnGenerator, NewsGenerator, BookGenerator } -------------------------------------------------------------------------------- /homePageGenerator/index.js: -------------------------------------------------------------------------------- 1 | import Config from '../config' 2 | import Iconv from 'iconv-lite' 3 | import amqp from 'amqplib' 4 | import Rx from 'rxjs' 5 | import { ArrayGenerator, ColumnGenerator, NewsGenerator, BookGenerator } from './generators' 6 | import PublishOnTime from './publishOnTime' 7 | import singleton from '../common/singleton' 8 | const { mainDB } = singleton 9 | const publishOnTime = new PublishOnTime(() => { GenerateHomePage() }) 10 | const homePageSqls = [ 11 | //"SELECT *,0 Type FROM (SELECT `Code`,id Id,Title,SelectPicture Pic,SecuritiesNo,ShowTime FROM wf_news news WHERE IsStartNews = 0 AND type = 9 AND ColumnNo = '' UNION SELECT `Code`,id,Title,SelectPicture,SecuritiesNo,ShowTime FROM wf_news news,wf_news_column ncolumn WHERE news.ColumnNo = ncolumn.ColumnNo AND (ncolumn.State = 0 OR ncolumn.Type = 0) AND news.Type=9) tp ORDER BY ShowTime desc", 12 | "select 0 Type,id Id,`Code`,Title,SelectPicture Pic,SecuritiesNo,ShowTime from wf_news news where type = 9 ORDER BY ShowTime desc", //普通资讯 13 | "SELECT a.ColumnId Id,a.ColumnNo,a.`Name` ColumnTitle,a.HomePage_Image,a.Description ColumnDes,b.`Code`,b.id Id,b.Title,b.SelectPicture Pic FROM wf_news_column a,wf_news b WHERE a.ColumnNo = b.ColumnNo AND a.State = 1 AND a.Type = 1 AND b.Type=9 ORDER BY b.ShowTime desc", //专栏 14 | "SELECT 2 Type,`Code`,id Id,Thumbnail Pic,Details,CreateTime FROM wf_imagetext WHERE State = 1 AND `Status` = 1 ORDER BY id DESC", //图说 15 | "SELECT 3 Type,Cover_Image Pic,`Code`,id Id FROM wf_dissertation_type WHERE State = 1 AND `Status` = 1 ORDER BY id DESC", //专题 16 | "SELECT 4 Type,`Code`,id Id,HomePage_Image Pic,Cover_Image Pic2,BookName,Author FROM wf_books WHERE `Status` = 1 ORDER BY id DESC", //书籍 17 | "SELECT 5 Type,VoteId Id,VoteCode `Code`,Title,Description Des,HomePageImage Pic,CreateTime,VoteCount from wf_vote where IsDelete = 0 order by CreateTime desc" //投票 18 | ]; 19 | 20 | (async() => { 21 | var amqpConnection = await amqp.connect(Config.amqpConn) 22 | let channel = await amqpConnection.createChannel() 23 | let ok = await channel.assertQueue('homepageGenerate') 24 | channel.consume('homepageGenerate', msg => { 25 | //var data = JSON.parse(Iconv.decode(msg.content, 'utf-8')) 26 | GenerateHomePage() 27 | channel.ack(msg) 28 | }) 29 | })(); 30 | 31 | /**首页生成主程序 */ 32 | async function GenerateHomePage() { 33 | //获取最大版本号 34 | let [version] = (await mainDB.query("select max(Versions)+1 maxVersion from wf_homepage"))[0] 35 | version = version['maxVersion'] 36 | if (!version) version = 1 37 | let allData = (await Promise.all(homePageSqls.map(sql => mainDB.query(sql)))).map(d => d[0]) 38 | let columnsMap = {} //按专栏id分组专栏数据 39 | let columns = [] 40 | for (var data1 of allData[1]) { 41 | if (!columnsMap[data1.ColumnNo]) { 42 | columnsMap[data1.ColumnNo] = { Type: 1, Id: data1.ColumnNo, Pic: data1.HomePage_Image, Title: data1.ColumnTitle, Des: data1.ColumnDes, News: [] } 43 | columns.push(columnsMap[data1.ColumnNo]) 44 | } 45 | columnsMap[data1.ColumnNo].News.push({ Id: data1.Id, Code: data1.Code, Title: data1.Title, Pic: data1.Pic }) 46 | } 47 | console.log(allData[0].length) 48 | let newsG = new NewsGenerator(publishOnTime.reset(allData[0])) 49 | let pageData = [] 50 | let page = 0 51 | let news = newsG.getOne() 52 | pageData.push(...news) //首页先生成几个资讯 53 | let temp = [new ColumnGenerator(columns), new ArrayGenerator(allData[3]), new BookGenerator(allData[4]), new ArrayGenerator(allData[5])] //专栏、图说、专题、书籍、投票 54 | //let temp = [columns, allData[2], allData[3], allData[4]] 55 | let now = new Date() 56 | news = null 57 | while (true) { 58 | for (let t of temp) { 59 | let big = t.getOne() 60 | if (big) { 61 | news = newsG.getOne() 62 | if (news) pageData.push(big, ...news) 63 | else break 64 | } 65 | } 66 | if (!news) break //资讯用完则跳出循环 67 | //生成的json进行一些格式处理 68 | let content = JSON.stringify(pageData, (key, value) => { 69 | if (value == null) return null 70 | switch (key) { 71 | case "Title": 72 | case "Des": 73 | case "BookName": 74 | case "Author": 75 | return value.replace(/"/g, '\\"') 76 | case "ShowTime": 77 | case "CreateTime": //时间格式 78 | return new Date(value).format() 79 | case "Pic": //加入图片的路径前缀 80 | return Config.picBaseURL + value 81 | case "Details": //截取前100个字符 82 | return (value.length > 100 ? value.substr(0, 100) : value).replace(/"/g, '\\"') 83 | default: 84 | return value 85 | } 86 | }) 87 | content = content.substr(1, content.length - 2).replace(/\\r|\\n/g, "") //去掉回车换行和前后中括号 88 | await mainDB.query(`insert into wf_homepage(Versions,Page,Content,CreateTime) values(${version},${page},'${content}','${now.format()}')`) 89 | pageData.length = 0 90 | page++ 91 | } 92 | console.log("生成首页完成,共" + page + "页"); 93 | try { 94 | ([ 95 | [{ maxVersion: version }] 96 | ] = await mainDB.query("select max(Versions) maxVersion from wf_homepage where CreateTime < CURDATE()")) //选出今天之前的最大版本号 97 | //version = version[0]['maxVersion'] 98 | let delResult = await mainDB.query("delete from wf_homepage where Versions < " + version) 99 | console.log("已删除旧数据:", delResult[0].affectedRows) 100 | } catch (ex) { 101 | console.error(ex) 102 | } 103 | } 104 | GenerateHomePage() -------------------------------------------------------------------------------- /homePageGenerator/publishOnTime.js: -------------------------------------------------------------------------------- 1 | /**定时发布工具 */ 2 | class PublishOnTime { 3 | constructor(f) { 4 | this.timeoutIds = [] 5 | this.callback = f 6 | } 7 | clear() { 8 | for (let id of this.timeoutIds) { 9 | clearTimeout(id) 10 | } 11 | this.timeoutIds.clear() 12 | } 13 | 14 | reset(datas, propName = "ShowTime") { 15 | this.clear() 16 | let timeMap = {} 17 | let now = new Date() 18 | let normals = [] 19 | for (let data of datas) { 20 | let showTime = new Date(data[propName]) 21 | if (!timeMap[showTime]) { 22 | timeMap[showTime] = showTime - now 23 | if (timeMap[showTime] > 0) 24 | this.timeoutIds.push(setTimeout(this.callback, timeMap[showTime])) 25 | } 26 | if (timeMap[showTime] < 0) normals.push(data) 27 | } 28 | return normals 29 | } 30 | } 31 | export default PublishOnTime -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./' + process.argv[2] + '/index.js'); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "price-notify", 3 | "version": "1.0.0", 4 | "description": "股价提醒系统", 5 | "main": "index.js", 6 | "scripts": { 7 | "babel-node": "babel-node --plugins=transform-es2015-modules-commonjs", 8 | "build": "webpack --config ./webapi/web/webpack.config.js --progress --colors --watch -d", 9 | "buildll": "webpack --config ./webapi/web/webpack.dll.config.js --progress -p", 10 | "priceNotify": "nodemon --exec npm run babel-node priceNotify/index.js", 11 | "homePageGen": "nodemon --exec npm run babel-node homePageGenerator/index.js" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "http://git.wolfstreet.tv/PriceNotify.git" 16 | }, 17 | "author": "dexter", 18 | "license": "ISC", 19 | "dependencies": { 20 | "amqplib": "^0.5.1", 21 | "axios": "^0.15.3", 22 | "babel-register": "^6.18.0", 23 | "bluebird": "^3.5.0", 24 | "body-parser": "^1.15.2", 25 | "cookie-parser": "^1.4.3", 26 | "express": "^4.13.4", 27 | "express-graphql": "^0.5.3", 28 | "express-session": "^1.14.2", 29 | "gm": "^1.23.0", 30 | "graphql": "^0.6.0", 31 | "iconv-lite": "^0.4.15", 32 | "jpush-sdk": "^3.3.7", 33 | "jshtml-express": "^0.0.1", 34 | "knex": "^0.13.0", 35 | "less-loader": "^4.0.3", 36 | "moment": "^2.18.1", 37 | "moment-timezone": "^0.5.13", 38 | "mongodb": "^2.2.26", 39 | "multer": "^1.2.1", 40 | "mysql": "^2.12.0", 41 | "node-uuid": "^1.4.8", 42 | "pm2": "^2.4.3", 43 | "redis": "^2.7.1", 44 | "request": "^2.81.0", 45 | "request-promise": "^4.2.0", 46 | "rongcloud-sdk": "^1.0.3", 47 | "rxjs": "^5.0.2", 48 | "sequelize": "^3.28.0", 49 | "vue-resource": "^1.3.1", 50 | "wechat-api": "^1.33.0" 51 | }, 52 | "devDependencies": { 53 | "babel-cli": "^6.9.0", 54 | "babel-loader": "^6.4.1", 55 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", 56 | "babel-plugin-transform-object-rest-spread": "^6.23.0", 57 | "babel-preset-es2015": "^6.18.0", 58 | "css-loader": "^0.28.0", 59 | "file-loader": "^0.11.1", 60 | "muse-ui": "^2.0.2", 61 | "nodemon": "^1.9.2", 62 | "pug": "^2.0.0-beta11", 63 | "style-loader": "^0.16.1", 64 | "vue": "^2.2.6", 65 | "vue-loader": "^11.3.4", 66 | "vue-template-compiler": "^2.2.6", 67 | "webpack": "^2.3.3" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /paperTrade.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./paperTrade/index.js'); -------------------------------------------------------------------------------- /paperTrade/deal.js: -------------------------------------------------------------------------------- 1 | import singleton from '../common/singleton' 2 | import sqlstr from '../common/sqlStr' 3 | const { mainDB, redisClient, jpushClient } = singleton 4 | export default async({ Id: OrderId, Commission, delta, AccountNo, OrdType, Side, OrderQty, Price, SecuritiesType, SecuritiesNo, MemberCode, Amount }) => { 5 | let Type = ((OrdType - 1) / 3 >> 0) + 1 //1,2,3=>1做多;4,5,6=>2做空 6 | let { Cash, UsableCash } = await singleton.selectMainDB0("wf_street_practice_account", { AccountNo }) 7 | let { Positions = 0, TradAble = 0, CostPrice, Id: PositionsId } = await singleton.selectMainDB0("wf_street_practice_positions", { AccountNo, SecuritiesType, SecuritiesNo, Type }) 8 | let result = await singleton.transaction(async t => { 9 | let OldPositions = Positions 10 | let UsableCashDelta = 0 11 | //做多判断是否是卖,做空判断是否是买 12 | if (Side == "SB" [Type - 1]) { 13 | Positions -= OrderQty 14 | if (Type == 2) delta += OrderQty * (CostPrice - Price) 15 | if (Positions > 0) { 16 | await singleton.updateMainDB("wf_street_practice_positions", { Positions }, null, { Id: PositionsId }, t) 17 | } else if (Positions == 0) { 18 | await singleton.deleteMainDB("wf_street_practice_positions", { Id: PositionsId }, null, t) 19 | } else { 20 | throw 2 21 | } 22 | UsableCashDelta = delta 23 | } else { 24 | if (Positions) { 25 | // let Cost = Positions * CostPrice + OrderQty * Price; 26 | let Cost = Positions * CostPrice - delta 27 | Positions += OrderQty 28 | CostPrice = Cost / Positions 29 | await singleton.updateMainDB("wf_street_practice_positions", { Positions, CostPrice, TradAble: TradAble + OrderQty }, null, { Id: PositionsId }, t) 30 | } else { 31 | TradAble = Positions = OrderQty 32 | CostPrice = -delta / OrderQty 33 | await singleton.insertMainDB("wf_street_practice_positions", { Positions, TradAble, CostPrice, SecuritiesType, SecuritiesNo, MemberCode, AccountNo, Type }, { CreateTime: "now()" }, t) 34 | } 35 | UsableCashDelta = Amount + delta 36 | } 37 | if (Cash + delta < 0) { 38 | throw 1 39 | } 40 | UsableCash += UsableCashDelta; 41 | await singleton.updateMainDB("wf_street_practice_account", { Cash: Cash + delta, UsableCash }, null, { AccountNo }, t) 42 | await singleton.insertMainDB("wf_street_practice_positionshistory" + (Type == 2 ? "_short" : ""), { OrderId, MemberCode, AccountNo, OldPositions, Positions, SecuritiesType, SecuritiesNo }, { CreateTime: "now()" }, t) 43 | await singleton.insertMainDB("wf_street_practice_cashhistory", { OrderId, MemberCode, AccountNo, OldCash: Cash, Cash: Cash + delta }, { CreateTime: "now()" }, t) 44 | await singleton.updateMainDB("wf_street_practice_order", { execType: 1, Commission, Price }, { TurnoverTime: "now()" }, { Id: OrderId }, t) 45 | }) 46 | switch (result) { 47 | case 0: 48 | return 0 49 | case 1: 50 | case 2: 51 | result = await singleton.transaction(async t => { 52 | await singleton.updateMainDB("wf_street_practice_order", { execType: 3, Reason: result }, null, { Id: OrderId }, t) 53 | if (Side == "SB" [Type - 1]) { 54 | TradAble += OrderQty //修改可交易仓位 55 | await singleton.updateMainDB("wf_street_practice_positions", { TradAble }, null, { Id: PositionsId }, t) 56 | } else { 57 | await singleton.updateMainDB("wf_street_practice_account", { UsableCash: UsableCash + Amount }, null, { AccountNo }, t) 58 | } 59 | }) 60 | return result 61 | default: 62 | return result 63 | } 64 | } -------------------------------------------------------------------------------- /priceNotify.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./priceNotify/index.js'); -------------------------------------------------------------------------------- /priceNotify/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import Config from '../config' 3 | import JPush from 'jpush-sdk' 4 | import amqp from 'amqplib' 5 | import moment from 'moment-timezone' 6 | import singleton from '../common/singleton' 7 | const { mainDB, redisClient, jpushClient } = singleton 8 | const jpushRegIDSql = ` 9 | SELECT 10 | a.*, b.JpushRegID,c.SecuritiesName 11 | FROM 12 | wf_securities_remind a 13 | LEFT JOIN wf_im_jpush b ON a.MemberCode = b.MemberCode 14 | LEFT JOIN wf_securities_trade c ON a.SecuritiesNo = c.SecuritiesNo and a.SmallType = c.SmallType 15 | WHERE 16 | a.MemberCode not in (select MemberCode from wf_system_setting where PriceNotify = 0) 17 | AND ( 18 | a.IsOpenLower = 1 19 | OR a.IsOpenUpper = 1 20 | OR a.IsOpenRise = 1 21 | OR a.IsOpenFall = 1 22 | );` 23 | const someBodySql = ` 24 | SELECT 25 | a.*, b.JpushRegID,c.SecuritiesName 26 | FROM 27 | wf_securities_remind a 28 | LEFT JOIN wf_im_jpush b ON a.MemberCode = b.MemberCode 29 | LEFT JOIN wf_securities_trade c ON a.SecuritiesNo = c.SecuritiesNo and a.SmallType = c.SmallType 30 | WHERE 31 | a.MemberCode = :memberCode 32 | AND ( 33 | a.IsOpenLower = 1 34 | OR a.IsOpenUpper = 1 35 | OR a.IsOpenRise = 1 36 | OR a.IsOpenFall = 1 37 | );` 38 | import StockRef from '../getSinaData/stocksRef' 39 | var stocksRef = new StockRef() 40 | var notifies = new Map() 41 | 42 | function isAllClose({ IsOpenLower, IsOpenUpper, IsOpenRise, IsOpenFall }) { 43 | return !(IsOpenLower || IsOpenUpper || IsOpenRise || IsOpenFall) 44 | } 45 | 46 | //rabitmq 通讯 47 | async function startMQ() { 48 | var amqpConnection = await amqp.connect(Config.amqpConn) 49 | let channel = await amqpConnection.createChannel() 50 | let ok = await channel.assertQueue('priceNotify') 51 | async function updateNotify(data) { 52 | let { RemindId } = data 53 | let name = Config.getQueryName(data) 54 | if (notifies.has(RemindId)) { 55 | if (isAllClose(data)) { 56 | notifies.delete(RemindId) 57 | if (stocksRef.removeSymbol(name)) 58 | channel.sendToQueue("getSinaData", new Buffer(JSON.stringify({ type: "remove", listener: "priceNotify", symbols: [name] }))) 59 | } else { 60 | let notify = notifies.get(data.RemindId) 61 | Object.assign(notify, data) 62 | data = notify 63 | } 64 | 65 | } else { 66 | if (!isAllClose(data)) { 67 | data.JpushRegID = (await mainDB.query('select JpushRegID from wf_im_jpush where MemberCode=:MemberCode', { replacements: data }))[0][0].JpushRegID; 68 | if (stocksRef.addSymbol(name)) { 69 | data.SecuritiesName = (await mainDB.query('select SecuritiesName from wf_securities_trade where SecuritiesNo =:SecuritiesNo and SmallType = :SmallType', { replacements: data }))[0][0].SecuritiesName 70 | channel.sendToQueue("getSinaData", new Buffer(JSON.stringify({ type: "add", listener: "priceNotify", symbols: [name] }))) 71 | } 72 | notifies.set(RemindId, data) 73 | } 74 | } 75 | console.log("更新股价提醒", data) 76 | } 77 | await getAllNotify() 78 | channel.sendToQueue("getSinaData", new Buffer(JSON.stringify({ type: "reset", listener: "priceNotify", symbols: stocksRef.array }))) 79 | channel.consume('priceNotify', async msg => { 80 | let { cmd, data } = JSON.parse(msg.content.toString()) 81 | switch (cmd) { 82 | case "update": 83 | updateNotify(data) 84 | break; 85 | case "changeJpush": 86 | console.log("更新Jpush", data) 87 | let { MemberCode, JpushRegID } = data 88 | for (let notify of notifies.values()) { 89 | if (notify.MemberCode == MemberCode) notify.JpushRegID = JpushRegID 90 | } 91 | break; 92 | case "turnOff": 93 | console.log("关闭股价提醒", data.memberCode) 94 | for (let notify of notifies.values()) { 95 | if (notify.MemberCode == data.memberCode) { 96 | let name = Config.getQueryName(notify) 97 | if (stocksRef.removeSymbol(name)) { 98 | channel.sendToQueue("getSinaData", new Buffer(JSON.stringify({ type: "remove", listener: "priceNotify", symbols: [name] }))) 99 | } 100 | notifies.delete(notify.RemindId) 101 | } 102 | } 103 | break; 104 | case "turnOn": 105 | console.log("打开股价提醒", data.memberCode) 106 | let ns = await mainDB.query(someBodySql, { replacements: data, type: "SELECT" }) 107 | ns.forEach(n => updateNotify(n)) 108 | break; 109 | } 110 | channel.ack(msg) 111 | }); 112 | await channel.assertExchange("broadcast", "fanout"); 113 | ok = await channel.assertQueue('sinaData_priceNotify'); 114 | ok = await channel.bindQueue('sinaData_priceNotify', 'broadcast', 'fanout'); 115 | channel.consume('sinaData_priceNotify', msg => { 116 | switch (msg.content.toString()) { 117 | case "restart": //股票引擎重启信号 118 | console.log(new Date(), "sina restart") 119 | channel.sendToQueue("getSinaData", new Buffer(JSON.stringify({ type: "reset", listener: "priceNotify", symbols: stocksRef.array }))) 120 | break; 121 | } 122 | channel.ack(msg) 123 | }) 124 | } 125 | startMQ() 126 | 127 | 128 | /** 129 | * 获取jpushregid和所有提醒数据 130 | */ 131 | async function getAllNotify() { 132 | notifies.clear() 133 | stocksRef.clear() 134 | let ns = await mainDB.query(jpushRegIDSql, { type: "SELECT" }) 135 | for (let n of ns) { 136 | notifies.set(n.RemindId, n) 137 | stocksRef.addSymbol(Config.getQueryName(n)) 138 | } 139 | } 140 | 141 | function sendNotify(type, notify, price, chg) { 142 | let title = `${notify.SecuritiesName}(${notify.SecuritiesNo})最新价${price}` 143 | //您关注的Snapchat(SNAP)于2017-03-11 10:09:11(美东时间)达到21.82,涨幅为7.07%,超过7%了。 144 | let msg = `您关注的${notify.SecuritiesName}(${notify.SecuritiesNo})于${notify.SmallType=='us'?moment().tz('America/New_York').format('YYYY-MM-DD hh:mm:ss')+"(美东时间)":moment().format("YYYY-MM-DD hh:mm:ss")}达到${price}` 145 | switch (type) { 146 | case 0: 147 | msg += `,低于${notify.LowerLimit}` 148 | break 149 | case 1: 150 | msg += `,超过了${notify.UpperLimit}` 151 | break 152 | case 2: 153 | msg += `,跌幅为${chg.toFixed(2)}%,超过了-${notify.FallLimit.toFixed(2)}%` 154 | break 155 | case 3: 156 | msg += `,涨幅为${chg.toFixed(2)}%,超过了${notify.RiseLimit.toFixed(2)}%` 157 | break 158 | } 159 | if (notify.JpushRegID) 160 | jpushClient.push().setPlatform(JPush.ALL).setAudience(JPush.registration_id(notify.JpushRegID)) 161 | //sendno, time_to_live, override_msg_id, apns_production, big_push_duration 162 | .setOptions(null, null, null, Config.apns_production) 163 | .setNotification('股价提醒', JPush.ios(msg, 'sound', 0, false, { AlertType: Config.jpushType, SmallType: notify.SmallType, SecuritiesNo: notify.SecuritiesNo }), JPush.android(msg, title, 1, { AlertType: Config.jpushType, SmallType: notify.SmallType, SecuritiesNo: notify.SecuritiesNo })) 164 | .send(async(err, res) => { 165 | if (err) { 166 | if (err instanceof JPush.APIConnectionError) { 167 | console.log(err.message) 168 | } else if (err instanceof JPush.APIRequestError) { 169 | console.log(err.message) 170 | } 171 | } else { 172 | let replacements = { msg, title, MemberCode: notify.MemberCode, Extension: JSON.stringify({ SmallType: notify.SmallType, SecuritiesNo: notify.SecuritiesNo }) } 173 | await mainDB.query("insert into wf_message(Type,Content,MemberCode,CreateTime,Title,Status,Extension,IsSend) values(1,:msg,:MemberCode,now(),:title,0,:Extension,1)", { replacements }); 174 | } 175 | }) 176 | } 177 | 178 | setInterval(async() => { 179 | let marketIsOpen = await singleton.marketIsOpen() 180 | //调用新浪接口 181 | for (let notify of notifies.values()) { 182 | if (!marketIsOpen[notify.SmallType]) { 183 | continue 184 | } 185 | let name = Config.getQueryName(notify) 186 | let [, , , price, pre, chg] = await singleton.getLastPrice(name) 187 | if (!chg) chg = pre ? (price - pre) * 100 / pre : 0 188 | chg = Number(chg.toFixed(2)) 189 | //console.log(name, price, chg, notify) 190 | if (notify.IsOpenLower) { 191 | if (notify.isLowSent) { 192 | if (price > notify.LowerLimit) { 193 | //恢复状态 194 | notify.isLowSent = false 195 | } 196 | } else { 197 | if (price < notify.LowerLimit) { 198 | //向下击穿 199 | sendNotify(0, notify, price, chg) 200 | notify.isLowSent = true 201 | } 202 | } 203 | } 204 | if (notify.IsOpenUpper) { 205 | if (notify.isUpperSent) { 206 | if (price < notify.UpperLimit) { 207 | //恢复状态 208 | notify.isUpperSent = false 209 | } 210 | } else { 211 | if (price > notify.UpperLimit) { 212 | //向上突破 213 | sendNotify(1, notify, price, chg) 214 | notify.isUpperSent = true 215 | } 216 | } 217 | } 218 | if (chg < 0) { 219 | if (notify.IsOpenFall) { 220 | let target = -Number(notify.FallLimit.toFixed(2)) 221 | if (notify.isFallSent) { 222 | if (chg > target) { 223 | //恢复状态 224 | notify.isFallSent = false 225 | } 226 | } else { 227 | if (chg < target) { 228 | //向下击穿 229 | sendNotify(2, notify, price, chg) 230 | notify.isFallSent = true 231 | } 232 | } 233 | } 234 | } else { 235 | if (notify.IsOpenRise) { 236 | let target = Number(notify.RiseLimit.toFixed(2)) 237 | if (notify.isRiseSent) { 238 | if (chg < target) { 239 | //恢复状态 240 | notify.isRiseSent = false 241 | } 242 | } else { 243 | if (chg > target) { 244 | //向上突破 245 | sendNotify(3, notify, price, chg) 246 | notify.isRiseSent = true 247 | } 248 | } 249 | } 250 | } 251 | } 252 | }, 5000); 253 | // let server = app.listen(process.env.PORT, function() { 254 | // let host = server.address().address; 255 | // let port = server.address().port; 256 | 257 | // console.log('server listening at %s %d', host, port); 258 | // }); -------------------------------------------------------------------------------- /webapi.js: -------------------------------------------------------------------------------- 1 | require('babel-register')(require('./babelconfig')); 2 | require('./webapi/index.js'); -------------------------------------------------------------------------------- /webapi/assets/image/bg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg1.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg1_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg1_1.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg2.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg3.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg4.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg5.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg6.jpg -------------------------------------------------------------------------------- /webapi/assets/image/bg7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/bg7.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_1.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_2.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_3.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_4.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_5.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_6.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_7.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_1_8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_1_8.jpg -------------------------------------------------------------------------------- /webapi/assets/image/main1_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/main1_2.jpg -------------------------------------------------------------------------------- /webapi/assets/image/pic1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/pic1.png -------------------------------------------------------------------------------- /webapi/assets/image/qxtbg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/qxtbg.png -------------------------------------------------------------------------------- /webapi/assets/image/qxtqg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/qxtqg.png -------------------------------------------------------------------------------- /webapi/assets/image/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/top.png -------------------------------------------------------------------------------- /webapi/assets/image/up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/image/up.png -------------------------------------------------------------------------------- /webapi/assets/js/TouchSlide.1.1.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * TouchSlide v1.1 3 | * javascript触屏滑动特效插件,移动端滑动特效,触屏焦点图,触屏Tab切换,触屏多图切换等 4 | * 详尽信息请看官网:http://www.SuperSlide2.com/TouchSlide/ 5 | * 6 | * Copyright 2013 大话主席 7 | * 8 | * 请尊重原创,保留头部版权 9 | * 在保留版权的前提下可应用于个人或商业用途 10 | 11 | * 1.1 宽度自适应(修复安卓横屏时滑动范围不变的bug) 12 | */ 13 | 14 | var TouchSlide=function(a){a=a||{};var b={slideCell:a.slideCell||"#touchSlide",titCell:a.titCell||".hd li",mainCell:a.mainCell||".bd",effect:a.effect||"left",autoPlay:a.autoPlay||!1,delayTime:a.delayTime||200,interTime:a.interTime||2500,defaultIndex:a.defaultIndex||0,titOnClassName:a.titOnClassName||"on",autoPage:a.autoPage||!1,prevCell:a.prevCell||".prev",nextCell:a.nextCell||".next",pageStateCell:a.pageStateCell||".pageState",pnLoop:"undefined "==a.pnLoop?!0:a.pnLoop,startFun:a.startFun||null,endFun:a.endFun||null,switchLoad:a.switchLoad||null},c=document.getElementById(b.slideCell.replace("#",""));if(!c)return!1;var d=function(a,b){a=a.split(" ");var c=[];b=b||document;var d=[b];for(var e in a)0!=a[e].length&&c.push(a[e]);for(var e in c){if(0==d.length)return!1;var f=[];for(var g in d)if("#"==c[e][0])f.push(document.getElementById(c[e].replace("#","")));else if("."==c[e][0])for(var h=d[g].getElementsByTagName("*"),i=0;iR;R++)Q+="
  • "+(R+1)+"
  • ";else for(var R=0;p>R;R++)Q+=b.autoPage.replace("$",R+1);o.innerHTML=Q,o=o.children}"leftLoop"==i&&(P+=2,m.appendChild(m.children[0].cloneNode(!0)),m.insertBefore(m.children[n-1].cloneNode(!0),m.children[0])),N=e(m,'
    '),m.style.cssText="width:"+P*M+"px;"+"position:relative;overflow:hidden;padding:0;margin:0;";for(var R=0;P>R;R++)m.children[R].style.cssText="display:table-cell;vertical-align:top;width:"+M+"px";var S=function(){"function"==typeof b.startFun&&b.startFun(r,p)},T=function(){"function"==typeof b.endFun&&b.endFun(r,p)},U=function(a){var b=("leftLoop"==i?r+1:r)+a,c=function(a){for(var b=m.children[a].getElementsByTagName("img"),c=0;ca;a++)m.children[a].style.width=M+"px";var b="leftLoop"==i?r+1:r;W(-b*M,0)};window.addEventListener("resize",V,!1);var W=function(a,b,c){c=c?c.style:m.style,c.webkitTransitionDuration=c.MozTransitionDuration=c.msTransitionDuration=c.OTransitionDuration=c.transitionDuration=b+"ms",c.webkitTransform="translate("+a+"px,0)"+"translateZ(0)",c.msTransform=c.MozTransform=c.OTransform="translateX("+a+"px)"},X=function(a){switch(i){case"left":r>=p?r=a?r-1:0:0>r&&(r=a?0:p-1),null!=q&&U(0),W(-r*M,s),x=r;break;case"leftLoop":null!=q&&U(0),W(-(r+1)*M,s),-1==r?(z=setTimeout(function(){W(-p*M,0)},s),r=p-1):r==p&&(z=setTimeout(function(){W(-M,0)},s),r=0),x=r}S(),A=setTimeout(function(){T()},s);for(var c=0;p>c;c++)h(o[c],b.titOnClassName),c==r&&g(o[c],b.titOnClassName);0==w&&(h(k,"nextStop"),h(j,"prevStop"),0==r?g(j,"prevStop"):r==p-1&&g(k,"nextStop")),l&&(l.innerHTML=""+(r+1)+"/"+p)};if(X(),u&&(y=setInterval(function(){r++,X()},t)),o)for(var R=0;p>R;R++)!function(){var a=R;o[a].addEventListener("click",function(){clearTimeout(z),clearTimeout(A),r=a,X()})}();k&&k.addEventListener("click",function(){(1==w||r!=p-1)&&(clearTimeout(z),clearTimeout(A),r++,X())}),j&&j.addEventListener("click",function(){(1==w||0!=r)&&(clearTimeout(z),clearTimeout(A),r--,X())});var Y=function(a){clearTimeout(z),clearTimeout(A),O=void 0,D=0;var b=H?a.touches[0]:a;B=b.pageX,C=b.pageY,m.addEventListener(J,Z,!1),m.addEventListener(K,$,!1)},Z=function(a){if(!H||!(a.touches.length>1||a.scale&&1!==a.scale)){var b=H?a.touches[0]:a;if(D=b.pageX-B,E=b.pageY-C,"undefined"==typeof O&&(O=!!(O||Math.abs(D)0||r>=p-1&&0>D)&&(D=.4*D),W(-r*M+D,0);break;case"leftLoop":W(-(r+1)*M+D,0)}null!=q&&Math.abs(D)>M/3&&U(D>-0?-1:1)}}},$=function(a){0!=D&&(a.preventDefault(),O||(Math.abs(D)>M/10&&(D>0?r--:r++),X(!0),u&&(y=setInterval(function(){r++,X()},t))),m.removeEventListener(J,Z,!1),m.removeEventListener(K,$,!1))};m.addEventListener(I,Y,!1)}; -------------------------------------------------------------------------------- /webapi/assets/js/calculator.js: -------------------------------------------------------------------------------- 1 | // JavaScript Document 2 | var h=document.documentElement.clientHeight; 3 | var w=document.documentElement.clientWidth; 4 | var j=document.documentElement.clientHeight+"px"; 5 | var k=document.documentElement.clientWidth+"px"; 6 | 7 | var covew=$(".cover").width(); 8 | var coveh=(covew/640)*253; 9 | $(".cover").css({"height":coveh}); 10 | 11 | 12 | 13 | 14 | 15 | 16 | /*var maphelpw=$(".maphelp").width(); 17 | var maphelph=(ltimelw/640)*463; 18 | $(".maphelp").css({"height":maphelph});*/ 19 | 20 | $("style").append("@-webkit-keyframes lengthen{0%{height:0;}100%{height:"+j+";}}") 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | function playOrPaused(){ 29 | $(".play-div span").addClass("z-show"); 30 | var audio = document.getElementById("audioPlay"); 31 | if(audio.paused){ 32 | audio.play(); 33 | $(".player-button").addClass("animation"); 34 | $('.player-tip').text('播放'); 35 | setTimeout("Invite();", 1000); 36 | return; 37 | } 38 | audio.pause(); 39 | $(".player-button").removeClass("animation"); 40 | $('.player-tip').text('暂停'); 41 | setTimeout("Invite();", 1000); 42 | } 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /webapi/assets/js/countUp.min.js: -------------------------------------------------------------------------------- 1 | !function(a,b){"function"==typeof define&&define.amd?define(b):"object"==typeof exports?module.exports=b(require,exports,module):a.CountUp=b()}(this,function(){var d=function(a,b,c,d,e,f){for(var g=0,h=["webkit","moz","ms","o"],i=0;ithis.endVal,this.frameVal=this.startVal,this.decimals=Math.max(0,d||0),this.dec=Math.pow(10,this.decimals),this.duration=1e3*Number(e)||2e3;var k=this;this.version=function(){return"1.5.3"},this.printValue=function(a){var b=isNaN(a)?"--":k.formatNumber(a);"INPUT"==k.d.tagName?this.d.value=b:"text"==k.d.tagName?this.d.textContent=b:this.d.innerHTML=b},this.easeOutExpo=function(a,b,c,d){return 1024*c*(-Math.pow(2,-10*a/d)+1)/1023+b},this.count=function(a){k.startTime||(k.startTime=a),k.timestamp=a;var b=a-k.startTime;k.remaining=k.duration-b,k.frameVal=k.options.useEasing?k.countDown?k.startVal-k.easeOutExpo(b,0,k.startVal-k.endVal,k.duration):k.easeOutExpo(b,k.startVal,k.endVal-k.startVal,k.duration):k.countDown?k.startVal-(k.startVal-k.endVal)*(b/k.duration):k.startVal+(k.endVal-k.startVal)*(b/k.duration),k.frameVal=k.countDown?k.frameValk.endVal?k.endVal:k.frameVal,k.frameVal=Math.round(k.frameVal*k.dec)/k.dec,k.printValue(k.frameVal),bk.endVal,k.rAF=requestAnimationFrame(k.count)},this.formatNumber=function(a){a=a.toFixed(k.decimals),a+="";var b,c,d,e;if(b=a.split("."),c=b[0],d=b.length>1?k.options.decimal+b[1]:"",e=/(\d+)(\d{3})/,k.options.useGrouping)for(;e.test(c);)c=c.replace(e,"$1"+k.options.separator+"$2");return k.options.prefix+c+d+k.options.suffix},k.printValue(k.startVal)};return d}); -------------------------------------------------------------------------------- /webapi/assets/js/idangerous.swiper.progress.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Swiper Smooth Progress 1.1.0 3 | * Smooth progress plugin for Swiper 4 | * 5 | * http://www.idangero.us/sliders/swiper/plugins/progress.php 6 | * 7 | * Copyright 2010-2014, Vladimir Kharlampidi 8 | * The iDangero.us 9 | * http://www.idangero.us/ 10 | * 11 | * Licensed under GPL & MIT 12 | * 13 | * Released on: January 29, 2014 14 | */ 15 | Swiper.prototype.plugins.progress=function(a){function b(){for(var b=0;b settings.failure_limit) { 53 | return false; 54 | } 55 | } 56 | }); 57 | 58 | } 59 | 60 | if(options) { 61 | /* Maintain BC for a couple of versions. */ 62 | if (undefined !== options.failurelimit) { 63 | options.failure_limit = options.failurelimit; 64 | delete options.failurelimit; 65 | } 66 | if (undefined !== options.effectspeed) { 67 | options.effect_speed = options.effectspeed; 68 | delete options.effectspeed; 69 | } 70 | 71 | $.extend(settings, options); 72 | } 73 | 74 | /* Cache container as jQuery as object. */ 75 | $container = (settings.container === undefined || 76 | settings.container === window) ? $window : $(settings.container); 77 | 78 | /* Fire one scroll event per scroll. Not one scroll event per image. */ 79 | if (0 === settings.event.indexOf("scroll")) { 80 | $container.on(settings.event, function() { 81 | return update(); 82 | }); 83 | } 84 | 85 | this.each(function() { 86 | var self = this; 87 | var $self = $(self); 88 | 89 | self.loaded = false; 90 | 91 | /* If no src attribute given use data:uri. */ 92 | if ($self.attr("src") === undefined || $self.attr("src") === false) { 93 | if ($self.is("img")) { 94 | $self.attr("src", settings.placeholder); 95 | } 96 | } 97 | 98 | /* When appear is triggered load original image. */ 99 | $self.one("appear", function() { 100 | if (!this.loaded) { 101 | if (settings.appear) { 102 | var elements_left = elements.length; 103 | settings.appear.call(self, elements_left, settings); 104 | } 105 | $("") 106 | .one("load", function() { 107 | var original = $self.attr("data-" + settings.data_attribute); 108 | $self.hide(); 109 | if ($self.is("img")) { 110 | $self.attr("src", original); 111 | } else { 112 | $self.css("background-image", "url('" + original + "')"); 113 | } 114 | $self[settings.effect](settings.effect_speed); 115 | 116 | self.loaded = true; 117 | 118 | /* Remove image from array so it is not looped next time. */ 119 | var temp = $.grep(elements, function(element) { 120 | return !element.loaded; 121 | }); 122 | elements = $(temp); 123 | 124 | if (settings.load) { 125 | var elements_left = elements.length; 126 | settings.load.call(self, elements_left, settings); 127 | } 128 | }) 129 | .attr("src", $self.attr("data-" + settings.data_attribute)); 130 | } 131 | }); 132 | 133 | /* When wanted event is triggered load original image */ 134 | /* by triggering appear. */ 135 | if (0 !== settings.event.indexOf("scroll")) { 136 | $self.on(settings.event, function() { 137 | if (!self.loaded) { 138 | $self.trigger("appear"); 139 | } 140 | }); 141 | } 142 | }); 143 | 144 | /* Check if something appears when window is resized. */ 145 | $window.on("resize", function() { 146 | update(); 147 | }); 148 | 149 | /* With IOS5 force loading images when navigating with back button. */ 150 | /* Non optimal workaround. */ 151 | if ((/(?:iphone|ipod|ipad).*os 5/gi).test(navigator.appVersion)) { 152 | $window.on("pageshow", function(event) { 153 | if (event.originalEvent && event.originalEvent.persisted) { 154 | elements.each(function() { 155 | $(this).trigger("appear"); 156 | }); 157 | } 158 | }); 159 | } 160 | 161 | /* Force initial check if images should appear. */ 162 | $(document).ready(function() { 163 | update(); 164 | }); 165 | 166 | return this; 167 | }; 168 | 169 | /* Convenience methods in jQuery namespace. */ 170 | /* Use as $.belowthefold(element, {threshold : 100, container : window}) */ 171 | 172 | $.belowthefold = function(element, settings) { 173 | var fold; 174 | 175 | if (settings.container === undefined || settings.container === window) { 176 | fold = (window.innerHeight ? window.innerHeight : $window.height()) + $window.scrollTop(); 177 | } else { 178 | fold = $(settings.container).offset().top + $(settings.container).height(); 179 | } 180 | 181 | return fold <= $(element).offset().top - settings.threshold; 182 | }; 183 | 184 | $.rightoffold = function(element, settings) { 185 | var fold; 186 | 187 | if (settings.container === undefined || settings.container === window) { 188 | fold = $window.width() + $window.scrollLeft(); 189 | } else { 190 | fold = $(settings.container).offset().left + $(settings.container).width(); 191 | } 192 | 193 | return fold <= $(element).offset().left - settings.threshold; 194 | }; 195 | 196 | $.abovethetop = function(element, settings) { 197 | var fold; 198 | 199 | if (settings.container === undefined || settings.container === window) { 200 | fold = $window.scrollTop(); 201 | } else { 202 | fold = $(settings.container).offset().top; 203 | } 204 | 205 | return fold >= $(element).offset().top + settings.threshold + $(element).height(); 206 | }; 207 | 208 | $.leftofbegin = function(element, settings) { 209 | var fold; 210 | 211 | if (settings.container === undefined || settings.container === window) { 212 | fold = $window.scrollLeft(); 213 | } else { 214 | fold = $(settings.container).offset().left; 215 | } 216 | 217 | return fold >= $(element).offset().left + settings.threshold + $(element).width(); 218 | }; 219 | 220 | $.inviewport = function(element, settings) { 221 | return !$.rightoffold(element, settings) && !$.leftofbegin(element, settings) && 222 | !$.belowthefold(element, settings) && !$.abovethetop(element, settings); 223 | }; 224 | 225 | /* Custom selectors for your convenience. */ 226 | /* Use as $("img:below-the-fold").something() or */ 227 | /* $("img").filter(":below-the-fold").something() which is faster */ 228 | 229 | $.extend($.expr[":"], { 230 | "below-the-fold" : function(a) { return $.belowthefold(a, {threshold : 0}); }, 231 | "above-the-top" : function(a) { return !$.belowthefold(a, {threshold : 0}); }, 232 | "right-of-screen": function(a) { return $.rightoffold(a, {threshold : 0}); }, 233 | "left-of-screen" : function(a) { return !$.rightoffold(a, {threshold : 0}); }, 234 | "in-viewport" : function(a) { return $.inviewport(a, {threshold : 0}); }, 235 | /* Maintain BC for couple of versions. */ 236 | "above-the-fold" : function(a) { return !$.belowthefold(a, {threshold : 0}); }, 237 | "right-of-fold" : function(a) { return $.rightoffold(a, {threshold : 0}); }, 238 | "left-of-fold" : function(a) { return !$.rightoffold(a, {threshold : 0}); } 239 | }); 240 | 241 | })(jQuery, window, document); 242 | -------------------------------------------------------------------------------- /webapi/assets/style/dynamic.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/style/dynamic.css -------------------------------------------------------------------------------- /webapi/assets/style/idangerous.swiper.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/langhuihui/paper-trade/936aa4ef8b498cb77ceb3447ca65afd96796534b/webapi/assets/style/idangerous.swiper.css -------------------------------------------------------------------------------- /webapi/assets/style/loading.css: -------------------------------------------------------------------------------- 1 |  2 | #mainld{ 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | background-color: #f5f7f9; 9 | z-index:10000000; 10 | } 11 | 12 | #mainld .loading{ 13 | position: absolute; 14 | top: 38%; 15 | left: 40%; 16 | width: 20%; 17 | text-align: center; 18 | margin:0; 19 | padding: 0; 20 | } 21 | 22 | #mainld .loading .iconRound{ 23 | margin:0; 24 | padding:0; 25 | width: 100%; 26 | vertical-align: middle; 27 | } 28 | 29 | .animation{ 30 | animation-name: roateD; 31 | animation-duration: 1.3s; 32 | animation-timing-function: linear; 33 | animation-delay: 200ms; 34 | animation-iteration-count: infinite; 35 | animation-direction: normal; 36 | animation-play-state: running; 37 | /* Firefox: */ 38 | -moz-animation-name: roateD; 39 | -moz-animation-duration: 1.3s; 40 | -moz-animation-timing-function: linear; 41 | -moz-animation-delay: 200ms; 42 | -moz-animation-iteration-count: infinite; 43 | -moz-animation-direction: normal; 44 | -moz-animation-play-state: running; 45 | /* Safari 和 Chrome: */ 46 | -webkit-animation-name: roateD; 47 | -webkit-animation-duration: 1.3s; 48 | -webkit-animation-timing-function: linear; 49 | -webkit-animation-delay: 200ms; 50 | -webkit-animation-iteration-count: infinite; 51 | -webkit-animation-direction: normal; 52 | -webkit-animation-play-state: running; 53 | /* Opera: */ 54 | -o-animation-name: roateD; 55 | -o-animation-duration: 1.3s; 56 | -o-animation-timing-function: linear; 57 | -o-animation-delay: 200ms; 58 | -o-animation-iteration-count: infinite; 59 | -o-animation-direction: normal; 60 | -o-animation-play-state: running; 61 | } 62 | 63 | @keyframes roateD 64 | { 65 | 0% { 66 | transform:rotate(0); 67 | } 68 | 100% { 69 | transform:rotate(360deg); 70 | } 71 | } 72 | 73 | @-moz-keyframes roateD /* Firefox */ 74 | { 75 | 0% { 76 | -moz-transform:rotate(0); 77 | } 78 | 100% { 79 | -moz-transform:rotate(360deg); 80 | } 81 | } 82 | 83 | @-webkit-keyframes roateD /* Safari 和 Chrome */ 84 | { 85 | 0% { 86 | -webkit-transform:rotate(0); 87 | } 88 | 100% { 89 | -webkit-transform:rotate(360deg); 90 | } 91 | } 92 | 93 | @-o-keyframes roateD /* Opera */ 94 | { 95 | 0% { 96 | -o-transform:rotate(0); 97 | } 98 | 100% { 99 | -o-transform:rotate(360deg); 100 | } 101 | } 102 | 103 | 104 | 105 | .spinner { 106 | margin: 60% auto; 107 | width: 30px; 108 | height: 30px; 109 | position: relative; 110 | } 111 | 112 | .container1 > div, .container2 > div, .container3 > div { 113 | width: 8px; 114 | height: 8px; 115 | background-color: #4cd964; 116 | 117 | border-radius: 100%; 118 | position: absolute; 119 | -webkit-animation: bouncedelay 1.2s infinite ease-in-out; 120 | animation: bouncedelay 1.2s infinite ease-in-out; 121 | -webkit-animation-fill-mode: both; 122 | animation-fill-mode: both; 123 | } 124 | .container1 > div{ 125 | background-color: #8056ed; 126 | } 127 | .container2 > div{ 128 | background-color: #8056ed; 129 | } 130 | .container3 > div{ 131 | background-color: #8056ed; 132 | } 133 | .container4 > div{ 134 | background-color: #8056ed; 135 | } 136 | .container5 > div{ 137 | background-color: #8056ed; 138 | } 139 | .spinner .spinner-container { 140 | position: absolute; 141 | width: 100%; 142 | height: 100%; 143 | } 144 | 145 | .container2 { 146 | -webkit-transform: rotateZ(45deg); 147 | transform: rotateZ(45deg); 148 | } 149 | 150 | .container3 { 151 | -webkit-transform: rotateZ(90deg); 152 | transform: rotateZ(90deg); 153 | } 154 | 155 | .circle1 { top: 0; left: 0; background-color: #8056ed;} 156 | .circle2 { top: 0; right: 0; background-color: #8056ed;} 157 | .circle3 { right: 0; bottom: 0; background-color: #8056ed;} 158 | .circle4 { left: 0; bottom: 0; background-color: #8056ed;} 159 | 160 | .container2 .circle1 { 161 | -webkit-animation-delay: -1.1s; 162 | animation-delay: -1.1s; 163 | } 164 | 165 | .container3 .circle1 { 166 | -webkit-animation-delay: -1.0s; 167 | animation-delay: -1.0s; 168 | } 169 | 170 | .container1 .circle2 { 171 | -webkit-animation-delay: -0.9s; 172 | animation-delay: -0.9s; 173 | } 174 | 175 | .container2 .circle2 { 176 | -webkit-animation-delay: -0.8s; 177 | animation-delay: -0.8s; 178 | } 179 | 180 | .container3 .circle2 { 181 | -webkit-animation-delay: -0.7s; 182 | animation-delay: -0.7s; 183 | } 184 | 185 | .container1 .circle3 { 186 | -webkit-animation-delay: -0.6s; 187 | animation-delay: -0.6s; 188 | } 189 | 190 | .container2 .circle3 { 191 | -webkit-animation-delay: -0.5s; 192 | animation-delay: -0.5s; 193 | } 194 | 195 | .container3 .circle3 { 196 | -webkit-animation-delay: -0.4s; 197 | animation-delay: -0.4s; 198 | } 199 | 200 | .container1 .circle4 { 201 | -webkit-animation-delay: -0.3s; 202 | animation-delay: -0.3s; 203 | } 204 | 205 | .container2 .circle4 { 206 | -webkit-animation-delay: -0.2s; 207 | animation-delay: -0.2s; 208 | } 209 | 210 | .container3 .circle4 { 211 | -webkit-animation-delay: -0.1s; 212 | animation-delay: -0.1s; 213 | } 214 | 215 | @-webkit-keyframes bouncedelay { 216 | 0%, 80%, 100% { -webkit-transform: scale(0.0) } 217 | 40% { -webkit-transform: scale(1.0) } 218 | } 219 | 220 | @keyframes bouncedelay { 221 | 0%, 80%, 100% { 222 | transform: scale(0.0); 223 | -webkit-transform: scale(0.0); 224 | } 40% { 225 | transform: scale(1.0); 226 | -webkit-transform: scale(1.0); 227 | } 228 | } -------------------------------------------------------------------------------- /webapi/config.js: -------------------------------------------------------------------------------- 1 | let config = { 2 | port: 10002, 3 | tokenTime: 14400000, 4 | clientInitAll: { forceUpdate: "2.4", quotation: { source: "sina" }, dbVersion: 1, showRank: true }, //所有客户端配置 5 | clientInitDefault: { 6 | showTrade: true 7 | }, //默认配置 8 | clientInit: { //特定版本客户端配置 9 | "2.5": { showTrade: false } 10 | }, 11 | FinancialIndexBaseURL: "http://test.wolfstreet.tv/kmap/mark.html", 12 | FinancialIndex: { 13 | hs: [{ Title: "上证指数", URL: "?code=0000001&type=hs" }, { Title: "深证指数", URL: "?code=1399001&type=hs" }, { Title: "创业指数", URL: "?code=1399006&type=hs" }], 14 | hk: [{ Title: "恒生指数", URL: "?code=HSI&type=hk" }, { Title: "国企指数", URL: "?code=HSCEI&type=hk" }, { Title: "红筹指数", URL: "?code=HSCCI&type=hk" }], 15 | us: [{ Title: "道琼斯", URL: "?code=DOWJONES&type=us" }, { Title: "纳斯达克", URL: "?code=NASDAQ&type=us" }, { Title: "标普500", URL: "?code=SP500&type=us" }], 16 | get sz() { 17 | return this.hs 18 | }, 19 | get sh() { 20 | return this.hs 21 | } 22 | }, 23 | EveryDayURL: "http://sharetest.wolfstreet.tv/kmap/profit.html?memberCode=" 24 | } 25 | 26 | if (process.env.NODE_ENV === "production") { 27 | Object.assign(config, require('./pconfig.js')) 28 | } 29 | for (let k in config.clientInit) { 30 | config.clientInit[k] = Object.assign(Object.assign(Object.assign({}, config.clientInitDefault), config.clientInit[k]), config.clientInitAll) 31 | } 32 | (() => { 33 | let { hs, hk, us } = config.FinancialIndex; 34 | [].concat(hs, hk, us).forEach(x => x.URL = config.FinancialIndexBaseURL + x.URL) 35 | })() 36 | export default config -------------------------------------------------------------------------------- /webapi/index.js: -------------------------------------------------------------------------------- 1 | import checkToken from './middles/checkToken' 2 | import checkEmpty from './middles/checkEmpty' 3 | import checkNum from './middles/checkNum' 4 | import Statistic from './statistic' 5 | import express from 'express' 6 | import path from 'path' 7 | import bodyParser from 'body-parser' 8 | import Config from '../config' 9 | import config from './config' 10 | import amqp from 'amqplib' 11 | import sqlstr from '../common/sqlStr' 12 | import singleton from '../common/singleton' 13 | const { mainDB, redisClient, rongcloud } = singleton 14 | 15 | const app = express(); 16 | app.use(bodyParser.json()) 17 | app.use(bodyParser.urlencoded({ extended: true })); 18 | app.engine('jshtml', require('jshtml-express')); 19 | app.set('view engine', 'jshtml'); 20 | app.set('views', path.resolve(__dirname, 'views')) 21 | app.use('/h5', express.static(path.resolve(__dirname, 'assets'))) 22 | if (Config.test) app.use('/admin', express.static(path.resolve(__dirname, 'web', 'dist'))) 23 | app.use((req, res, next) => { 24 | if (req.body.Token) delete req.body.Token 25 | next() 26 | }) 27 | const statistic = new Statistic() 28 | const wrap = fn => (...args) => fn(...args).catch(err => args[2] ? args[2](err) : console.error(new Date(), err)) 29 | let shareData = { config: Config, wrap, rongcloud, statistic, express, checkEmpty, checkNum, mainDB, redisClient, ctt: checkToken(true), ctf: checkToken(false) } //路由中的共享数据 30 | async function startMQ() { 31 | var amqpConnection = await amqp.connect(Config.amqpConn) 32 | let channel = await amqpConnection.createChannel() 33 | let ok = await channel.assertQueue('priceNotify') 34 | shareData.mqChannel = channel 35 | shareData.realDB = await singleton.getRealDB() 36 | app.use('/h5', require('./routes/h5')(shareData)) 37 | app.use('/v2.5/Home', require('./routes/homePage')(shareData)) 38 | app.use('/v2.5/Trade', require('./routes/trade')(shareData)) 39 | app.use('/v2.5/Personal', require('./routes/personal')(shareData)) 40 | app.use('/v2.5/ImageTalk', require('./routes/imageTalk')(shareData)) 41 | app.use('/v2.5/Choiceness', require('./routes/choiceness')(shareData)) 42 | app.use('/v2.5/DriveWealth', require('./routes/drivewealth')(shareData)) 43 | app.use('/v2.5/Video', require('./routes/video')(shareData)) 44 | app.use('/v2.5/Game', require('./routes/stockcompetition')(shareData)) 45 | app.use('/v2.5/PaperTrade', require('./routes/paperTrade')(shareData)) 46 | app.use('/v2.5/Statistics', require('./routes/statistics')(shareData)) 47 | app.use('/v2.7/User', require('./routes/user')(shareData)) 48 | if (Config.test) app.use('/admin', require('./routes/admin')(shareData)) 49 | /**全局错误处理 */ 50 | app.use((err, req, res, next) => { 51 | console.error(err.stack); 52 | res.send({ Status: 500, Explain: err.stack }) 53 | }) 54 | } 55 | startMQ(); 56 | 57 | /**客户端初始化配置 */ 58 | app.get('/System/GetConfig', checkEmpty('version'), wrap(async({ query: { version, dbVersion, memberCode, UUID, IMEI = "", token } }, res) => { 59 | let TokenValid = false 60 | if (token) { 61 | let { ValidityTime = false } = await singleton.selectMainDB0("wf_token", { TokenValue: token }) 62 | TokenValid = ValidityTime && (Date.parse(ValidityTime) + config.tokenTime * 60 > new Date().getTime()) 63 | } 64 | let setting = version && config.clientInit[version] ? Object.assign({}, config.clientInit[version]) : Object.assign(Object.assign({}, config.clientInitDefault), config.clientInitAll) 65 | if (dbVersion) { 66 | let [dbResult] = await mainDB.query('select * from wf_securities_version where Versions>:dbVersion order by Versions asc', { replacements: { dbVersion } }) 67 | if (dbResult.length) { 68 | //let maxVersion = dbResult.last.Versions 69 | dbResult = dbResult.map(x => x.Content) 70 | setting.updateSQL = dbResult.join(''); 71 | } 72 | } 73 | res.send({ Status: 0, Explain: "", Config: setting, TokenValid }) 74 | //埋点 75 | statistic.login({ LoginId: memberCode ? memberCode : (UUID ? UUID : IMEI), DataSource: UUID ? "ios" : "android", AppVersion: version, IsLogin: memberCode != null }) 76 | })); 77 | 78 | 79 | let server = app.listen(config.port, () => { 80 | let host = server.address().address; 81 | let port = server.address().port; 82 | console.log('server listening at %s %d', host, port); 83 | }); -------------------------------------------------------------------------------- /webapi/middles/allowAccess.js: -------------------------------------------------------------------------------- 1 | /**设置跨域问题 */ 2 | import Config from '../../config' 3 | 4 | function allowAccess(method = "GET") { 5 | return function(req, res, next) { 6 | res.set("Access-Control-Allow-Origin", Config.ajaxOrigin); 7 | //res.set("Access-Control-Allow-Headers", "X-Requested-With"); 8 | res.set("Access-Control-Allow-Methods", method); 9 | next() 10 | } 11 | } 12 | export default allowAccess -------------------------------------------------------------------------------- /webapi/middles/checkEmpty.js: -------------------------------------------------------------------------------- 1 | /**检查参数是否为空 */ 2 | function checkEmpty(...args) { 3 | return function(req, res, next) { 4 | for (let arg of args) { 5 | if (req.query[arg] == undefined || req.query[arg] === "") { 6 | res.send({ Status: 40002, Explain: arg + "不能为空!" }) 7 | return 8 | } 9 | } 10 | next() 11 | } 12 | } 13 | export default checkEmpty -------------------------------------------------------------------------------- /webapi/middles/checkNum.js: -------------------------------------------------------------------------------- 1 | /**检测是否是数字 */ 2 | export default (...args) => { 3 | return (req, res, next) => { 4 | for (let arg of args) { 5 | if (req.query[arg] != undefined) { 6 | if (Number.isNaN(req.query[arg] = Number(req.query[arg]))) { 7 | res.send({ Status: 40003, Explain: arg + "参数类型应该是数字,您传的是:" + JSON.stringify(req.query) }) 8 | return 9 | } 10 | } 11 | } 12 | next() 13 | } 14 | } -------------------------------------------------------------------------------- /webapi/middles/checkToken.js: -------------------------------------------------------------------------------- 1 | import Config from '../../config' 2 | import config from '../config' 3 | import singleton from '../../common/singleton' 4 | const { mainDB } = singleton 5 | const tokenSql = "SELECT wf_token.TokenID,wf_token.ClientType,wf_token.MemberCode,wf_token.TokenValue,wf_token.ValidityTime,wf_member.Status FROM wf_token LEFT JOIN wf_member ON wf_member.MemberCode=wf_token.MemberCode WHERE wf_token.TokenValue=?"; 6 | const updateTokenSql = "UPDATE wf_token set ValidityTime=? WHERE TokenID=?"; 7 | 8 | function updateToken(tokenId) { 9 | mainDB.query(updateTokenSql, { replacements: [new Date(), tokenId] }) 10 | } 11 | async function checkToken(token, isLogin) { 12 | let result = 0 13 | let memberCode = "" 14 | let tokenModel = await mainDB.query(tokenSql, { replacements: [token], type: "SELECT" }) 15 | tokenModel = tokenModel[0] 16 | if (isLogin) { 17 | if (!token) { 18 | result = { Status: 40012, Explain: "Token 不能为空" } 19 | } else { 20 | if (tokenModel) { 21 | memberCode = tokenModel.MemberCode 22 | if (Date.parse(tokenModel.ValidityTime) + config.tokenTime * 60 < new Date().getTime()) { 23 | result = { Status: 40012, Explain: "您还没有登录,请登录后操作" } 24 | } else if (tokenModel.Status != 1) { 25 | result = { Status: 40012, Explain: "您的账号已经停用,如有疑问请联系客服!" } 26 | } else { 27 | updateToken(tokenModel.TokenID) 28 | } 29 | } else { 30 | result = { Status: 40012, Explain: "您的登录已丢失,请重新登录" } 31 | } 32 | } 33 | } else { 34 | memberCode = tokenMode.MemberCode 35 | updateToken(tokenModel.TokenID) 36 | } 37 | return { result, memberCode, token } 38 | } 39 | //token验证中间件 40 | 41 | function checkLogin(isLogin) { 42 | return async function(req, res, next) { 43 | let { result, memberCode, token } = await checkToken(req.header('Token') || req.params.Token, isLogin) 44 | req.memberCode = memberCode 45 | req.token = token 46 | if (result === 0) next() 47 | else res.send(result) 48 | } 49 | } 50 | export default checkLogin -------------------------------------------------------------------------------- /webapi/routes/admin.js: -------------------------------------------------------------------------------- 1 | import sqlstr from '../../common/sqlStr' 2 | import _config from '../config' 3 | import pm2 from 'pm2' 4 | module.exports = function({ config, mainDB, ctt, express, checkEmpty, mqChannel, redisClient }) { 5 | const router = express.Router(); 6 | router.get('/token/:memberCode', async(req, res) => { 7 | let [result] = await mainDB.query('select * from wf_token where MemberCode=:memberCode', { replacements: req.params }) 8 | if (result.length) 9 | res.send(result[0]["TokenValue"]) 10 | else res.send("") 11 | }) 12 | router.get('/config', (req, res) => { 13 | res.send(config) 14 | }) 15 | router.get('/webConfig', (req, res) => { 16 | res.send(_config) 17 | }) 18 | router.get('/pm2/list', (req, res) => { 19 | pm2.connect(err => { 20 | if (err) { 21 | res.send("error") 22 | return 23 | } 24 | pm2.list((err2, processDescriptionList) => { 25 | res.send(processDescriptionList) 26 | pm2.disconnect() 27 | }) 28 | }) 29 | }) 30 | return router 31 | } -------------------------------------------------------------------------------- /webapi/routes/choiceness.js: -------------------------------------------------------------------------------- 1 | module.exports = function({ mainDB, statistic, ctt, express, config, wrap, redisClient }) { 2 | const router = express.Router(); 3 | /**获取精选头部列表 */ 4 | router.get('/ChoicenessBannerList', wrap(async(req, res) => { 5 | let result = await redisClient.getAsync("cacheResult:bannerChoice") 6 | res.send('{"Status":0,"Explain":"ok","Data":' + result + '}') 7 | })); 8 | /**获取精选列 */ 9 | router.get('/ChoicenessList', wrap(async(req, res) => { 10 | let result = await redisClient.getAsync("cacheResult:normalChoice"); 11 | res.send('{"Status":0,"Explain":"ok","Data":' + result + '}') 12 | })) 13 | return router 14 | } -------------------------------------------------------------------------------- /webapi/routes/drivewealth.js: -------------------------------------------------------------------------------- 1 | import { encrypt, decrypt } from '../../common/aes' 2 | import sqlstr from '../../common/sqlStr' 3 | import singleton from '../../common/singleton' 4 | module.exports = function({ config, mainDB, ctt, express, checkEmpty, mqChannel, redisClient, wrap }) { 5 | const router = express.Router(); 6 | /**创建嘉维真实账户 */ 7 | router.post('/CreateAccount', ctt, wrap(async({ memberCode, body: data }, res) => { 8 | data.MemberCode = memberCode 9 | data.password = encrypt(data.password) 10 | data.phoneHome = encrypt(data.phoneHome) 11 | data.idNo = encrypt(data.idNo) 12 | let result = await mainDB.query("select * from wf_drivewealth_account where MemberCode=:MemberCode and UserId=:userID", { replacements: data, type: "SELECT" }) 13 | if (result.length) { 14 | ([result] = await mainDB.query(...sqlstr.update2("wf_drivewealth_account", data, { AccountId: null, CreateTime: "now()" }, "where AccountId=:AccountId"))) 15 | } else 16 | ([result] = await mainDB.query(...sqlstr.insert2("wf_drivewealth_account", data, { CreateTime: "now()" }))) 17 | res.send({ Status: 0, Explain: "", Result: result }) 18 | })) 19 | return router 20 | } -------------------------------------------------------------------------------- /webapi/routes/h5.js: -------------------------------------------------------------------------------- 1 | import singleton from '../../common/singleton' 2 | import WechatAPI from 'wechat-api'; 3 | const reg_iOS = /\(i[^;]+;( U;)? CPU.+Mac OS X/ 4 | module.exports = function({ mainDB, ctt, express, config, wrap }) { 5 | const router = express.Router(); 6 | const wechat = new WechatAPI(config.jssdk_appId, config.jssdk_appSecret); 7 | const getWechatJsConfig = function(url) { 8 | var param = { 9 | debug: false, 10 | jsApiList: ['onMenuShareTimeline', 'onMenuShareAppMessage'], 11 | url: url 12 | }; 13 | return new Promise(function(resolve, reject) { 14 | wechat.getJsConfig(param, function(err, data) { 15 | if (err) return reject(err); 16 | resolve(data); 17 | }); 18 | }) 19 | } 20 | router.get('/Article/:Id', async function({ params: { Id }, headers }, res) { 21 | Id = Number(Id) 22 | let article = await singleton.selectMainDB0("wf_competition_affiche", { Id }) 23 | res.locals = { 24 | adminHost: config.adminHostURL, 25 | content: article.Content.replace(/(src=\"[^\"]+\")/g, "src=\"http://share.wolfstreet.tv/wffenxiang/img/LOGO@3x.png\" d$1") 26 | }; 27 | if (reg_iOS.exec(headers['user-agent'])) 28 | res.render('articleIOS'); 29 | else 30 | res.render('articleAndroid'); 31 | }); 32 | router.get('/get-signature', function(req, res) { 33 | console.log(req) 34 | wx.jssdk.getSignature(req.url).then(function(signatureData) { 35 | res.json(signatureDate); 36 | }); 37 | }); 38 | router.get('/BattleReport/:Id', wrap(async(req, res) => { 39 | let { url, protocol, originalUrl, params: { Id }, headers } = req 40 | Id = Number(Id) 41 | let Data = await singleton.selectMainDB0("wf_competition_report", { Id }); 42 | let startDate = new Date(new Date("2017-7-4 00:00:00").setDate(4 + (Data.Period - 1) * 7)); 43 | let endDate = new Date(new Date("2017-7-4 00:00:00").setDate(4 + Data.Period * 7)); 44 | Data.ProfitDaily = await mainDB.query("select WeekYield profit,DATE_FORMAT(EndDate,'%Y%m%d') as date from wf_drivewealth_practice_asset_v where MemberCode=:MemberCode and EndDate between :startDate and :endDate", { replacements: { MemberCode: Data.MemberCode, startDate, endDate }, type: "SELECT" }) 45 | if (Data.ProfitDaily[Data.ProfitDaily.length - 1].profit > Data.ProfitDaily[Data.ProfitDaily.length - 2].profit) { 46 | Data.ProfitTitle = "华尔街上人密集,常有散户较高低,当年小白勤练习,而今成为老司机" 47 | } else 48 | Data.ProfitTitle = "枯藤老树昏鸦,空调wifi西瓜,以为轻松会发,不料自己眼瞎" 49 | if (Data.TeamId) { 50 | Data.Member = await mainDB.query("select NickName,WeekYield,concat(:picBaseURL,case when isnull(HeadImage) or HeadImage='' then :defaultHeadImage else HeadImage end)HeadImage from wf_competition_report where TeamId=:TeamId and Period=:Period order by WeekYield desc", { type: "SELECT", replacements: { TeamId: Data.TeamId, Period: Data.Period, picBaseURL: config.picBaseURL, defaultHeadImage: config.defaultHeadImage } }) 51 | Data.TeamProfitDaily = await mainDB.query("select AvgYield profit,DATE_FORMAT(EndDate,'%Y%m%d') as date from wf_competition_team_asset where TeamId=:TeamId ", { replacements: { TeamId: Data.TeamId }, type: "SELECT" }) 52 | } 53 | if (!Data.MinSecuritiesName) Data.MinSecuritiesNo = Data.MinSecuritiesName = "无" 54 | if (Data.HeadImage) { 55 | Data.HeadImage = config.picBaseURL + Data.HeadImage 56 | } else { 57 | Data.HeadImage = config.defaultHeadImage 58 | } 59 | res.locals = Data 60 | var fullUrl = config.shareHostURL + originalUrl 61 | fullUrl = fullUrl.replace('h5', 'nodeh5') 62 | let signatureData = await getWechatJsConfig(fullUrl); 63 | signatureData.appId = config.jssdk_appId; 64 | signatureData.title = `第${Data.Period}周战报`; 65 | signatureData.description = `沃夫界模拟交易大赛${Data.Period}第${Data.Period}周战报`; 66 | signatureData.imgUrl = Data.HeadImage; 67 | signatureData.link = fullUrl; 68 | res.locals.signatureData = signatureData; 69 | res.render('battleReport'); 70 | })) 71 | return router 72 | } -------------------------------------------------------------------------------- /webapi/routes/homePage.js: -------------------------------------------------------------------------------- 1 | module.exports = function({ mainDB, ctt, express, config, wrap }) { 2 | const router = express.Router(); 3 | router.get('/Banner', ctt, wrap(async(req, res) => { 4 | let [result] = await mainDB.query("select Title,ActionType,concat(:picBaseURL,ImageUrl) ImageUrl,(case when ActionType='url' then concat(ActionTarget,'?memberCode=',:memberCode) when ActionType='image' then concat(:adminHost,FocusmapID) when ActionType='H5' then concat(ActionTarget,'?utoken=',:token,'&t=',unix_timestamp(now())) else ActionTarget end) ActionTarget from wf_ad_focusmap where Status=0", { replacements: { picBaseURL: config.picBaseURL, memberCode: req.memberCode, adminHost: config.adminHostURL + "/bannerlink/index.php?FocusmapID=", token: req.token } }) 5 | res.send({ Status: 0, Explain: "", DataList: result }); 6 | 7 | })) 8 | 9 | router.get('/AdPush', ctt, wrap(async(req, res) => { 10 | let result = await mainDB.query("select Id,ActionType,ActionTarget,concat(:picBaseURL,ImageUrl) ImageUrl from wf_ad_push where Status=0", { replacements: { picBaseURL: config.picBaseURL }, type: "SELECT" }) 11 | res.send({ Status: 0, Explain: "", Data: result[0] }); 12 | })) 13 | return router 14 | } -------------------------------------------------------------------------------- /webapi/routes/imageTalk.js: -------------------------------------------------------------------------------- 1 | module.exports = function({ mainDB, statistic, ctt, express, config, wrap }) { 2 | const router = express.Router(); 3 | /**图说详情 */ 4 | router.get('/Detail/:code', ctt, wrap(async({ params: replacements, memberCode }, res) => { 5 | replacements.memberCode = memberCode 6 | replacements.picBaseURL = config.picBaseURL 7 | let [result] = await mainDB.query("select *,LikeCount LikesCount,concat(:picBaseURL,Original_Image) Composite_Image from wf_imagetext where Status=1 and `Code`=:code", { replacements }) 8 | if (result.length) { 9 | let [it] = result 10 | //点赞数和我是否已经点赞 11 | let [likeCount] = await mainDB.query(`select count(*) myLikes from wf_imagetext_likes where ITCode=:code and CreateUser=:memberCode`, { replacements }) 12 | it.IsLikes = likeCount[0]["myLikes"] > 0; 13 | ([result] = await mainDB.query('select `Code` from wf_imagetext where Id<:Id and Status=1 order by Id desc limit 1', { replacements: it })); 14 | if (result.length)([{ Code: it.NextCode }] = result); 15 | else it.NextCode = ""; 16 | ([result] = await mainDB.query('select `Code` from wf_imagetext where Id>:Id and Status=1 limit 1', { replacements: it })); 17 | if (result.length)([{ Code: it.LastCode }] = result); 18 | else it.LastCode = ""; 19 | //埋点 20 | statistic.page({ LoginId: memberCode, TypeId: 18, PageId: it.Id, IsLogin: true }) 21 | Object.deleteProperties(it, "Id", "Original_Image", "Thumbnail", "Status", "State", "MemberCode", "CreateTime", "LikeCount") 22 | res.send({ Status: 0, Explain: "", Data: it }) 23 | } else { 24 | res.send({ Status: -1, Explain: "该图说不存在!" }) 25 | } 26 | })); 27 | /**删除我发布的图说 */ 28 | router.delete('/Delete', ctt, wrap(async({ query: { code }, memberCode }, res) => { 29 | if (!code) { 30 | res.send({ Status: 40002, Explain: "Code不能为空!" }) 31 | return 32 | } 33 | let [result] = await mainDB.query("update wf_imagetext set Status=0 where Status=1 and `Code`=:code and MemberCode=:memberCode", { replacements: { code, memberCode } }) 34 | res.send({ Status: 0, Explain: "成功", Data: result }) 35 | })) 36 | return router 37 | } -------------------------------------------------------------------------------- /webapi/routes/statistics.js: -------------------------------------------------------------------------------- 1 | import sqlstr from '../../common/sqlStr' 2 | import singleton from '../../common/singleton' 3 | import _config from '../config' 4 | 5 | module.exports = function({ mainDB, statistic, ctt, express, config, wrap }) { 6 | const router = express.Router(); 7 | /**页面停留时间埋点 */ 8 | router.post('/StayTimeStatistics', ctt, wrap(async({ memberCode, body }, res) => { 9 | //res.end() 10 | let result = {} 11 | body.LoginId = memberCode 12 | body.IsLogin = 1 13 | switch (body.PageType) { 14 | case "news": //资讯类 15 | switch (parseInt(body.TypeId)) { 16 | case 13: //投票 17 | result = await mainDB.query("select VoteId from wf_vote where VoteCode=:VoteCode", { replacements: { VoteCode: body.PageId }, type: "SELECT" }) 18 | if (result.length) { 19 | body.PageId = result[0].VoteId 20 | statistic.pageStay(body) 21 | } 22 | break 23 | case 14: //书籍 24 | result = await mainDB.query("select Id from wf_books where Code=:Code", { replacements: { Code: body.PageId }, type: "SELECT" }) 25 | if (result.length) { 26 | body.PageId = result[0].Id 27 | statistic.pageStay(body) 28 | } 29 | break 30 | case 15: //专题 31 | result = await mainDB.query("select Id from wf_dissertation_type where Code=:Code", { replacements: { Code: body.PageId }, type: "SELECT" }) 32 | if (result.length) { 33 | body.PageId = result[0].Id 34 | statistic.pageStay(body) 35 | } 36 | break 37 | case 16: //栏目 38 | result = await mainDB.query("select ColumnId from wf_news_column where ColumnNo=:ColumnNo", { replacements: { ColumnNo: body.PageId }, type: "SELECT" }) 39 | if (result.length) { 40 | body.PageId = result[0].ColumnId 41 | statistic.pageStay(body) 42 | } 43 | break 44 | case 17: //精选 45 | result = await mainDB.query("select Id from wf_choiceness where Code=:Code", { replacements: { Code: body.PageId }, type: "SELECT" }) 46 | if (result.length) { 47 | body.PageId = result[0].Id 48 | statistic.pageStay(body) 49 | } 50 | break 51 | case 18: //图说 52 | result = await mainDB.query("select Id from wf_imagetext where Code=:Code", { replacements: { Code: body.PageId }, type: "SELECT" }) 53 | if (result.length) { 54 | body.PageId = result[0].Id 55 | statistic.pageStay(body) 56 | } 57 | break 58 | case 19: //资讯 59 | result = await mainDB.query("select Id from wf_news where Code=:Code", { replacements: { Code: body.PageId }, type: "SELECT" }) 60 | if (result.length) { 61 | body.PageId = result[0].Id 62 | statistic.pageStay(body) 63 | } 64 | break 65 | case 20: //视频 66 | result = await mainDB.query("select VideoId from wf_live_video where VideoCode=:VideoCode", { replacements: { VideoCode: body.PageId }, type: "SELECT" }) 67 | if (result.length) { 68 | body.PageId = result[0].VideoId 69 | statistic.pageStay(body) 70 | } 71 | break 72 | } 73 | break 74 | case "stock": //股票详情页面停留时间 75 | statistic.stockPageStay(body) 76 | break 77 | } 78 | res.send({ Status: 0, Explain: "" }) 79 | })); 80 | /**股票详情页面埋点 */ 81 | router.post('/Module', ctt, wrap(async({ body, memberCode }, res) => { 82 | body.LoginId = memberCode 83 | body.IsLogin = 1 84 | statistic.module(body) 85 | res.send({ Status: 0, Explain: "" }) 86 | })); 87 | return router 88 | } -------------------------------------------------------------------------------- /webapi/routes/trade.js: -------------------------------------------------------------------------------- 1 | import sqlstr from '../../common/sqlStr' 2 | import singleton from '../../common/singleton' 3 | import _config from '../config' 4 | import allowAccess from '../middles/allowAccess' 5 | module.exports = function({ config, mainDB, realDB, ctt, express, checkEmpty, mqChannel, redisClient, rongcloud, wrap }) { 6 | const router = express.Router(); 7 | /**是否开市*/ 8 | router.get('/:type/IsOpen', wrap(async({ params: { type } }, res) => { 9 | res.send({ Status: 0, Explain: "", IsOpen: await singleton.marketIsOpen(type) }) 10 | })); 11 | // /**是否已经绑定(创建)嘉维账户 */ 12 | // router.get('/IsDwAccCreated', ctt, async(req, res) => { 13 | // let memberCode = req.memberCode 14 | // let [result] = await mainDB.query("select userID,username,emailAddress1 from wf_drivewealth_user where MemberCode=:memberCode", { replacements: { memberCode } }) 15 | 16 | // res.send({ Status: 0, Explain: "", IsDwAccCreated: result.length }) 17 | // }); 18 | /**获取股价提醒 */ 19 | router.get('/GetPriceNotify/:SmallType/:SecuritiesNo', ctt, async(req, res) => { 20 | let replacements = req.params 21 | replacements.MemberCode = req.memberCode 22 | let result = await mainDB.query("select * from wf_securities_remind where MemberCode=:MemberCode and SecuritiesNo=:SecuritiesNo and SmallType=:SmallType limit 1", { replacements, type: "SELECT" }) 23 | if (result.length) 24 | res.send({ Status: 0, Explain: "", Data: result[0] }) 25 | else res.send({ Status: 0, Explain: "", Data: { IsOpenLower: false, IsOpenUpper: false, IsOpenRise: false, IsOpenFall: false, LowerLimit: 0, UpperLimit: 0, FallLimit: 0, RiseLimit: 0 } }) 26 | }); 27 | /**修改股价提醒 */ 28 | router.put('/SetPriceNotify', ctt, async(req, res) => { 29 | let replacements = req.body 30 | replacements.MemberCode = req.memberCode 31 | let [result] = await mainDB.query("select * from wf_securities_remind where MemberCode=:MemberCode and SecuritiesNo=:SecuritiesNo and SmallType=:SmallType limit 1", { replacements }) 32 | if (result.length) { 33 | ([{ RemindId: replacements.RemindId }] = result); 34 | result = await mainDB.query(sqlstr.update("wf_securities_remind", replacements, { RemindId: null, MemberCode: null, SecuritiesNo: null, SmallType: null }) + "where RemindId=:RemindId", { replacements }) 35 | } else { 36 | result = await mainDB.query(...sqlstr.insert2("wf_securities_remind", replacements, { RemindId: null, CreateTime: "Now()" })); 37 | replacements.RemindId = result[0].insertId 38 | } 39 | res.send({ Status: 0, Explain: "", Result: result }) 40 | mqChannel.sendToQueue("priceNotify", new Buffer(JSON.stringify({ cmd: "update", data: replacements }))) 41 | }); 42 | /**排行榜 */ 43 | router.get('/RankList/:type', allowAccess(), async(req, res) => { 44 | switch (req.params.type) { 45 | case "TotalAmount": 46 | res.set('Content-Type', 'application/json').send(`{ "Status": 0, "Explain": "", "DataList": ${await redisClient.getAsync("RankList:totalAssets")} }`) 47 | break 48 | case "TodayProfit": 49 | res.set('Content-Type', 'application/json').send(`{ "Status": 0, "Explain": "", "DataList": ${await redisClient.getAsync("RankList:todayProfit")} }`) 50 | break 51 | case "TotalPrifitV": //炒股大赛总排行 52 | //缓存炒股大赛总排行 53 | let [matchTotalProfitReulst] = await mainDB.query("SELECT c.*,wf_member.NickName,concat(:picBaseURL,case when isnull(wf_member.HeadImage) or wf_member.HeadImage='' then :defaultHeadImage else wf_member.HeadImage end)HeadImage FROM (SELECT a.RankValue totalamount,b.RankValue totalprofit,a.MemberCode,a.Rank from wf_drivewealth_practice_rank_v a ,wf_drivewealth_practice_rank_v b where a.MemberCode = b.MemberCode and a.Type = 11 and b.Type = 10 limit 100)c left join wf_member on wf_member.MemberCode=c.MemberCode ORDER BY c.rank ", { replacements: { picBaseURL: config.picBaseURL, defaultHeadImage: config.defaultHeadImage } }) 54 | res.set('Content-Type', 'application/json').send({ Status: 0, Explain: "", DataList: matchTotalProfitReulst }) 55 | //res.set('Content-Type', 'application/json').send(`{ "Status": 0, "Explain": "", "DataList": ${await redisClient.getAsync("RankList:matchTotalProfit")} }`) 56 | break 57 | case "WeekProfitV": //炒股大赛周排行 58 | //缓存炒股大赛周排行 59 | let [matchWeekProfitReulst] = await mainDB.query("SELECT c.*,wf_member.NickName,concat(:picBaseURL,case when isnull(wf_member.HeadImage) or wf_member.HeadImage='' then :defaultHeadImage else wf_member.HeadImage end)HeadImage FROM (SELECT a.RankValue totalamount,b.RankValue totalprofit,a.MemberCode,a.Rank from wf_drivewealth_practice_rank_v a ,wf_drivewealth_practice_rank_v b where a.MemberCode = b.MemberCode and a.Type = 3 and b.Type = 4 limit 100)c left join wf_member on wf_member.MemberCode=c.MemberCode ORDER BY c.rank ", { replacements: { picBaseURL: config.picBaseURL, defaultHeadImage: config.defaultHeadImage } }) 60 | res.set('Content-Type', 'application/json').send({ Status: 0, Explain: "", DataList: matchWeekProfitReulst }) 61 | //res.set('Content-Type', 'application/json').send(`{ "Status": 0, "Explain": "", "DataList": ${await redisClient.getAsync("RankList:matchWeekProfit")} }`) 62 | break 63 | default: 64 | res.send({ Status: 40003, Explain: "未知类型" }) 65 | } 66 | }); 67 | /**是否可以交易,是否属于嘉维*/ 68 | router.get('/StockCanTrade/:stockcode', async(req, res) => { 69 | let { stockcode } = req.params 70 | let [result] = await mainDB.query("select * from wf_securities_trade where remark='DW' and SecuritiesNo=:stockcode", { replacements: { stockcode } }) 71 | res.send({ Status: 0, Explain: "", Result: result.length > 0 }) 72 | 73 | }); 74 | /**获取金融指数的名称和h5链接地址 */ 75 | router.get('/FinancialIndex/:type', async(req, res) => { 76 | let data = _config.FinancialIndex[req.params.type] 77 | if (data) res.send({ Status: 0, Explain: "", DataList: data }) 78 | else res.send({ Status: 40003, Explain: "未知类型", }) 79 | }); 80 | /**新增股票详情评论 */ 81 | router.post('/AddQuotationComment', ctt, async(req, res) => { 82 | let replacements = Object.filterProperties(req.body, "StockCode", "StockType", "ParentID", "Content", "IsDelete") 83 | rongcloud.message.chatroom.publish("999999999", replacements.StockType + replacements.StockCode, "RC:TxtMsg", JSON.stringify({ content: "comment", extra: replacements.Content }), (err, result) => { 84 | if (err) console.error(err) 85 | else console.log(result) 86 | }) 87 | replacements.CreateUser = req.memberCode 88 | let result = await mainDB.query(sqlstr.insert("wf_quotation_comment", replacements, { Id: null, CreateTime: "Now()", IsDelelte: 0 }), { replacements }) 89 | res.send({ Status: 0, Explain: "", Result: result }) 90 | }); 91 | /**删除评论股票详情评论 */ 92 | router.delete('/DelQuotationComment/:id', ctt, async(req, res) => { 93 | let [result] = await mainDB.query("update wf_quotation_comment set isdelete=1 where id=:id", { replacements: req.params }); 94 | res.send({ Status: 0, Explain: "", Result: result.length > 0 }) 95 | }); 96 | //获取此股票的所有评论 97 | router.get('/GetQuotationCommentList/:StockType/:StockCode', async(req, res) => { 98 | let replacements = req.params 99 | replacements.picBaseURL = config.picBaseURL 100 | let [result] = await mainDB.query("select wf_quotation_comment.*,DATE_FORMAT(wf_quotation_comment.CreateTime,'%Y-%m-%d %H:%i:%s') CreateTime,wf_member.NickName,concat(:picBaseURL,wf_member.headimage)HeadImage,IFNULL(wf_member.SchoolName,'')SchoolName from wf_quotation_comment left join wf_member on wf_member.membercode=wf_quotation_comment.CreateUser where isdelete=0 and StockCode=:StockCode and StockType=:StockType order by wf_quotation_comment.id desc", { replacements }); 101 | res.send({ Status: 0, Explain: "", DataList: result }) 102 | }); 103 | //查询中概股排行榜 104 | router.all('/QuotationRank/:type/:order', wrap(async(req, res) => { 105 | let currentSRT = await redisClient.getAsync("currentSRT") 106 | let result = await realDB.collection(currentSRT).find({ ShowType: req.params.type }, { _id: 0 }).sort({ RiseFallRange: req.params.order == "desc" ? -1 : 1 }).toArray() 107 | //let [result] = await mainDB.query("select * from " + currentSRT + " where ShowType=:type order by RiseFallRange " + req.params.order, { replacements: req.params }) 108 | res.send({ Status: 0, Explain: "", DataList: result }) 109 | })); 110 | //用中文搜索股票 111 | router.get('/SearchStock/:searchword', wrap(async(req, res) => { 112 | let searchword = "%" + req.params.searchword + "%" 113 | let [result] = await mainDB.query("SELECT SecuritiesNo,SecuritiesName from wf_securities_trade where Remark='DW' and (UPPER(SecuritiesName) like :searchword or UPPER(PinYin) like UPPER(:searchword) or UPPER(SecuritiesNo) like UPPER(:searchword))", { replacements: { searchword } }) 114 | res.send({ Status: 0, Explain: "", DataList: result }) 115 | })); 116 | //股票整体排序 117 | router.post('/QuotationUsRank/:ShowType/:Order/:Limit', ctt, wrap(async({ params: { ShowType, Order, Limit } }, res) => { 118 | let limit = Number(Limit) 119 | let currentUSrankCname = await redisClient.getAsync("currentUSSRT") 120 | console.log(currentUSrankCname) 121 | let result = {} 122 | switch (ShowType) { 123 | case "ALL": 124 | result = await realDB.collection(currentUSrankCname).find({ hasNewPrice: 1 }, { _id: 0, sort: { RiseFallRange: Order == "desc" ? -1 : 1 }, limit }).toArray() 125 | break 126 | case "CCS": 127 | case "GS": 128 | case "ETF": 129 | result = await realDB.collection(currentUSrankCname).find({ hasNewPrice: 1, ShowType }, { _id: 0, sort: { RiseFallRange: Order == "desc" ? -1 : 1 }, limit }).toArray() 130 | break 131 | } 132 | res.send({ Status: 0, Explain: "", DataList: result }) 133 | })); 134 | //记录股票操作记录,包括真实和模拟 135 | router.post('/Operation', ctt, wrap(async({ memberCode, body }, res) => { 136 | let replacements = body 137 | replacements.MemberCode = memberCode 138 | let result = await mainDB.query(sqlstr.insert("wf_drivewealth_practice_order", replacements, { Id: null, CreateTime: "Now()", username: null }), { replacements }) 139 | res.send({ Status: 0, Explain: "", Result: result }) 140 | })); 141 | return router 142 | } -------------------------------------------------------------------------------- /webapi/routes/user.js: -------------------------------------------------------------------------------- 1 | import sqlstr from '../../common/sqlStr' 2 | import singleton from '../../common/singleton' 3 | import _config from '../config' 4 | import uuid from 'node-uuid' 5 | import fs from 'fs' 6 | //const gm = require('gm').subClass({ imageMagick: true }) 7 | const OpenIDField = { qq: "QQOpenID", weixin: "WeixinOpenID", weibo: "WeiboOpenID", alipay: "AlipayOpenID" } 8 | module.exports = function({ config, mainDB, realDB, ctt, express, checkEmpty, mqChannel, redisClient, rongcloud, wrap }) { 9 | const router = express.Router(); 10 | async function _Login({ body, user }, res) { 11 | if (user) { 12 | if (user.Status != 1) { 13 | return res.send({ Status: 40007, Explain: "您的账号已被停用" }) 14 | } else if (!user.Mobile) { 15 | return res.send({ Status: -2, Explain: "没有绑定手机号码", MemberCode: user.MemberCode }) 16 | } else { 17 | body.MemberCode = user.MemberCode 18 | user.HeadImage = config.picBaseURL + (user.HeadImage ? user.HeadImage : "/UploadFile/Default/default_headerimage.png") 19 | user.RoomImageUrl = user.Remark1 ? (config.picBaseURL + user.Remark1) : "" 20 | let { code, token: RongCloudToken } = await new Promise((resolve, reject) => { 21 | rongcloud.user.getToken(user.MemberCode, user.Nickname, user.HeadImage, (err, resultText) => { 22 | if (err) reject(err) 23 | else resolve(JSON.parse(resultText)) 24 | }) 25 | }); 26 | let [result] = await mainDB.query(`CALL PRC_WF_LOGIN(:MemberCode,:JpushRegID,:JpushIMEI,:JpushDeviceID,:JpushVersion,:JpushPlatform)`, { replacements: { JpushIMEI: null, JpushDeviceID: null, ClientVersion: null, ...body } }) 27 | mqChannel.sendToQueue("priceNotify", new Buffer(JSON.stringify({ cmd: "changeJpush", data: { MemberCode: user.MemberCode, JpushRegID: body.JpushRegID } }))) 28 | res.send({ Explain: "", RongCloudToken, ...user, ...result, IsAnchor: user.Remark3 == 1, IsAuthor: user.Remark2 == 1, IsBindQQ: user.QQOpenID != null, IsBindWeixin: user.WeixinOpenID != null, IsBindWeibo: user.WeiboOpenID != null, IsBindAlipay: user.AlipayOpenID != null }) 29 | } 30 | } else { 31 | //未创建账户 32 | res.send({ Status: -1, Explain: "没有账户" }) 33 | } 34 | } 35 | async function LoginThirdParty({ body, user }, res) { 36 | let { LoginType, OpenID, Nickname, ImageFormat, HeadImage, JpushRegID } = body 37 | let field = OpenIDField[LoginType] 38 | if (!field) { 39 | return res.send({ Status: 40009, Explain: "没有该登录类型" }) 40 | } 41 | if (!user) { 42 | ([user] = await mainDB.query(`select a.*,(SELECT b.Rank FROM wf_member_rank b where b.UpperValue>=a.RankValue LIMIT 1) as Rank from wf_member a where ${field} ='${OpenID}'`, { type: "SELECT" })) 43 | } 44 | _Login({ body, user }, res) 45 | } 46 | async function Login({ body, user }, res) { 47 | if (!user) { 48 | ([user] = await mainDB.query(`select a.*,(SELECT b.Rank FROM wf_member_rank b where b.UpperValue>=a.RankValue LIMIT 1) as Rank from wf_member a where (Mobile=:UserName or Email=:UserName or MemberCode=:UserName) and LoginPwd=:LoginPwd limit 1`, { type: "SELECT", replacements: body })); 49 | if (!user) return res.send({ Status: 40006, Explain: "用户名或密码不正确" }) 50 | } 51 | _Login({ body, user }, res) 52 | } 53 | router.post('/Login', wrap(Login)); 54 | /**第三方登录 */ 55 | router.post('/LoginThirdParty', wrap(LoginThirdParty)); 56 | /**绑定手机号码——注册账号 */ 57 | router.post('/Register', wrap(async(req, res) => { 58 | let field = OpenIDField[req.body.LoginType] 59 | if (req.body.LoginType) { 60 | let user = await singleton.selectMainDB0("wf_member", { 61 | [field]: req.body.OpenID 62 | }) 63 | let { MemberCode, Mobile } = user 64 | if (MemberCode) { 65 | if (Mobile) { 66 | return res.send({ Status: 1 }) 67 | } 68 | let [{ P_RESULT }] = await mainDB.query("CALL PRC_WF_BIND_MOBILE(:MemberCode,:CountryCode,:Mobile, :VerifyCode, :LoginPwd)", { replacements: { MemberCode, ...req.body } }) 69 | switch (P_RESULT) { 70 | case 0: 71 | user.CountryCode = req.body.CountryCode 72 | user.Mobile = req.body.Mobile 73 | user.LoginPwd = req.body.LoginPwd 74 | req.user = user 75 | return LoginThirdParty(req, res) 76 | case 40005: 77 | return res.send({ Status: 40005, Explain: "验证码过期" }) 78 | default: 79 | return res.send({ Status: P_RESULT }) 80 | } 81 | } 82 | } 83 | let [result] = await mainDB.query("CALL PRC_WF_CREATE_MEMBER(:DataSource,:PhoneBrand,:PhoneModel,:ImageFormat,:Nickname,:CountryCode, :Mobile, :VerifyCode, :LoginPwd)", { replacements: req.body }) 84 | let { P_RESULT, ...user } = result 85 | switch (P_RESULT) { 86 | case 0: 87 | await singleton.CreateParactice(user.MemberCode) 88 | req.user = user 89 | if (req.body.HeadImage) { 90 | let buffer = new Buffer(req.body.HeadImage, "base64") 91 | fs.writeFile(config.uploadFilePath + user.HeadImage, buffer, function(...arg) { 92 | console.log(arg) 93 | }); 94 | // gm(buffer, 'head.' + req.body.ImageFormat).write(config.uploadFilePath + user.HeadImage, function(...arg) { 95 | // console.log(arg) 96 | // }) 97 | } 98 | if (!req.body.LoginType) { 99 | Login(req, res) 100 | } else { 101 | let field = OpenIDField[req.body.LoginType] 102 | await singleton.updateMainDB("wf_member", { 103 | [field]: req.body.OpenID 104 | }, null, { MemberCode: user.MemberCode }) 105 | LoginThirdParty(req, res) 106 | } 107 | break 108 | case 1: 109 | req.user = user 110 | if (!user.HeadImage) { 111 | if (req.body.HeadImage) { 112 | let buffer = new Buffer(req.body.HeadImage, "base64") 113 | user.HeadImage = "/images/head/" + user.MemberCode + "." + req.body.ImageFormat 114 | fs.writeFile(config.uploadFilePath + user.HeadImage, buffer, function(...arg) { 115 | console.log(arg) 116 | }); 117 | } else { 118 | user.HeadImage = config.defaultHeadImage 119 | } 120 | } 121 | if (!req.body.LoginType) 122 | res.send({ Status: 1 }) 123 | else { 124 | await singleton.updateMainDB("wf_member", { 125 | [field]: req.body.OpenID, 126 | HeadImage: user.HeadImage 127 | }, null, { MemberCode: user.MemberCode }) 128 | LoginThirdParty(req, res) 129 | } 130 | break; 131 | case 40005: 132 | res.send({ Status: 40005, Explain: "验证码过期" }) 133 | break; 134 | default: 135 | res.send({ Status: P_RESULT }) 136 | } 137 | })); 138 | return router; 139 | } -------------------------------------------------------------------------------- /webapi/routes/video.js: -------------------------------------------------------------------------------- 1 | const sql1 = ` 2 | SELECT 3 | room.RoomCode,room.RoomTitle,room.MemberCode,room.SecretType,room.City, c.Topic,(case when room.Status=0 then 'video' else 'live' end) Type,DATE_FORMAT(room.CreateTime,'%Y-%m-%d %H:%i:%s') CreateTime, 4 | concat(:picBaseURL, HeadImage) HeadImage,NickName,concat(:picBaseURL, ImageUrl) ImageUrl 5 | FROM 6 | (Select * from wf_liveroom where Status < 2 order by Status Desc,CreateTime Desc LIMIT :page,20) room 7 | LEFT JOIN ( 8 | SELECT 9 | a.LiveRoomCode, 10 | GROUP_CONCAT(b.TopicName) AS Topic 11 | FROM 12 | wf_live_roomtopic a, 13 | wf_live_topic b 14 | WHERE 15 | a.TopicCode = b.TopicCode 16 | GROUP BY 17 | 18 | LiveRoomCode 19 | ) c ON room.RoomCode = c.LiveRoomCode 20 | LEFT JOIN wf_member ON room.MemberCode=wf_member.MemberCode 21 | order by room.Status Desc,room.CreateTime Desc 22 | ` 23 | module.exports = function({ mainDB, statistic, ctt, express, config, wrap, redisClient }) { 24 | const router = express.Router(); 25 | /**直播列表*/ 26 | router.get('/LiveList/:page', wrap(async(req, res) => { 27 | let replacements = { page: Number(req.params.page) * 20, picBaseURL: config.picBaseURL } 28 | let [result] = await mainDB.query(sql1, { replacements }) 29 | let roomCodes = result.map(i => i.RoomCode) 30 | roomCodes = await redisClient.mgetAsync(...roomCodes.map(i => "roommemberrealcount:" + i).concat(roomCodes.map(i => "roomrobotcount:" + i))) 31 | let count = result.length; 32 | for (let i = 0; i < count; i++) { 33 | result[i].MemberNum = Number(roomCodes[i]) + Number(roomCodes[count + i]) 34 | } 35 | res.send({ Status: 0, Explain: "", DataList: result }) 36 | })); 37 | /**直播列表Banner条 */ 38 | router.get('/LiveBanner', wrap(async(req, res) => { 39 | let [result] = await mainDB.query("select Title,VideoCode Code,concat(:picBaseURL, CoverImageUrl) Image from wf_video_banner where IsDelete=0 order by SortOrder,Id desc", { replacements: config }) 40 | res.send({ Status: 0, Explain: "", DataList: result }) 41 | })); 42 | /**视频一级列表 */ 43 | router.get('/Home', wrap(async(req, res) => { 44 | let replacements = config; 45 | let [Dissertation] = await mainDB.query("select Id,Name,Description,concat(:picBaseURL, CoverImageUrl) Image from wf_video_dissertation where IsDelete=0 order by SortOrder,Id desc", { replacements }) 46 | let [AllColumn] = await mainDB.query("select Id,Name,concat(:picBaseURL, CoverImageUrl) Image from wf_video_column where IsDelete=0 order by SortOrder,Id desc", { replacements }) 47 | let Column = AllColumn.slice(0, 5) 48 | let [Latest] = await mainDB.query("select VideoCode Code,VideoName Name,concat(:picBaseURL, VideoImage) Image,TimeLong from wf_live_video where Status=0 order by ShowTime desc limit 5", { replacements }) 49 | res.send({ Status: 0, Explain: "", Data: { Dissertation, Column, Latest, AllColumn } }) 50 | })); 51 | /**教学列表 */ 52 | router.get('/Teaching/:page', wrap(async(req, res) => { 53 | let [result] = await mainDB.query("select VideoCode Code,VideoName Title,concat(:picBaseURL, VideoImage) Image,TimeLong ,DATE_FORMAT(ShowTime,'%Y-%m-%d %H:%i:%s') ShowTime from wf_live_video where Backup1=3 and Status=0 limit :page,20", { replacements: { page: Number(req.params.page) * 20, picBaseURL: config.picBaseURL } }) 54 | res.send({ Status: 0, Explain: "", DataList: result }) 55 | })); 56 | /**热门分类 */ 57 | // router.get('/Column', wrap(async(req, res) => { 58 | // let [Column] = await mainDB.query("select Id,Name from wf_video_column where IsDelete=0 order by SortOrder,Id desc", { replacements: config }) 59 | // if (Column.length) { 60 | // let columnId = req.query.Id ? req.query.Id : Column[0].Id 61 | // let [Detail] = await mainDB.query("select VideoCode Code,VideoName Title,concat(:picBaseURL, VideoImage) Image,TimeLong,DATE_FORMAT(ShowTime,'%Y-%m-%d %H:%i:%s') ShowTime from wf_live_video where Status=0 and ColumnId=:Id order by ShowTime desc limit 20", { replacements: { picBaseURL: config.picBaseURL, Id: columnId } }) 62 | // res.send({ Status: 0, Explain: "", DataList: Column, Detail }) 63 | // } else res.send({ Status: 0, Explain: "无数据", DataList: Column }) 64 | // })); 65 | /**某分类的视频列表 */ 66 | router.get('/Column/:Id/:page', wrap(async(req, res) => { 67 | let [Column] = await mainDB.query("select VideoCode Code,VideoName Title,concat(:picBaseURL, VideoImage) Image,TimeLong,DATE_FORMAT(ShowTime,'%Y-%m-%d %H:%i:%s') ShowTime from wf_live_video where Status=0 and ColumnId=:Id order by ShowTime desc limit :page,20", { replacements: { page: Number(req.params.page) * 20, picBaseURL: config.picBaseURL, Id: Number(req.params.Id) } }) 68 | res.send({ Status: 0, Explain: "", DataList: Column }) 69 | })); 70 | /**某专题视频列表*/ 71 | router.get('/Dissertation/:Id/:page', wrap(async(req, res) => { 72 | let [Dissertation] = await mainDB.query("select VideoCode Code,VideoName Title,concat(:picBaseURL, VideoImage) Image,TimeLong,DATE_FORMAT(ShowTime,'%Y-%m-%d %H:%i:%s') ShowTime from wf_live_video where Status=0 and DissertationId=:Id order by ShowTime desc limit :page,20", { replacements: { page: Number(req.params.page) * 20, picBaseURL: config.picBaseURL, Id: Number(req.params.Id) } }) 73 | res.send({ Status: 0, Explain: "", DataList: Dissertation }) 74 | })); 75 | /**最新视频列表 */ 76 | router.get('/Lastest/:page', wrap(async(req, res) => { 77 | let [result] = await mainDB.query("select VideoCode Code,VideoName Title,concat(:picBaseURL, VideoImage) Image,TimeLong,DATE_FORMAT(ShowTime,'%Y-%m-%d %H:%i:%s') ShowTime from wf_live_video where Status=0 order by ShowTime desc limit :page,20", { replacements: { page: Number(req.params.page) * 20, picBaseURL: config.picBaseURL } }) 78 | res.send({ Status: 0, Explain: "", DataList: result }) 79 | })); 80 | return router; 81 | } -------------------------------------------------------------------------------- /webapi/statistic.js: -------------------------------------------------------------------------------- 1 | import sqlstr from '../common/sqlStr' 2 | import singleton from '../common/singleton' 3 | const { mainDB } = singleton 4 | export default class Statistic { 5 | login(data) { 6 | mainDB.query(...sqlstr.insert2("wf_statistics_login", data, { CreateTime: "now()" })); 7 | } 8 | page(data) { 9 | mainDB.query(...sqlstr.insert2("wf_statistics_page", data, { StartTime: "now()" })); 10 | } 11 | stock(data) { 12 | mainDB.query(...sqlstr.insert2("wf_statistics_stock", data, { StartTime: "now()" })); 13 | } 14 | module(data) { 15 | mainDB.query(...sqlstr.insert2("wf_statistics_module", data, { username: null })); 16 | } 17 | pageStay(data) { 18 | singleton.insertMainDB("wf_statistics_page", data, { StockType: null, StockNo: null, PageType: null, username: null }) 19 | } 20 | stockPageStay(data) { 21 | singleton.insertMainDB("wf_statistics_stock", data, { TypeId: null, PageId: null, PageType: null, username: null }) 22 | } 23 | } -------------------------------------------------------------------------------- /webapi/views/articleAndroid.jshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 资讯详情 9 | 10 | 11 | 30 | 170 | 171 | 172 | 173 | 174 | 175 |
    176 |
    177 | @locals.content 178 |
    179 |
    180 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /webapi/views/articleIOS.jshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 资讯详情 9 | 11 | 12 | 13 | 51 | 158 | 159 | 160 | 161 | 162 | 163 |
    164 |
    165 | @locals.content 166 |
    167 |
    168 | 219 | 220 | 221 | -------------------------------------------------------------------------------- /webapi/web/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /webapi/web/dist/vendor-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vendor_library", 3 | "content": { 4 | "./node_modules/vue/dist/vue.runtime.esm.js": { 5 | "id": 0, 6 | "meta": { 7 | "harmonyModule": true 8 | }, 9 | "exports": [ 10 | "default" 11 | ] 12 | }, 13 | "./node_modules/muse-ui/dist/muse-ui.js": { 14 | "id": 1, 15 | "meta": {} 16 | }, 17 | "./node_modules/vue-resource/dist/vue-resource.es2015.js": { 18 | "id": 2, 19 | "meta": { 20 | "harmonyModule": true 21 | }, 22 | "exports": [ 23 | "default", 24 | "Url", 25 | "Http", 26 | "Resource" 27 | ] 28 | }, 29 | "./node_modules/webpack/buildin/global.js": { 30 | "id": 3, 31 | "meta": {} 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /webapi/web/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import MuseUI from 'muse-ui' 3 | import VueResource from 'vue-resource' 4 | import 'muse-ui/dist/muse-ui.css' 5 | Vue.use(MuseUI) 6 | Vue.use(VueResource); 7 | new Vue({ el: 'app', render: h => h(require('./view/app.vue')) }) -------------------------------------------------------------------------------- /webapi/web/view/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Mu_Table from './table.vue' 3 | export default { 4 | data() { 5 | return { 6 | memberCode: "", 7 | token: "", 8 | resultTitle: "", 9 | resultContent: "" 10 | } 11 | }, 12 | methods: { 13 | getToken() { 14 | this.$http.get('token/' + this.memberCode).then(res => { 15 | this.token = res.body 16 | }) 17 | }, 18 | getConfig() { 19 | this.$http.get('config').then(res => { 20 | this.resultTitle = "config" 21 | this.resultContent = "
    " + JSON.stringify(res.body, null, 4) + "
    " 22 | 23 | }) 24 | }, 25 | getWebConfig() { 26 | this.$http.get('webConfig').then(res => { 27 | this.resultTitle = "webConfig" 28 | this.resultContent = "
    " + JSON.stringify(res.body, null, 4) + "
    " 29 | }) 30 | }, 31 | getPM2List() { 32 | this.resultTitle = "PM2List" 33 | this.resultContent = "" 34 | this.$http.get('pm2/list').then(res => { 35 | let _data = res.body.map(({ pid, name, pm_id, pm2_env: { watch }, monit: { memory, cpu } }) => ({ pid, name, pm_id, memory, cpu, watch })) 36 | new Vue({ 37 | el: 'pm2list', 38 | render: h => h(Mu_Table, { 39 | props: { 40 | titles: ["PID", "Name", "PM_ID", "Memory", "CPU", "Watch"], 41 | data: _data 42 | } 43 | }) 44 | }) 45 | }) 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /webapi/web/view/app.vue: -------------------------------------------------------------------------------- 1 | 16 | 20 | -------------------------------------------------------------------------------- /webapi/web/view/table.vue: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /webapi/web/webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"); 2 | var path = require("path") 3 | module.exports = { 4 | entry: { 5 | index: path.resolve(__dirname, './index.js') 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, './dist'), 9 | publicPath: 'dist/', 10 | filename: "bundle.js" 11 | }, 12 | module: { 13 | rules: [{ 14 | test: /\.vue$/, 15 | loader: 'vue-loader' 16 | }, 17 | { 18 | test: /\.js$/, 19 | include: __dirname, 20 | exclude: /node_modules/, 21 | loader: 'babel-loader' 22 | }, 23 | { 24 | test: /\.css$/, 25 | loader: 'style-loader!css-loader' 26 | }, 27 | { 28 | test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/, 29 | loader: 'file-loader' 30 | }, 31 | { 32 | test: /\.(png|jpe?g|gif|svg)(\?\S*)?$/, 33 | loader: 'file-loader', 34 | query: { 35 | name: '[name].[ext]?[hash]' 36 | } 37 | } 38 | ] 39 | }, 40 | resolve: {}, 41 | plugins: [ 42 | new webpack.DllReferencePlugin({ 43 | context: __dirname, 44 | /** 45 | * 在这里引入 manifest 文件 46 | */ 47 | manifest: require('./dist/vendor-manifest.json') 48 | }) 49 | ] 50 | }; -------------------------------------------------------------------------------- /webapi/web/webpack.dll.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | vendor: ['vue', 'muse-ui', 'vue-resource'] 7 | }, 8 | output: { 9 | path: path.join(__dirname, 'dist'), 10 | filename: '[name].dll.js', 11 | /** 12 | * output.library 13 | * 将会定义为 window.${output.library} 14 | * 在这次的例子中,将会定义为`window.vendor_library` 15 | */ 16 | library: '[name]_library' 17 | }, 18 | plugins: [ 19 | new webpack.DllPlugin({ 20 | /** 21 | * path 22 | * 定义 manifest 文件生成的位置 23 | * [name]的部分由entry的名字替换 24 | */ 25 | path: path.join(__dirname, 'dist', '[name]-manifest.json'), 26 | /** 27 | * name 28 | * dll bundle 输出到那个全局变量上 29 | * 和 output.library 一样即可。 30 | */ 31 | name: '[name]_library' 32 | }) 33 | ] 34 | }; -------------------------------------------------------------------------------- /wechat-info.json: -------------------------------------------------------------------------------- 1 | { 2 | "globalToken": { 3 | "count": 0 4 | }, 5 | "urls": {}, 6 | "oauth": {} 7 | } --------------------------------------------------------------------------------