Извините, ничего не найдено.

Не расстраивайся! Лучше выпей чайку!
Регистрация
Справка
Календарь

Вернуться   forum.boolean.name > Программирование в широком смысле слова > Алгоритмика

Алгоритмика Об алгоритмах вообще; методы, обсуждения способов решения

Ответ
 
Опции темы
Старый 20.10.2015, 15:49   #16
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Если тебе нужно бращаться к данным которые являются суб-документом, то лучше не сувать все в один документ, а разбить по коллекциям. Поэтому лучше так:
users - юзеры
rooms - комнаты, содержит массив users - что является массивом id пользователей.
messages - сообщения.
notifications - разного рода оповещания пользователям. Например что их пригласили в комнату или в чат с другим пользователем.

Предположим такой пример документа messages:
{
    
_id32,
    
user16,
    
room64,
    
timestampISODate("2015-10-20T11:43:18.915Z"),
    
data'hello world!'

Для переписки 1-1 я бы тоже создавал room.

Можно содержать также у room'а, время последнего сообщение.
Таким образом если кто-то пришел в чат и версия его кеша отличается, то клиент запросит у сервера данные недавней истории сообщений, также раскажет о времени в кеше.
Естественно сервер не должен высылать сразу всю историю, а должен слать только недавние скажем 32 сообщения. Далее если клиент продолжает скроллить, и ему нужно еще сообщений, то запросит еще 32.
Сортировка тут по времени, следственно просто запрашиваешь типо такого:
db.messages.find({
    
'room'64,
    
'timestamp': {
        
$ltISODate("2015-10-20T11:56:07.516Z")
    }
}).
sort({ timestamp}).limit(32); 
Вот так получишь последние 32 сообщения, до определенной даты.

Индексировать коллекции естественно нужно тоже как полагается.
Например messages индексируй так:
db.messages.ensureIndex({
    
'room'1,
    
'timestamp': -1
}); 
Давай больше экспериментов, и начинай с попроще, не замудряй
(Offline)
 
Ответить с цитированием
Эти 2 пользователя(ей) сказали Спасибо moka за это полезное сообщение:
impersonalis (27.10.2015), St_AnGer (20.10.2015)
Старый 20.10.2015, 16:04   #17
pax
Unity/C# кодер
 
Аватар для pax
 
Регистрация: 03.10.2005
Адрес: Россия, Рязань
Сообщений: 7,568
Написано 3,006 полезных сообщений
(для 5,323 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

16 мб на одну запись же.
__________________
Blitz3d to Unity Wiki
(Offline)
 
Ответить с цитированием
Старый 20.10.2015, 16:37   #18
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Сообщение от pax Посмотреть сообщение
16 мб на одну запись же.
Ну тут больше вопрос о том что тебе нужно запросить у бд сообщения, отсортированные по дате, или другим параметрам, и вернуть ограниченный список.
Индексация субдокументов не гибко и ограничено, плюс мешается с индексацией самого документа.
В итоге лишь усложняет без какой-либо весомой причины.
В чем выгода держать сообщения субдокументом комнаты? Я вижу только недостатоки.
(Offline)
 
Ответить с цитированием
Старый 21.10.2015, 17:33   #19
St_AnGer
Элита
 
Аватар для St_AnGer
 
Регистрация: 21.01.2010
Адрес: Россия, Рязанская область, г.Михайлов
Сообщений: 2,067
Написано 1,185 полезных сообщений
(для 2,828 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

НЕОЖИДАННЫЙ вопрос.

Мне прям невмоготу как надо сделать join. Что то ничего полезного по теме не могу найти без лишних подключаемых модулей.

Задача: достать список друзей и групп одним api-вызовом.
Что имеем:
- 3 коллекции:
users - пользователи (требуемые поля _id, first_name, last_name)
friends - список друзей каждого пользователя (что то типа связывающей таблицы многие-ко-многим, содержит поля group_id, owner и friend, где group_id - _id группы в которую пользователь запихнул этого друга, owner - _id самого пользователя-владельца, friend - _id друга (по сути - _id другого пользователя))
groups - список групп

В общем виде вот так выглядят записи в этих хранилищах:

Users:
{
  "_id" : ObjectId("56263bd1bc9082875487820b"),
  "first_name" : "Вячеслав",
  "last_ip" : "",
  "last_name" : "Дядчиков",
  "online" : 1,
  "online_date" : ISODate("2015-10-21T07:49:30.93Z"),
  "register_date" : ISODate("2015-10-20T13:04:17.814Z"),
  "sex" : 1,
  "state" : 2
}
Friends:
{
  "owner" : ObjectId("56263bd1bc9082875487820b"),
  "friend" : ObjectId("562743368a654b61739a7b27"),
  "group_id" : 1,
  "state" : 1,
  "add_date" : ISODate("2015-10-21T11:43:17.498Z"),
  "_id" : ObjectId("56277a55f472bd707e67f2d7")
}
Groups:
{
  "_id" : 1,
  "name" : "Без группы",
  "owner" : 0,
  "state" : 1,
  "add_date" : ""
}


Достать список групп - легко.
Достать просто список друзей (чисто список _id друзей) - легко.
Достать расширенный список друзей - что то никак не получается. Ну, точнее получается, но... Сейчас опишу ситуацию.
Достать всё это сразу и отправить пользователю - вообще никак не получается.

Клиент делает вызов api "блаблабла/friends.get". Передаётся _id пользователя запрашивающего список. Должен вернуться JSON содержащий массив friends (одна запись содержит поля _id,first_name,last_name,group_id) и массив groups (одна запись содержит поля _id, name).

Соответственно как я вижу решение:
1) Берём из коллекции friends нужные записи по owner, и по этим записям берём из коллекции users данные (first_name, last_name).
2) Берём из коллекции groups нужные записи по owner
3) формируем JSON и отправляем в ответ на вызов API

