hit-counter
This commit is contained in:
+56
-35
@@ -51,7 +51,16 @@ module.exports = (config) => {
|
||||
let showError;
|
||||
const fw = require('./file_walker')(config);
|
||||
const renderer = require('./renderer')(config);
|
||||
const hc = require('./hit_counter')(config);
|
||||
const hc = require('./hit_counter')(config,
|
||||
() => {
|
||||
console.log(cons.ok, 'redis connected');
|
||||
},
|
||||
(err) => {
|
||||
if (err.code !== 'ECONNREFUSED') {
|
||||
console.log(cons.warn, 'redis error: ' + err);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// set view engine from configuration
|
||||
app.set('view engine', config['view_engine']);
|
||||
@@ -159,22 +168,28 @@ module.exports = (config) => {
|
||||
if (err) {
|
||||
showError(req, res, 404);
|
||||
} else {
|
||||
hc.count(req, '/');
|
||||
render(req, res, homePath,
|
||||
{
|
||||
articles: Object.values(articles)
|
||||
.filter(d => !d.draft)
|
||||
.sort((a, b) => ('' + b.path).localeCompare(a.path)),
|
||||
});
|
||||
hc.count(req, '/', () => {
|
||||
render(req, res, homePath,
|
||||
{
|
||||
articles: Object.values(articles)
|
||||
.filter(d => !d.draft)
|
||||
.sort((a, b) => ('' + b.path).localeCompare(a.path)),
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
app.get('/stats', (req, res) => {
|
||||
const data = hc.read('/');
|
||||
res.json({
|
||||
hits: data.hits,
|
||||
visitors: data.visitors,
|
||||
});
|
||||
if (config['modules']['hit_counter']) {
|
||||
hc.read('/', (data) => {
|
||||
res.json({
|
||||
hits: data.hits,
|
||||
visitors: data.visitors,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
showError(req, res, 404);
|
||||
}
|
||||
});
|
||||
|
||||
//RSS endpoint
|
||||
@@ -251,29 +266,35 @@ module.exports = (config) => {
|
||||
if (!article) {
|
||||
showError(req, res, 404);
|
||||
} else if (req.path.endsWith('stats')) {
|
||||
const data = hc.read(articlePath);
|
||||
res.json({
|
||||
hits: data.hits,
|
||||
visitors: data.visitors,
|
||||
});
|
||||
} else {
|
||||
hc.count(req, articlePath);
|
||||
renderer.render(article.realPath, (err, html) => {
|
||||
if (err) {
|
||||
console.log(cons.error, `failed to render article ${req.path} : ${err}`);
|
||||
showError(req, res, 500);
|
||||
} else {
|
||||
article.content = html;
|
||||
const templatePath = path.join(config['data_dir'], config['article']['template']);
|
||||
fs.access(templatePath, fs.constants.R_OK, (err) => {
|
||||
if (err) {
|
||||
console.log(cons.error, `no template found at ${templatePath}`);
|
||||
showError(req, res, 500);
|
||||
} else {
|
||||
render(req, res, templatePath, { article: article });
|
||||
}
|
||||
if (config['modules']['hit_counter']) {
|
||||
hc.read(articlePath, (data) => {
|
||||
res.json({
|
||||
hits: data.hits,
|
||||
visitors: data.visitors,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
showError(req, res, 404);
|
||||
}
|
||||
} else {
|
||||
hc.count(req, articlePath, () => {
|
||||
renderer.render(article.realPath, (err, html) => {
|
||||
if (err) {
|
||||
console.log(cons.error, `failed to render article ${req.path} : ${err}`);
|
||||
showError(req, res, 500);
|
||||
} else {
|
||||
article.content = html;
|
||||
const templatePath = path.join(config['data_dir'], config['article']['template']);
|
||||
fs.access(templatePath, fs.constants.R_OK, (err) => {
|
||||
if (err) {
|
||||
console.log(cons.error, `no template found at ${templatePath}`);
|
||||
showError(req, res, 500);
|
||||
} else {
|
||||
render(req, res, templatePath, { article: article });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"prism": true,
|
||||
"mathjax": true,
|
||||
"plantuml": true,
|
||||
"fa-diagrams": true
|
||||
"fa-diagrams": true,
|
||||
"hit_counter": true
|
||||
},
|
||||
"home": {
|
||||
"title": "GitBlog.md",
|
||||
@@ -58,5 +59,12 @@
|
||||
},
|
||||
"plantuml": {
|
||||
"output_format": "svg"
|
||||
},
|
||||
"hit_counter": {
|
||||
"unique_visitor_timeout": 7200000
|
||||
},
|
||||
"redis": {
|
||||
"host": "localhost",
|
||||
"port": 6379
|
||||
}
|
||||
}
|
||||
|
||||
+38
-7
@@ -1,13 +1,44 @@
|
||||
module.exports = (config) => {
|
||||
const count = (req, path) => {
|
||||
const redis = require('redis');
|
||||
|
||||
module.exports = (config, onConnect, onError) => {
|
||||
const client = config['modules']['hit_counter'] ? redis.createClient(config['redis']) : { connected: false, on: () => { /* ignore */ } };
|
||||
|
||||
client.on('connect', onConnect);
|
||||
client.on('error', onError);
|
||||
|
||||
const visitors = {};
|
||||
|
||||
const count = (req, path, cb) => {
|
||||
if (!client.connected) {
|
||||
cb();
|
||||
} else {
|
||||
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
|
||||
const key = path + ':' + ip;
|
||||
const now = Date.now();
|
||||
const isNewVisitor = (now - (visitors[key] || 0)) > config['hit_counter']['unique_visitor_timeout'];
|
||||
visitors[key] = now;
|
||||
client
|
||||
.multi()
|
||||
.hincrby(path, 'h', 1)
|
||||
.hincrby(path, 'v', isNewVisitor ? 1 : 0)
|
||||
.exec(cb);
|
||||
}
|
||||
};
|
||||
|
||||
const read = (path) => {
|
||||
return {
|
||||
hits: 0,
|
||||
visitors: 0,
|
||||
};
|
||||
const read = (path, cb) => {
|
||||
if (!client.connected) {
|
||||
cb({
|
||||
hits: 0,
|
||||
visitors: 0,
|
||||
});
|
||||
} else {
|
||||
client.hgetall(path, (_, value) => {
|
||||
cb({
|
||||
hits: value ? value.h || 0 : 0,
|
||||
visitors: value ? value.v || 0 : 0,
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user