问题:我在NodeJS应用程序中使用Waterline作为我的ORM和OrientDB。 OrientDB使用数字ID,因此在收到帖子时我不希望它们出现在我的网址中。为什么?因为只需增加ID就可以轻松查询完整数据。
解决方案:创建一个独特的slu ..
问题:如何在具有异步回调的Waterline中实现这一目标?我需要这样的东西,但没有找到解决方案。流程可能是这样的:
答案 0 :(得分:2)
以下是我目前正在使用的乐观解决方案:
我的帮助班:
=SUM(INDIRECT("'"&F2&"'!C4"),INDIRECT("'"&F3&"'!C4")),
我的模型定义:
// databaseExtensions.js
var _ = require('lodash');
function getUnique(property, value, separator, criteria){
separator = separator || '-';
criteria = criteria || {};
var searchObject = {};
searchObject[property] = { like: value + '%' };
_.mergeDefaults(searchObject, criteria);
return this.find(searchObject)
.then(function(models){
if(!models || models.length === 0)
return value; // value is unique
var values = _.pluck(models, property);
return getUniqueFromArray(values, value, separator);
});
}
function getUniqueFromArray(existingValues, newValue, separator){
var valuesArray = _.clone(existingValues);
var version = 2; // starting version
var currentValue = newValue;
var unique;
while(version < 10000) { //just to be safe and we don't end up in a infinite loop
unique = true;
for(var i=0; i<valuesArray.length; i++){
if(currentValue === valuesArray[i]){
unique = false;
valuesArray.splice(i, 1);
break;
}
}
if (unique) {
return currentValue;
}
currentValue = newValue + separator + version;
version++;
}
}
module.exports.getUnique = getUnique;
module.exports.getUniqueFromArray = getUniqueFromArray;
在我的控制器中:
// post.model.js
{
//..
atributes: {
//...
urlSlug : {
type : 'string',
required : true,
index : true
}
},
},
getUnique: require('path/to/databaseExtensions');.getUnique
}
在我的情况下,冲突是不太可能的,所以我不太担心并发问题,其中2个模型同时以相同的标题到达。但在成千上万的用户创建帖子的背景下,这可能是一个问题。
如果这没有帮助,请告诉我。
答案 1 :(得分:1)
我使用async提出了自己的解决方案。最后,我决定不使用一个独特的slug作为我的标识符,现在使用一个随机字符串的组合,我称之为hash_id,以及一个不必是唯一的slug,它就是SEO。但是这个答案也包含了对独特slu to的解决方案。所以我的网址有这种格式:
http://example.com/posts/23hlj2l2/i_am_a_slug or
http://example.com/posts/:hash_id/:slug
我创建了一个辅助模块来进行字符串转换/创建。他们只是处理这个问题,对ORM一无所知或者价值是唯一的。
ModelHelpers模块导出两个方法,一个用于规范化输入(如标题)以创建一个slug。它接受一个可选参数,该参数是一个可以添加到slug末尾的数字。
第二种方法创建一个随机字母数字字符串。您可以传入一个参数作为字符串的长度。
var ModelHelpers = function() {
// Init
}
ModelHelpers.prototype.createSlugString = function(input_string, added_number) {
added_number = typeof added_number !== 'undefined' ? added_number : '';
// First replace all whitespaces and '-' and make sure there are no double _
var clean_string = input_string.replace(/[\s\n\-]+/g, '_').replace(/_{2,}/g, '_');
// Replace Umlaute and make lowercase
clean_string = clean_string.toLowerCase().replace(/ä/ig, 'ae').replace(/ö/ig, 'oe').replace(/ü/ig, 'ue');
// Replace any special characters and _ at the beginning or end
clean_string = clean_string.replace(/[^\w]/g, '').replace(/^_+|_$/g, '');
// Only return the first 8 words
clean_string = clean_string.split("_").slice(0,8).join("_");
// Add number if needed
if(added_number !== '') {
clean_string = clean_string + '_' + added_number.toString();
}
return clean_string;
}
ModelHelpers.prototype.makeHashID = function(hash_length)
{
hash_length = typeof hash_length !== 'undefined' ? hash_length : 10;
var text = "";
var possible = "abcdefghijklmnopqrstuvwxyz0123456789";
for( var i=0; i < hash_length; i++ ) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
module.exports = ModelHelpers;
我的解决方案的下一部分是将Waterlines的生命周期回调beforeValidate
与async
结合使用。这样我就可以将slug字段或hash id字段设置为unique,并在Waterline验证之前创建它。 Async是一个非常强大的工具,我只能建议调查它。我正在使用方法whilst
:
while(test,fn,callback)
反复调用fn,而test返回true。在何时调用回调 停止,或发生错误。
我创建了两个版本,一个是你需要一个随机字符串(hash_id),另一个是你想在你的slug的末尾添加一个数字,如果它不是唯一的。
对于哈希ID:
var Waterline = require('Waterline');
var orientAdapter = require('sails-orientdb');
var ModelHelpers = require('../modules/model-helpers');
var async = require('async');
var mh = new ModelHelpers();
var Post = Waterline.Collection.extend({
identity: 'post',
connection: 'myLocalOrient',
attributes: {
text: {
type: 'text',
required: true
},
slug: {
type: 'string'
},
hash_id: {
type: 'string',
unique: true
}
},
// Lifecycle Callbacks
beforeValidate: function(values, next) {
var model_self = this;
var keep_running = true;
// Create first slug
values.hash_id = mh.makeHashID();
values.slug = mh.createSlugString(values.text);
async.whilst(
function () {
// execute whilst while other post has not been retrieved or while it matches a hash_id
// in the database
return keep_running;
},
function (callback) {
// search for post with this hash_id
model_self.findOne().where({hash_id: values.hash_id}).then(function(op) {
if(op === undefined) {
// Nothing found, stop executing
keep_running = false;
} else {
// Create new hash_id
values.hash_id = mh.makeHashID();
}
callback();
});
},
function (err) {
// End the process
// next(); is the callback of Waterlines' beforeValidate
next();
}
); // End whilst
}
});
module.exports = Post;
对于独特的slu ::
var Waterline = require('Waterline');
var orientAdapter = require('sails-orientdb');
var ModelHelpers = require('../modules/model-helpers');
var async = require('async');
var mh = new ModelHelpers();
var Post = Waterline.Collection.extend({
identity: 'post',
connection: 'myLocalOrient',
attributes: {
text: {
type: 'text',
required: true
},
slug: {
type: 'string',
unique: true
},
hash_id: {
type: 'string'
}
},
// Lifecycle Callbacks
beforeValidate: function(values, next) {
var model_self = this;
var keep_running = true;
var counter = 0; // we use this to add a number
// Create first slug
values.hash_id = mh.makeHashID();
values.slug = mh.createSlugString(values.text);
async.whilst(
function () {
// execute whilst while other post has not been retrieved or while it matches a slug
// in the database
return keep_running;
},
function (callback) {
counter++;
// search for post with this slug
model_self.findOne().where({slug: values.slug}).then(function(op) {
if(op === undefined) {
// Nothing found, stop executing
keep_running = false;
} else {
// Create new slug
values.slug = mh.createSlugString(values.text, counter);
}
callback();
});
},
function (err) {
// End the test
next();
}
); // End whilst
}
});
module.exports = Post;
这种方法的优点是,它只是一直运行,直到找到一个独特的slug / hash_id,并且它不关心数字之间的间隙(如果slug_2存在但不是slug_1)。它也不关心你使用的数据库类型。
如果偶然两个进程在同一时刻写入相同的slug,它仍然可能导致问题,但这必须在几毫秒内发生。而且我认为防止这种情况的唯一方法就是以某种方式锁定表格 - 如果我有幸遇到这个问题,我可以解决这个问题......