На какие проблемы я наткнулся:
1) не пойму как вызывать функции ПОСЛЕДОВАТЕЛЬНО, т.е.
что бы функция поиска групп вызвалась сразу после окончания поиска друзей.
Сделал так:
db.collection('friends').find({owner:id,state:{$gt:0}}).toArray(friendCallback);
db.collection('groups').find({state:{$gt:0},$or:[{owner:id},{owner:0}]}).toArray(groupCallback);
res.end(JSON.stringify({
			data: [{
				type: 0,
				code: 0,
				friends: friends,
				groups: groups
			}],
			error: null
		}));
Но, оно выполняется сразу, вызывается сначала поиск в друзьях, потом не дожидаясь отрабатывания коллбэка вызывается поиск в группах, и , не дожидаясь отрабатывания коллбэка отправляется ответ.
Пытался делать выхов в коллбэке, тогда работает, но код выглядит просто ужасно (ещё ужаснее чем сейчас).

2) связанно с первым, так как в функции friendCallback я делаю ещё один запрос для раширенных данных:
function friendCallback(err, results){
	for (var i = 0; i < results.length; i++) {
		db.collection('users').find({_id:new ObjectID(results[i].friend)}).toArray(friendCallback1);
	}
}
И оно конечно же тоже не успевая отрабатывать перелетает по циклу, а ведь в friendCallback1 как раз происходит формирования массива friends, который потом отдаётся в ответ.

Сейчас в результате всего этого дела пользователю приходит ответ с пустыми массивами.
{"data":[{"type":0,"code":0,"friends":[],"groups":[]}],"error":null}
На SQL такое решается элементарным join:
SELECT	u.id,
		u.firstName,
		u.lastName,
		f.f_groupId
	FROM users AS u
	LEFT JOIN friends as f ON f.friend = u.id
	WHERE f.state = 1 AND f.owner = ?
Поиск вменяемого ответа не дал, да хотя я и ищу скорее всего не правильно (потому что ищу что то в стиле "как сделать join в node.js+mongodb"). Могу это реализовать тремя разными вызовами API (написав их, конечно же), но надо что бы всё прилетало в одном вызове.

зыЖ наверно это выглядит как тупое клянчанье "напишите за меня код", но нет, код не прошу. Прошу теорию, которую я, увы, не знаю.
__________________
Main PC:
Intel Core i5 4260U 1.44 GHz + LPDDR3 1x4096 1600 MHz + Intel HD Graphics 5000.

Asus Ёжик T101-MT:
Intel Atom N-570 1.66 Ghz + DDR2 2x1024 800 Mhz + Intel GMA 3150 128 Mb DDR2


Скачать Doom 2D: Remake v0.3.8a

Последний раз редактировалось St_AnGer, 22.10.2015 в 00:45.
(Offline)
 
Ответить с цитированием
Старый 22.10.2015, 03:54   #20
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Ой. Ты совсем не туда "пошел".

Во первых - асинхронное программирование, это концепт который ты видимо пока не понял.
Суть заключается в том что у тебя есть только один поток выполнения кода. И есть асинхронный паттерн, где ты типо говоришь: сделай А и когда будет готов ответ, вызови функцию Б.
Таким образом естественно у тебя два вызова и ответ на запрос будут обработаны сразу один за другим, не дожидаясь callback'ов.
Это не PHP, где каждый вызов блокируется - что на самом деле очень плохо.

Во первых делать API запросы которые возвращают много разной информации - очень плохо.
Лучше сделать два запроса. Таким образом в разных частях кода, когда тебе нужны будут первые или вторые данные, ты сможешь использовать уже имеющийся API route.

Пару советов по дизайну API:
1. Запросы должны быть конкретными и простыми.
2. Любой запрос должен выдавать нормальные ошибки и учитывать возможность не валидных данных от клиента.
3. Консистентный формат ответа - также помогает упростить реализацию API и клиента.

Вот пример с двумя частями.
Первая часть - это middleware для аутентификации по token'у пользователя - распространенный метод аутентификации с API. Этот токен не должен знать только клиент, и создается при аутентификации по логину паролю. Он также может иметь срок годности после последнего использования. При logout'е, token также должен удаляться.
Каждая аутентификация должна создавать свой новый token. Это по сути token клиента/сессии.
По token'у мы востанавливаем данные о пользователе. Далее имея пользователя, мы можем выполнять запрос от него.
Т.к. этот middleware загружает пользователя нам, удобно сделать список полей которые мы запрашиваем, чтобы не делать дополнительный запрос после.

Вторая часть кода, пример как получить список друзей и выслать ответ в виде JSON'а.
Подрузумевается что список ID храниться в массиве friends у пользователя.
Таким образом запросить список его друзей, очень просто.

var userAuthMiddleware = function(args) {
    var 
fields = { _id};
    if (
args.fields) {
        for(var 
0args.fields.lengthi++) {
            
fields[args.fields] = 1;
        }
    }

    return function(
reqresnext) {
        if (! 
req.query.token)
            return 
next(new Error('missing token'));

        
db.collection('auth').findOne({
            
_idreq.query.token
        
}, function(errtoken) {
            if (
err) return next(err);

            if (! 
token)
                return 
next(new Error('not authenticated'));

            
db.collection('users').findOne({
                
_idtoken.user
            
}, {
                
fieldsfields
            
}, function(erruser) {
                if (
err) return next(err);

                
// user might be not found
                
if (! user)
                    return 
next(new Error('user not found'));

                
// user logged in
                // store in req object so other middleware can access it
                
req.user user;

                
// call next middleware
                
next();
            });
        });
    }
};

