diff --git a/configuration.json b/configuration.json index c08bbfc0..1801d6fe 100644 --- a/configuration.json +++ b/configuration.json @@ -43,8 +43,9 @@ "officialURL" : "https://www.shen100.com" }, "api": { - "Prefix": "/api", - "URL": "http://127.0.0.1:8012" + "Prefix" : "/api", + "URL" : "/api", + "NodeURL" : "http://127.0.0.1:8012/api" }, "docs": { "github": "https://xxx.com/shen100" diff --git a/go/config/config.go b/go/config/config.go index 592e704d..36cdbdbc 100644 --- a/go/config/config.go +++ b/go/config/config.go @@ -68,6 +68,7 @@ func initServer() { type apiConfig struct { Prefix string URL string + NodeURL string } // APIConfig api相关配置 diff --git a/go/controller/visit/visit.go b/go/controller/visit/visit.go index 9e25a163..8d34d290 100644 --- a/go/controller/visit/visit.go +++ b/go/controller/visit/visit.go @@ -1,8 +1,12 @@ package visit import ( + "../../config" "../../model" + "strconv" + "time" "gopkg.in/kataras/iris.v6" + "github.com/jinzhu/gorm" ) // Latest30Day 近30天,每天的PV @@ -25,3 +29,70 @@ func Latest30Day(ctx *iris.Context) { "data" : data, }) } + +// PV 增加一次页面访问 +func PV(ctx *iris.Context) { + var err error + var msg = "" + var userVisit model.UserVisit + userVisit.ClientID = ctx.FormValue("clientId") + userVisit.OSName = ctx.FormValue("osName") + userVisit.OSVersion = ctx.FormValue("osVersion") + userVisit.Language = ctx.FormValue("language") + userVisit.Country = ctx.FormValue("country") + userVisit.DeviceModel = ctx.FormValue("deviceModel") + userVisit.DeviceWidth, err = strconv.Atoi(ctx.FormValue("deviceWidth")) + if err != nil { + msg = "无效的deviceWidth" + } + userVisit.DeviceHeight, err = strconv.Atoi(ctx.FormValue("deviceHeight")) + if err != nil { + msg = "无效的deviceHeight" + } + userVisit.IP = ctx.RemoteAddr() + userVisit.VisitTime = time.Now() + userVisit.Referrer = ctx.FormValue("referrer") + userVisit.URL = ctx.FormValue("url") + userVisit.BrowserName = ctx.FormValue("browserName") + userVisit.BrowserVersion = ctx.FormValue("browserVersion") + + if userVisit.ClientID == "" { + msg = "clientId不能为空" + } + if msg != "" { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : msg, + "data" : iris.Map{}, + }) + return + } + + db, connErr := gorm.Open(config.DBConfig.Dialect, config.DBConfig.URL) + + if connErr != nil { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error", + "data" : iris.Map{}, + }) + return + } + + defer db.Close() + + if err := db.Create(&userVisit).Error; err != nil { + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.ERROR, + "msg" : "error.", + "data" : iris.Map{}, + }) + return + } + + ctx.JSON(iris.StatusOK, iris.Map{ + "errNo" : model.ErrorCode.SUCCESS, + "msg" : "success", + "data" : iris.Map{}, + }) +} diff --git a/go/model/uservisit.go b/go/model/uservisit.go index 52278645..9068bc34 100644 --- a/go/model/uservisit.go +++ b/go/model/uservisit.go @@ -16,8 +16,8 @@ type UserVisit struct { UserID uint `json:"userID"` VisitTime time.Time `json:"visitTime"` IP string `json:"ip"` - DeviceWidth uint `json:"deviceWidth"` - DeviceHeight uint `json:"deviceHeight"` + DeviceWidth int `json:"deviceWidth"` + DeviceHeight int `json:"deviceHeight"` BrowserName string `json:"browserName"` BrowserVersion string `json:"browserVersion"` DeviceModel string `json:"deviceModel"` diff --git a/main.go b/main.go index 2a90d609..943d022b 100644 --- a/main.go +++ b/main.go @@ -27,6 +27,12 @@ func main() { app.Adapt(httprouter.New()) apiPrefix := config.APIConfig.Prefix + + router := app.Party(apiPrefix) + { + router.Get("/visit", visit.PV) + } + adminRouter := app.Party(apiPrefix + "/admin", admin.Authentication) { adminRouter.Get("/categories", category.List) diff --git a/nodejs/config/index.js b/nodejs/config/index.js index 695fe6c5..9dd60ed4 100644 --- a/nodejs/config/index.js +++ b/nodejs/config/index.js @@ -16,7 +16,8 @@ var config = { sitePath : configData.nodejs.page.sitePath, jsPath : configData.nodejs.page.jsPath, imagePath : configData.nodejs.page.imagePath, - cssPath : configData.nodejs.page.cssPath + cssPath : configData.nodejs.page.cssPath, + apiURL : configData.api.URL }, software: { name : configData.software.name, @@ -35,7 +36,7 @@ var config = { }; (function() { - var url = configData.api.URL + configData.api.Prefix; + var url = configData.api.NodeURL; for (var key in config.api) { if (config.api.hasOwnProperty(key)) { config.api[key] = url + config.api[key]; diff --git a/nodejs/package.json b/nodejs/package.json index 52cbef8e..9a23ae29 100755 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -44,6 +44,7 @@ "redux-devtools": "3.3.2", "redux-thunk": "2.2.0", "style-loader": "0.16.1", + "ua-parser-js": "0.7.12", "webpack": "2.3.2", "webpack-dev-middleware": "1.10.1", "webpack-hot-middleware": "2.17.1" diff --git a/nodejs/static/javascripts/admin/actions/requestUserAnalyze.js b/nodejs/static/javascripts/admin/actions/requestUserAnalyze.js index 00382148..90364583 100644 --- a/nodejs/static/javascripts/admin/actions/requestUserAnalyze.js +++ b/nodejs/static/javascripts/admin/actions/requestUserAnalyze.js @@ -16,7 +16,7 @@ function receiveUserAnalyze(json) { export default function() { return dispatch => { - var url = pageConfig.sitePath + '/admin/user/analyze'; + var url = pageConfig.apiURL + '/admin/user/analyze'; return fetch(url) .then(response => response.json()) .then(json => dispatch(receiveUserAnalyze(json.data))) diff --git a/nodejs/static/javascripts/admin/app.js b/nodejs/static/javascripts/admin/app.js index 12805aa4..9188d690 100644 --- a/nodejs/static/javascripts/admin/app.js +++ b/nodejs/static/javascripts/admin/app.js @@ -1,3 +1,5 @@ +import 'isomorphic-fetch'; + import React from 'react'; import ReactDOM from 'react-dom'; diff --git a/nodejs/static/javascripts/admin/containers/Index.js b/nodejs/static/javascripts/admin/containers/Index.js index f81121a1..21580bdd 100644 --- a/nodejs/static/javascripts/admin/containers/Index.js +++ b/nodejs/static/javascripts/admin/containers/Index.js @@ -2,6 +2,7 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { Row, Col } from 'antd'; import requestSystemIndex from '../actions/requestSystemIndex'; +import analyze from '../../sdk/analyze'; import '../../../styles/admin/index.css'; class Index extends Component { @@ -12,6 +13,7 @@ class Index extends Component { componentDidMount() { const { dispatch } = this.props; dispatch(requestSystemIndex()); + analyze.pv(); } render() { let { systemIndex } = this.props; diff --git a/nodejs/static/javascripts/sdk/analyze.js b/nodejs/static/javascripts/sdk/analyze.js new file mode 100644 index 00000000..b4555c6f --- /dev/null +++ b/nodejs/static/javascripts/sdk/analyze.js @@ -0,0 +1,67 @@ +import UAParser from 'ua-parser-js'; +import storage from '../storage'; +import uuid from './uuid'; + +let ua; + +function init() { + ua = new UAParser().getResult(); + + let lang = navigator.language + || navigator.browserLanguage + || navigator.systemLanguage + || navigator.userLanguage + || ''; + let country = ''; + let language = ''; + let index = lang.indexOf('-'); + if (index > 0) { + country = lang.substr(index + 1).toUpperCase(); + language = lang.substr(0, index); + } + ua.country = country; + ua.language = language; + ua.device.screenWidth = window.screen.width; + ua.device.screenHeight = window.screen.height; + ua.device.model = ua.device.model || ''; +} + +init(); + +export default { + pv() { + const LOCAL_DATA = 'analyzeLS'; + const LOCAL_DATA_VERSION = 'v1.0'; + + var localData = storage.getItem(LOCAL_DATA); + if (!localData || localData.version < LOCAL_DATA_VERSION) { + localData = { + clientId : uuid(), + version : LOCAL_DATA_VERSION + }; + storage.setItem(LOCAL_DATA, localData); + } + + let params = { + clientId : localData.clientId, + osName : ua.os.name, + osVersion : ua.os.version, + language : ua.language, + country : ua.country, + deviceModel : ua.device.model, + deviceWidth : ua.device.screenWidth, + deviceHeight : ua.device.screenHeight, + referrer : encodeURIComponent(document.referrer), + url : encodeURIComponent(location.href), + browserName : ua.browser.name, + browserVersion : ua.browser.version + }; + let paramArr = []; + for (let key in params) { + paramArr.push(key + '=' + params[key]); + } + let url = pageConfig.apiURL + '/visit?' + paramArr.join('&'); + fetch(url) + .then(response => response.json()) + } +}; \ No newline at end of file diff --git a/nodejs/static/javascripts/sdk/uuid.js b/nodejs/static/javascripts/sdk/uuid.js new file mode 100644 index 00000000..b6867084 --- /dev/null +++ b/nodejs/static/javascripts/sdk/uuid.js @@ -0,0 +1,157 @@ +export default (function(_window) { + 'use strict'; + + // Unique ID creation requires a high quality random # generator. We feature + // detect to determine the best RNG source, normalizing to a function that + // returns 128-bits of randomness, since that's what's usually required + var _rng, _mathRNG, _nodeRNG, _whatwgRNG, _previousRoot; + + //setupBrowser + (function() { + // Allow for MSIE11 msCrypto + var _crypto = _window.crypto || _window.msCrypto; + + if (!_rng && _crypto && _crypto.getRandomValues) { + // WHATWG crypto-based RNG - http://wiki.whatwg.org/wiki/Crypto + // + // Moderately fast, high quality + try { + var _rnds8 = new Uint8Array(16); + _whatwgRNG = _rng = function whatwgRNG() { + _crypto.getRandomValues(_rnds8); + return _rnds8; + }; + _rng(); + } catch (e) {} + } + + if (!_rng) { + // Math.random()-based (RNG) + // + // If all else fails, use Math.random(). It's fast, but is of unspecified + // quality. + var _rnds = new Array(16); + _mathRNG = _rng = function() { + for (var i = 0, r; i < 16; i++) { + if ((i & 0x03) === 0) { + r = Math.random() * 0x100000000; + } + _rnds[i] = r >>> ((i & 0x03) << 3) & 0xff; + } + + return _rnds; + }; + // if ('undefined' !== typeof console && console.warn) { + // console.warn("[SECURITY] node-uuid: crypto not usable, falling back to insecure Math.random()"); + // } + } + }()); + + // Buffer class to use + var BufferClass = ('function' === typeof Buffer) ? Buffer : Array; + + // Maps for number <-> hex string conversion + var _byteToHex = []; + var _hexToByte = {}; + for (var i = 0; i < 256; i++) { + _byteToHex[i] = (i + 0x100).toString(16).substr(1); + _hexToByte[_byteToHex[i]] = i; + } + + // **`parse()` - Parse a UUID into it's component bytes** + function parse(s, buf, offset) { + var i = (buf && offset) || 0, + ii = 0; + + buf = buf || []; + s.toLowerCase().replace(/[0-9a-f]{2}/g, function(oct) { + if (ii < 16) { // Don't overflow! + buf[i + ii++] = _hexToByte[oct]; + } + }); + + // Zero out remaining bytes if string was short + while (ii < 16) { + buf[i + ii++] = 0; + } + + return buf; + } + + // **`unparse()` - Convert UUID byte array (ala parse()) into a string** + function unparse(buf, offset) { + var i = offset || 0, + bth = _byteToHex; + return bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + '-' + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]] + + bth[buf[i++]] + bth[buf[i++]]; + } + + // **`v1()` - Generate time-based UUID** + // + // Inspired by https://github.com/LiosK/UUID.js + // and http://docs.python.org/library/uuid.html + + // random #'s we need to init node and clockseq + var _seedBytes = _rng(); + + // Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1) + var _nodeId = [ + _seedBytes[0] | 0x01, + _seedBytes[1], _seedBytes[2], _seedBytes[3], _seedBytes[4], _seedBytes[5] + ]; + + // Per 4.2.2, randomize (14 bit) clockseq + var _clockseq = (_seedBytes[6] << 8 | _seedBytes[7]) & 0x3fff; + + // Previous uuid creation time + var _lastMSecs = 0, + _lastNSecs = 0; + + // **`v4()` - Generate random UUID** + + // See https://github.com/broofa/node-uuid for API details + function v4(options, buf, offset) { + // Deprecated - 'format' argument, as supported in v1.2 + var i = buf && offset || 0; + + if (typeof(options) === 'string') { + buf = (options === 'binary') ? new BufferClass(16) : null; + options = null; + } + options = options || {}; + + var rnds = options.random || (options.rng || _rng)(); + + // Per 4.4, set bits for version and `clock_seq_hi_and_reserved` + rnds[6] = (rnds[6] & 0x0f) | 0x40; + rnds[8] = (rnds[8] & 0x3f) | 0x80; + + // Copy bytes to buffer, if provided + if (buf) { + for (var ii = 0; ii < 16; ii++) { + buf[i + ii] = rnds[ii]; + } + } + + return buf || unparse(rnds); + } + + // Export public API + var uuid = v4; + uuid.v4 = v4; + uuid.parse = parse; + uuid.unparse = unparse; + uuid.BufferClass = BufferClass; + uuid._rng = _rng; + uuid._mathRNG = _mathRNG; + uuid._nodeRNG = _nodeRNG; + uuid._whatwgRNG = _whatwgRNG; + + return uuid; +})('undefined' !== typeof window ? window : null) \ No newline at end of file diff --git a/nodejs/static/javascripts/storage/index.js b/nodejs/static/javascripts/storage/index.js new file mode 100644 index 00000000..27332e40 --- /dev/null +++ b/nodejs/static/javascripts/storage/index.js @@ -0,0 +1,44 @@ +export default { + getCookie: function(key) { + var cookies = document.cookie.split(';'); + for (var i = 0; i < cookies.length; i++) { + var cookie = cookies[i]; + cookie = cookie.replace(/^\s+/, ''); + var cookieArr = cookie.split('='); + if (cookieArr[0] === key) { + return decodeURIComponent(cookieArr[1]); + } + } + return ''; + }, + setCookie: function(key, value, day) { + day = day || 36500; + var expires = ''; + var date = new Date(new Date().getTime() + day * 24 * 60 * 60 * 1000); + expires = '; expires=' + date.toUTCString(); + + var cookie = [ + key + '=' + encodeURIComponent(value), + expires, + '; path=/' + ].join(''); + document.cookie = cookie; + }, + getItem: function(key) { + var local = window.localStorage; + var result = local ? local.getItem(key) : this.getCookie(key); + return result ? JSON.parse(result) : null; + }, + setItem: function(key, value) { + if (!value) { + return; + } + var local = window.localStorage; + value = JSON.stringify(value); + try { + local ? local.setItem(key, value) : this.setCookie(key, value); + } catch (e) { + + } + } +}; \ No newline at end of file