// friends
app.get('/friends'userAuthMiddleware({
    
fields: [ 'friends' ]
}), function(
reqresnext) {
    
// find friends if have any
    
if (req.user.friends.length) {
        
db.collection('users').find({
            
_id: {
                
$inreq.user.friends // we store friend ids in user's array field `friends`
            
}
        }, {
            
fields: {
                
name// we need to get only their name
            
}
        }).
toArray(function(erritems) {
            if (
err) return next(err);
            
// response with array of users
            
res.json(items);
        });
    } else {
        
// if no friends, just response with empty array
        
res.json([ ]);
    }
}); 
Также лучше использовать численные ID, нежели ObjectID, т.к. они слишком большие и с ними геморойнее работать.
Также тебе нужно позаботиться в будущем о pagination - страницы, для infinite scrolling или просто системы страниц, т.к. если у кого-то слишком много друзей, грузить один большой список - это может быть не слишком хорошо.

ИМХО, ты опять спешишь, и забегаешь слишком вперед.
Старайся начинать с более простых примеров и задачек, и экспериментов.
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
St_AnGer (22.10.2015)
Старый 22.10.2015, 09:36   #21
St_AnGer
Элита
 
Аватар для St_AnGer
 
Регистрация: 21.01.2010
Адрес: Россия, Рязанская область, г.Михайлов
Сообщений: 2,067
Написано 1,185 полезных сообщений
(для 2,828 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Сообщение от moka Посмотреть сообщение
Ой. Ты совсем не туда "пошел".

Во первых - асинхронное программирование, это концепт который ты видимо пока не понял.
Суть заключается в том что у тебя есть только один поток выполнения кода. И есть асинхронный паттерн, где ты типо говоришь: сделай А и когда будет готов ответ, вызови функцию Б.
Таким образом естественно у тебя два вызова и ответ на запрос будут обработаны сразу один за другим, не дожидаясь callback'ов.
Это не PHP, где каждый вызов блокируется - что на самом деле очень плохо.
С асинхроном у меня плохо, согласен. Пользовался им только для Ajax и то в простой форме - запрос сделали, потом когда то пришёл ответ и отреагировали асинхронно на него.

Сообщение от moka Посмотреть сообщение
Во первых делать API запросы которые возвращают много разной информации - очень плохо.
Лучше сделать два запроса. Таким образом в разных частях кода, когда тебе нужны будут первые или вторые данные, ты сможешь использовать уже имеющийся API route.
Собственно сейчас уже "временно" так и сделал - два отдельных API-запроса. Но тут дело в том, что эти два вызова связаны (вывод списка пользователей не возможен без вывода списка групп), поэтому и хотел сразу в одном запросе получиться все данные.

Сообщение от moka Посмотреть сообщение
Пару советов по дизайну API:
1. Запросы должны быть конкретными и простыми.
2. Любой запрос должен выдавать нормальные ошибки и учитывать возможность не валидных данных от клиента.
3. Консистентный формат ответа - также помогает упростить реализацию API и клиента.
1. Все запросы стараюсь делать конкретными (кроме этого как раз), да. За основу для себя взял список API Вконтакте, так как все его API-запросы соответствуют моим (friends.get, friends.add, user.signup, user.auth, friends.search и т.д.). Они довольно прозрачны.
2. Это прям сразу реализовал, до этого вёл довольно "крупный" проект по размещению данных в социальные сети (работал в рекламном агенстве, для чего писалось своё API над разными API социальных сетей (facebook, twitter, instagram, vk.com, livejournal) и целых ботов для некоторых соц.сетей (мой мир, одноклассники, google+). Поэтому ошибки которые были допущены там стараюсь не повторять и сразу предугадывать.
3. Это тоже стараюсь делать сразу, все ответы сводятся к JSON-массиву обязательно выглядящему либо так (отправляются запрошенные данные):
{
	data: {
		type: 0,
		code: 02,
		...
	},
	error: null
}
либо так (отправляется ошибка)
{
	data: null, 
	error: {
		code: 123, 
		msg: ...
	}
}
Сообщение от moka Посмотреть сообщение
Также лучше использовать численные ID, нежели ObjectID, т.к. они слишком большие и с ними геморойнее работать.
В таком случае надо их генерировать самому? Просто не копал в эту сторону пока что, использовал что есть. ObjectID на данный момент мне не понравились своей нечитабельностью.

Сообщение от moka Посмотреть сообщение
Также тебе нужно позаботиться в будущем о pagination - страницы, для infinite scrolling или просто системы страниц, т.к. если у кого-то слишком много друзей, грузить один большой список - это может быть не слишком хорошо.
Вот собственно и хотел после "перевода" сервера (с PHP+MySQL на node.js+mongodb, благо реализовано пока что всего 15 только самых нужных API, реализую их постепенно по мере модификации грубого наброска веб-чатика для первой версии сервера) заняться pagination, потому что с проблемой её отсутствия уже сталкивался при первом подходе к чату.

Сообщение от moka Посмотреть сообщение
ИМХО, ты опять спешишь, и забегаешь слишком вперед.
Старайся начинать с более простых примеров и задачек, и экспериментов.
Вот прям в точку. Просто хочется (мне самому от себя) чтоб было сделано ещё вчера, поэтому спешу, лечу и пропускаю повороты. Из за этого часто возвращаюсь назад. Есть такая проблема у меня, трудно с этим спорить. Борюсь с ней потихоньку . Дело ещё и в том, что на пыхе всё что нужно на данном этапе могу реализовать сходу, а вот на js "влоб" не получается. Языки то, по сути и подходу к ним - разные. И как бы получается что "я хочу, я могу, я умею, но не этой отвёрткой и не этот шуруп". Не сошлось, короче . Ну это ничего, свыкнусь, освоюсь.

За код спасибо, он для меня местами выглядит страшно, но вполне читаемо и понятно. Меня прёт в "реляционность", хоть тресни. Ещё не освоился с удобствами монги, поэтому сделал отдельную коллекцию friends со связками (users связаны с users в отношении многие-ко-многим через коллекцию friends), заместо того, чтобы хранить список id друзей у самого пользователя. Меня несёт не туда, а нода и монга попутно ещё и ломают мои взгляды на программирование в целом .
__________________
Main PC:
Intel Core i5 4260U 1.44 GHz + LPDDR3 1x4096 1600 MHz + Intel HD Graphics 5000.

Asus Ёжик T101-MT:
Intel Atom N-570 1.66 Ghz + DDR2 2x1024 800 Mhz + Intel GMA 3150 128 Mb DDR2


Скачать Doom 2D: Remake v0.3.8a

Последний раз редактировалось St_AnGer, 22.10.2015 в 11:39.
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
moka (22.10.2015)
Старый 22.10.2015, 13:59   #22
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Сообщение от St_AnGer Посмотреть сообщение
С асинхроном у меня плохо, согласен. Пользовался им только для Ajax и то в простой форме - запрос сделали, потом когда то пришёл ответ и отреагировали асинхронно на него.
Привыкнешь, и потом будешь использовать "силу асинхронности" в свою пользу.


Сообщение от St_AnGer Посмотреть сообщение
В таком случае надо их генерировать самому? Просто не копал в эту сторону пока что, использовал что есть. ObjectID на данный момент мне не понравились своей нечитабельностью.
ObjectID имеет выгоду тем что он гарантированно уникален при наличии нескольких баз данных (кластеризация). Но это также обходиться и вообще редко нужно. Я использую отдельную коллекцию, с ID - именем коллекции которой генерим ID, и value - увеличивающимся числом.
Код простой - атомарный запрос с увеличением счетчика. Также вставит новую запись если ее уже нету:
db.collection('ids').findAndModify({
    
_id'users'
}, { }, {
    
$inc: {
        
value1
    
}
}, {
    
upserttrue,
    new: 
true
}, function(erritem) {
    if (
err) return next(err)
        
    
id item.value;
    
    
// ...
}); 
Вот тебе счетчик, который убедится что не будет повторений, даже если много процессов вставляют записи в одну бд.

Сообщение от St_AnGer Посмотреть сообщение
Вот собственно и хотел после "перевода" сервера (с PHP+MySQL на node.js+mongodb, благо реализовано пока что всего 15 только самых нужных API, реализую их постепенно по мере модификации грубого наброска веб-чатика для первой версии сервера) заняться pagination, потому что с проблемой её отсутствия уже сталкивался при первом подходе к чату.
pagination на самом деле просто делается.
Я использовал middleware паттерн у express'а для этого.
В общем запросы: app.get('/path', middlewareA, middlewareB, ..);
Идея в том что middleware может быть сколько угодно, и они будут запускаться по очереди. Чтобы соблюдать очередь уважая возможную асинхронность в них, нужно самому вызывать next метод в каждом middleware. При этом можно вызвать next(new Error('...')); что закончит очередь, и вызовет middleware для ответа с ошибкой.
Это удобно как в примере выше я привел для аутентификации например.
Также и для pagination.
Можно сделать что-то типо:
var pagination = function(args) {
    
args args || { };
    
args.skip args.skip || 0;
    
args.limit args.limit || 16;
    if (
args.sort) {
        var 
key args.sort;
        
args.sort = { };
        
args.sort[key] = args.order || -1;
    } else {
        
args.sort = { _id: -};
    }

    return function(
reqresnext) {
        var 
obj = { };

        
obj.skip parseInt(req.query.skip10);
        if (
isNaN(obj.skip))
            
obj.skip args.skip;

        
obj.limit parseInt(req.query.limit10);
        if (
isNaN(obj.limit))
            
obj.limit args.limit;

        
obj.sort args.sort;

        
req.pagination obj;

        
next();
    }
};

app.get('/friends',
    
userAuth({ // auth
        
fields: [ 'friends' ]
    }),
    
pagination({ // pagination
        
sort'name',
        
order: -1,
        
skip0,
        
limit16
    
}),
    function(
reqresnext) { // route controller
        
if (req.user.friends.length) {
            
db.collection('users').find({
                
_id: {
                    
$inreq.user.friends
                
}
            }, {
                
fields: {
                    
name1
                
}
            })
            .
sort(req.pagination.sort// there needs to be a sorting
            
.skip(req.pagination.skip// how many items to skip
            
.limit(req.pagination.limit// how many to return
            
.toArray(function(erritems) {
                if (
err) return next(err);
                
res.json(items);
            });
        } else {
            
res.json([ ]);
        }
    }
); 
И этот middleware можно переиспользовать в других API запросах естественно.

Сообщение от St_AnGer Посмотреть сообщение
Вот прям в точку. Просто хочется (мне самому от себя) чтоб было сделано ещё вчера, поэтому спешу, лечу и пропускаю повороты. Из за этого часто возвращаюсь назад. Есть такая проблема у меня, трудно с этим спорить. Борюсь с ней потихоньку . Дело ещё и в том, что на пыхе всё что нужно на данном этапе могу реализовать сходу, а вот на js "влоб" не получается. Языки то, по сути и подходу к ним - разные. И как бы получается что "я хочу, я могу, я умею, но не этой отвёрткой и не этот шуруп". Не сошлось, короче . Ну это ничего, свыкнусь, освоюсь.
Да мне тоже так хочется часто, жаль что приходится постепенно

Сообщение от St_AnGer Посмотреть сообщение
За код спасибо, он для меня местами выглядит страшно, но вполне читаемо и понятно. Меня прёт в "реляционность", хоть тресни. Ещё не освоился с удобствами монги, поэтому сделал отдельную коллекцию friends со связками (users связаны с users в отношении многие-ко-многим через коллекцию friends), заместо того, чтобы хранить список id друзей у самого пользователя. Меня несёт не туда, а нода и монга попутно ещё и ломают мои взгляды на программирование в целом .
Вот то что ломаются взгляды на кодинг - это круто. Т.к. это значит ты вылезаешь из рамок одного типа языков. Что в результате лишь расширит твое мышление как разработчика, и ты будешь думать не языками, а более абстрактно как программист. Что дает возможность подходить к задаче более рационально, используя нужный инструмент.
В будущем выучить новый язык для тебя не составит труда вообще.
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
St_AnGer (22.10.2015)
Старый 26.10.2015, 09:51   #23
St_AnGer
Элита
 
Аватар для St_AnGer
 
Регистрация: 21.01.2010
Адрес: Россия, Рязанская область, г.Михайлов
Сообщений: 2,067
Написано 1,185 полезных сообщений
(для 2,828 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

И тут неожиданно возникла странность. Нужно сделать удаление пользователей из списка, и казалось бы всё просто, но...

В общем, опишу сначала добавление друзей в список контактов. Происходит оно у меня так:
ищем друга через поиск, жмакаем кнопку "добавить", обращаемся к соответствующему API где происходит добавление друга в массив friends текущего пользователя. В базе о друзьях хранятся следующие поля:
1) _id - _id существующего в базе пользователя, который является друга
2) group_id - _id группы которую создали мы (группы - что то типа групп из Аськи, просто группировка контактов как удобнее, что бы можно было писать сразу всей группе, не создавая комнату и не добавляя туда всех вручную)
3) state - состояние друга в списке (0 - удалён, 1 - в списке)
4) add_date - дата добавления

Добавление происходит двумя путями:
1) добавляемого друга нету и никогда не было в списке - всё просто, делаем Push в массив friends с нужными полямиь, state, соответственно, равен 1;
2) друг уже когда то был в спиcке друзей, но мы его удалили и сейчас в массиве friends у него state равняется 0 - тут в целом тоже просто, хотя пришлось поразвлекаться в итоге свёл всё к одному "запросу" в базу:
collection.update({_id:owner}, {$push:{friends:{_id:friend, group_id:1,state:1,add_date:addDate}}});
group_id = 1 это общая группа с названием "Без группы".

Так вот, этот этап я прекрасно пережил, всё шикарно и жизнь вроде бы удалась, удаление пользователя из списка друзей не должно стать какой то проблемой, но... Есть одно требование, которое надо было выполнить, что я и сделал. Заключается оно в том, что один друг может у текущего пользователя числиться сразу в нескольких группах. Это я реализовал тоже довольно просто - ещё один push в массив friends, с group_id нужной группы, всё работает. Соответственно с этого момента появляется 2 пути удаления:
1) полное удаление из списка (легко и не принуждённо ставим state = 0 всем текущим записям с _id друга в массиве friends пользователя)
2) удаление из какой то определённой группы (вот тут то и возникла проблема)

Контактный список выглядит сейчас вот так:
Нажмите на изображение для увеличения
Название: trouble_with_chat.PNG
Просмотров: 945
Размер:	19.4 Кб
ID:	22003

Проблема с удалением заключается в том, что когда я нажимаю удалить выделенного пользователя (Алексея) - у меня удаляется (state = 0) ПЕРВЫЙ пользователь в этой группе (то есть удаляется Людмила, а не Алексей).

Запрос на удаление вот такой:

collection.update({_id:owner,"friends._id":friend,"friends.group_id":group_id}, {$set:{"friends.$.state":0}})
Такое поведение начинается если указывать _id группы из которой удаляем ("friends.group_id":group_id) в выборке на update. Если _id группы не указать - удаляется именно Алексей, но из первой группы (что вполне логично - туда ведь он был добавлен раньше, чем в Group 1, и обновляется как раз эта запись). И вот я не очень понимаю как эту проблему порешить, вообще не понимаю. Почему он обновляет запись не с нужными _id друга, я же явно указываю его _id ("friends._id":friend), и он точно верный? Ощущение что вообще полностью игнорируется моё конкретное указание _id друга, поэтому и обновляется первая запись с нужным group_id.

В чём парадокс - добавление в группу у меня происходит почти таким же запросом (в случае когда друг был в списке, но потом мы его удалили и теперь надо просто поставить state = 1):
collection.update({_id:owner,"friends._id":friend,"friends.group_id":group_id}, {$set:{"friends.$.state":1,"friends.$.add_date":addDate}});
И ОНО РАБОТАЕТ!Не работает, сейчас перепроверил. До этого обновлял сам специально первого пользователя в списке (в данном случае удалял и добавлял Людмилу), в этом случае всё работает. А вот если я в базе сам ручками поставлю Алексею state = 1 в группе Group 1 (Алексей идёт второй записью с данной группой) и потом попробую его добавить - происходит точно тоже самое что и при удалении, обновляется первая запись с данной группой (то есть обновляется Людмила). WTF?
Я понимаю что проблема скорее всего в неправильном использовании мной вот этой конструкции:
"friends.$.state":1
, конкретно $. Но до введения правила "один пользователь в многих группах" оно работало как надо - и добавляло, и удаляло как надо. Проблемы начались когда начал указывать group_id в update.

На php+mysql, кстати, работает Но там я делал несколько по другому, ведь была отдельная таблица friends, соответственно там надо было просто найти запись с нужным friend_id и group_id, и, поставить ей state = 0.

У меня есть вариант как это сделать на ноде+монго чтоб 100% работало, но мне от чего-то кажется что он слегка черезжопный. Заключается вариант в том, что надо не пытаться апдейтить элемент массива friends в базе, а вытащить этот массив, "вживую" его проапдейтить и записать полностью на место старого. Реализовал сейчас временно такое "удаление", работает. Но не верю что нельзя вот так апдейтить как я выше пытался, явно должен быть способ.
__________________
Main PC:
Intel Core i5 4260U 1.44 GHz + LPDDR3 1x4096 1600 MHz + Intel HD Graphics 5000.

Asus Ёжик T101-MT:
Intel Atom N-570 1.66 Ghz + DDR2 2x1024 800 Mhz + Intel GMA 3150 128 Mb DDR2


Скачать Doom 2D: Remake v0.3.8a

Последний раз редактировалось St_AnGer, 26.10.2015 в 12:44.
(Offline)
 
Ответить с цитированием
Старый 26.10.2015, 15:01   #24
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Добавление group_id для меня кажется сомнительной затеей, по пару причинам:
1. friends содержит больше информации чем просто друзья
2. разные места хранения group_id, что тоже не збсь
3. Возможная дупликация данных, и необходимость это все менеджить.

По этому упрости лучше себе жизнь, и в friends тупо храни список друзей.
Если проще делать, то просто храни массив ID чтобы было проще индексировать.
Если таки хочешь объект держать, то не используй массив, а используй реальный объект:
friends: {
    
friend_id: {
        
addedDate,
        
state1
    
}
}; 
friend_id - реальный ID друга.
Но все равно тебе прийдется иметь отдельно массив со списком всех ID в нем для индексации, т.к. индексировать key'и объекта нельзя.
В итоге дополнительное поле будет:
friend_ids: [ friend_id_1friend_id_2 ]; 
Когда у тебя будет такой формат данных, тебе будет в разы проще обновлять какие-то данные конкретного друга, либо удалить его сразу.

Ты изначально себе усложнил задачу используя массив.

Также не храни там group_id, храни их в одной коллекции отдельно, чтобы было в разы проще оперировать этим делом.

Обновить стейт друга 64 у пользователя 32:
db.users.update({
    
_id32
}, {
    
$set: {
        
'friends.64.state'0
    
}
}); 
Удалить друга:
db.users.update({
    
_id32
}, {
    
$unset: {
        
'friends.64'1
    
},
    
$pull: {
        
'friend_ids'64
    
}
}); 
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
St_AnGer (26.10.2015)
Старый 27.10.2015, 11:27   #25
St_AnGer
Элита
 
Аватар для St_AnGer
 
Регистрация: 21.01.2010
Адрес: Россия, Рязанская область, г.Михайлов
Сообщений: 2,067
Написано 1,185 полезных сообщений
(для 2,828 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Сообщение от moka Посмотреть сообщение
Обновить стейт друга 64 у пользователя 32:
db.users.update({
    
_id32
}, {
    
$set: {
        
'friends.64.state'0
    
}
}); 
Удалить друга:
db.users.update({
    
_id32
}, {
    
$unset: {
        
'friends.64'1
    
},
    
$pull: {
        
'friend_ids'64
    
}
}); 
Как это сделать в БД я понял, а как мне в скрипте обратиться к тому же другу 64, если я знаю только переменную содержащую это значение? Ну то есть, я руками вставил друга 64, а дальше?

friend = 64; //64 нам пришло из POST запроса.

db.collection('users', function(err, collection){
	collection.find({_id:owner}).toArray(function(err, results){
		if (results[0].friends.friend) {
			...
		}
	});
});
results[0].friends.friend естественно не может найти поля friend.
А обратиться так надо кровь из носу.



И вставлять как? Вставка в массив происходила до безобразия просто $push-ем:
collection.update({_id:owner}, {$push:{friends:{_id:friend, group_id:0,state:1,add_date:addDate}}});
Как же быть с объектом?

И ещё - тип "объект" в данном случае будет эквивалентен типу "документ"?

АПД с созданием объекта я протупил что то. Объект в js этож как ассоциативный массив:
friends[64] = {...}
Надо иногда отдыхать...

Остался вопрос грамотной вставки нового друга в базу. И вопрос доступа. Я же владею только переменной id-друга которая пришла в запросе, надо как то собрать в этом вот куске
collection.update({_id:owner}, {$set:{'friends.64.state':0}});
строку 'friends.64.state' имея переменную friend = 64. Пока что не пойму как.
__________________
Main PC:
Intel Core i5 4260U 1.44 GHz + LPDDR3 1x4096 1600 MHz + Intel HD Graphics 5000.

Asus Ёжик T101-MT:
Intel Atom N-570 1.66 Ghz + DDR2 2x1024 800 Mhz + Intel GMA 3150 128 Mb DDR2


Скачать Doom 2D: Remake v0.3.8a

Последний раз редактировалось St_AnGer, 27.10.2015 в 16:01.
(Offline)
 
Ответить с цитированием
Старый 27.10.2015, 19:12   #26
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Вставлять:

var data = { };
data['friends.' id] = {
    
state0
};

collection.update({
    
_idowner
}, {
    
$setdata,
    
$addToSet: {
        
friend_idsid
    
}
}); 
Обновлять стейт:
var data = { };
data['friends.' id '.state'] = 0;

collection.update({
    
_idowner
}, {
    
$setdata
}); 
Если используешь node 4+ то там есть computed properties, часть ecma6, и можно просто так:

collection.update({
    
_idowner
}, {
    
$set: {
        [
'friends.' id '.state']: 0
    
}
}); 
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
St_AnGer (27.10.2015)
Старый 29.10.2015, 09:36   #27
St_AnGer
Элита
 
Аватар для St_AnGer
 
Регистрация: 21.01.2010
Адрес: Россия, Рязанская область, г.Михайлов
Сообщений: 2,067
Написано 1,185 полезных сообщений
(для 2,828 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Спасибо тебе Максим огромное! Ежели бы не ты, сидел бы мой сервачок на пэхапэ и дальше
В общем то, всю "ненужную" часть чатилки я реализовал (ну там добавление в друзья, группы, перенос в группы, удаление, просмотр информации, голосование за принятие в сеть и т.п.). Теперь нужно организовывать сами комнаты и переписку в них. Собственно, пока что я для реализации "онлайн"-переписки остановился на библиотеке socket.io, понравилась она мне своей простотой и мощью.

Для начала, потренироваться в обращении с библиотекой, я решил сделать некое подобие онлайн-оффлайн для пользователей как это было в Аське. Т.е. вошёл - тебе присвоился и отправился всем кому надо статус "онлайн", вышел - соответственно "оффлайн". И вот у меня возник вопрос - а как отправлять данные на нужные сокеты (т.е. только тем у кого ты в друзьях)?

Я сейчас реализовал это ровно так, как вижу сам.

Для начала, моё "api" разделилось на 2 части - часть основанная на чистой node.js (всякая мишура для обвязки чата) и часть использующая socket.io (онлайн-офлайн, комнаты и переписка).

Далее, я каждому пользователю добавил "скрытое" поле in_friend, которое содержит всех кто добавил его в друзья. Сделал я это исключительно из соображений скорости - пробежаться по одному массиву/объекту явно быстрее, чем перебрать всю базу и посмотреть у кого же наш вошедший пользователь в друзьях. Собственно, тут всё просто. А вот дальше началась жесть.

Для того что бы запоминать сокеты для клиентов я создал массив sockets, в который вносятся новые сокеты или обновляются старые при обращении через socket.io. Т.е. массив не содержит данных больше, чем должен содержать. Сравнение "старый-новый" происходит по моему собственному полю _id, которое я внёс в объект session у сокетов. Если сокет с сессией содержащей _id уже есть - заменяем на новый, на данном этапе это не критично, потом, конечно, может не кисло так аукнуться (в конце расписал почему), но будем разбираться с проблемами по мере их поступления, сейчас не об этом.

Так вот, собственно в сокетах реализовал два события, online и offline, которые берут из базы инфу о пользователе (id пользователя передаётся вместе с событием online/offline), достают из поля in_friend все id тех у кого пользователь состои в друзьях, потом я пробегаюсь по массиву с друзьями и внутри по массиву с сокетами, найдя нужный сокет отправляю соответствующее событие тем кого найду в этом массиве. Смущает один момент. Пока количество сокетов крайне мало (я вот имею возможность тестить на 5-6 машинах), перебор массива не представляет опасности. А если сокетов в массиве будет тысяча? Не уничтожит ли это мой сервер, если одновременно событие online/offline передадут хотя бы 20 человек, каждый из которых, допустим, состоит в друзьях у 40 человек? Ведь это уже будет фактически 40*1000*20 = 800000 циклов с отправкой данных по сокетам, что просто нереально дофига на такое маленькое событие как передача статуса online/offline. И точно такое же количество циклов будет и на приглашение в комнату, и на отправку сообщения. Т.е. нагрузка будет очень и очень недурственная только из за такого моего подхода к сокетам.

зыЖ Подсчёт количества циклов очень грубый, у меня стоит break в цикле с сокетами после совпадения _id и отправки статуса. Так как у меня сокеты с _id пользователя не повторяются, то этот подход с выходом из цикла вполне применим, как мне кажется. Но этот же подход накладывает очень существенное ограничение - один пользователь будет сидеть только с одного клиента, причём где зашёл последним, там и будет работать всё, старые сокеты будут замещены новым.

зыыЖ Забыл упомянуть, изредка клиентом посылается "alive"-пакет (раз в 60 секунд сейчас сделал), который записывает отославшему пользователю статус online в базе данных и записывает время получения этого пакета. При запросе списка друзей другими пользователями (у кого в списке есть наш пользователь) идёт проверка дат пакетов "alive", если разница между текущей датой и датой "alive" > 60 секунд - пользователю в базе записывается статус offline. Работает Но, опять же, вопрос возможной нагрузки стоИт остро.

зыыыЖ Сейчас пришла в голову идея хранить все живые сокеты пользователя в базе данных, допустим в поле sockets. Это должно избавить от перебора общего огромного массива сокетов, ведь нужные сокеты (а сокет же в данном случае простой JS объект?) мы сможем достать только лишь опираясь на массив in_friends текущего пользователя. Надо будет попробовать так сделать.
__________________
Main PC:
Intel Core i5 4260U 1.44 GHz + LPDDR3 1x4096 1600 MHz + Intel HD Graphics 5000.

Asus Ёжик T101-MT:
Intel Atom N-570 1.66 Ghz + DDR2 2x1024 800 Mhz + Intel GMA 3150 128 Mb DDR2


Скачать Doom 2D: Remake v0.3.8a

Последний раз редактировалось St_AnGer, 29.10.2015 в 12:36.
(Offline)
 
Ответить с цитированием
Старый 29.10.2015, 18:07   #28
moka
.
 
Регистрация: 05.08.2006
Сообщений: 10,429
Написано 3,454 полезных сообщений
(для 6,863 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Данные о сокетах, хранить нужно только в ОЗУ node.js сервера. Бд об этом знать не нужно вообще.

Вот я написал пример кода, как хранить в ОЗУ список пользователей, их списки соединений и кэш друзей и т.п.
Твоя задача следить за кешем, чтобы он был всегда обновлен. Когда делаешь обновления через API добавляя/убирая там друзей, важно держать кеш обновленным тоже.

По сути users кеш - это все пользователи онлайн.
т.к. они хранятся по ключу, то проверка на онлайн и доступ к ним очень быстрый.

Вот код, думаю идея понятна.
// users cache object
var users = { };
// users load requests queue
var userRequests = { };


// user class
function User(data) {
    
this.id data.id;
    
this.name data.name;
    
this.friends data.friend_ids;
    
this.sockets = { };
    
this.connections 0;
}

// send message to all user connections
User.prototype.send = function(namedata) {
    for(var 
key in this.sockets)
        
this.sockets[key].emit(namedata);
};

// add user connection
User.prototype.socketAdd = function(socket) {
    if (
this.sockets[socket.id])
        return 
false;

    
this.sockets[socket.id] = socket;
    
this.connections++;

    return 
true;
};

// remove user connection
User.prototype.socketRemove = function(socket) {
    if (! 
this.sockets[socket.id])
        return 
false;

    
delete this.sockets[socket.id];
    
this.connections--;

    return 
true;
};


// try get or load user
var userGetOrLoad = function(idfn) {
    if (
users[id])
        return 
fn(nullusers[id]);

    
// loading in progress, just queue then
    
if (userRequests[id])
        return 
userRequests[id].push(fn);

    
// first one to try loading
    
userRequests[id] = [ fn ];

    
// once loaded, report to all who need this info
    
var finish = function(erruser) {
        for(var 
0userRequests[id].lengthi++)
            
userRequests[id](erruser);

        
delete userRequests[id];
    };

    
// try load user from db
    
db.collection('users').findOne({
        
_idid
    
}, {
        
fields: {
            
name1,
            
friend_ids1
        
}
    }, function(
erritem) {
        var 
user null;

        
// if user loaded, create cache
        
if (! err && item)
            
user users[id] = new User(item);

        
finish(erruser);
    });
};

// add client to user
var addClient = function(userIdsocket) {
    
userGetOrLoad(userId, function(erruser) {
        if (
err || ! user)
            return;

        var 
added user.socketAdd(socket);

        if (
added) {
            
// notify all friends about user being online
            
for(var 0user.friends.lengthi++) {
                var 
friend users[user.friends[i]];
                if (! 
friend) continue;
                
friend.send('friend:online', { iduser.id });
            }
        }
    });
};

// remove client from user
var removeClient = function(userIdsocket) {
    var 
user users[userId];

    if (! 
user)
        return;

    
user.socketRemove(socket);

    if (
user.connections === 0) {
        
delete users[userId];

        
// notify all friends that user is offline now
        
for(var 0user.friends.lengthi++) {
            var 
friend users[user.friends[i]];
            if (! 
friend) continue;
            
friend.send('friend:offline', { iduser.id });
        }
    }
}; 
(Offline)
 
Ответить с цитированием
Сообщение было полезно следующим пользователям:
St_AnGer (29.10.2015)
Старый 02.11.2015, 20:20   #29
St_AnGer
Элита
 
Аватар для St_AnGer
 
Регистрация: 21.01.2010
Адрес: Россия, Рязанская область, г.Михайлов
Сообщений: 2,067
Написано 1,185 полезных сообщений
(для 2,828 пользователей)
Ответ: Чат: PHP + MySQLi или что то другое?

Хм, неожиданный косяк выскочил у меня тут при попытке сортировки по полю формата ISODate. При сортировке по таковому полю происходит следующее:
по ВРЕМЕНИ сортируется ровно как я задаю, а по ДАТЕ сортируется прямо противоположно.
ЧЯДНТ?

То есть имея несколько документов
{...,date:ISODate("2015-11-01T12:58:53.724Z"),...}
{...,date:ISODate("2015-11-02T11:58:53.724Z"),...}
{...,date:ISODate("2015-11-01T14:58:53.724Z"),...}
{...,date:ISODate("2015-11-02T12:58:53.724Z"),...}
{...,date:ISODate("2015-11-02T10:58:53.724Z"),...}
и совершая выборку с сортировкой по возрастанию
db.collection.find(...).sort({date:1})
результат имеем следующий:
{...,date:ISODate("2015-11-02T10:58:53.724Z"),...}
{...,date:ISODate("2015-11-02T11:58:53.724Z"),...}
{...,date:ISODate("2015-11-02T12:58:53.724Z"),...}
{...,date:ISODate("2015-11-01T12:58:53.724Z"),...}
{...,date:ISODate("2015-11-01T14:58:53.724Z"),...}
Как же так? Мне казалось что сортировать должно и по дате, и по времени в том формате, в котором я задал (т.е. или ASC или DESC, а не пополам)...


Эмм... ПРошу меня простить, всё работает как надо. Не пойму в чём была проблема, но сейчас всё пучком и не запутано.
__________________
Main PC:
Intel Core i5 4260U 1.44 GHz + LPDDR3 1x4096 1600 MHz + Intel HD Graphics 5000.

Asus Ёжик T101-MT:
Intel Atom N-570 1.66 Ghz + DDR2 2x1024 800 Mhz + Intel GMA 3150 128 Mb DDR2


Скачать Doom 2D: Remake v0.3.8a
(Offline)
 
Ответить с цитированием
Ответ


Опции темы

Ваши права в разделе
Вы не можете создавать темы
Вы не можете отвечать на сообщения
Вы не можете прикреплять файлы
Вы не можете редактировать сообщения

BB коды Вкл.
Смайлы Вкл.
[IMG] код Вкл.
HTML код Выкл.


Часовой пояс GMT +4, время: 23:07.


vBulletin® Version 3.6.5.
Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
Перевод: zCarot
Style crйe par Allan - vBulletin-Ressources.com