![]() |
Ответ: Чат: PHP + MySQLi или что то другое?
Если тебе нужно бращаться к данным которые являются суб-документом, то лучше не сувать все в один документ, а разбить по коллекциям. Поэтому лучше так:
users - юзеры rooms - комнаты, содержит массив users - что является массивом id пользователей. messages - сообщения. notifications - разного рода оповещания пользователям. Например что их пригласили в комнату или в чат с другим пользователем. Предположим такой пример документа messages: PHP код:
Можно содержать также у room'а, время последнего сообщение. Таким образом если кто-то пришел в чат и версия его кеша отличается, то клиент запросит у сервера данные недавней истории сообщений, также раскажет о времени в кеше. Естественно сервер не должен высылать сразу всю историю, а должен слать только недавние скажем 32 сообщения. Далее если клиент продолжает скроллить, и ему нужно еще сообщений, то запросит еще 32. Сортировка тут по времени, следственно просто запрашиваешь типо такого: PHP код:
Индексировать коллекции естественно нужно тоже как полагается. Например messages индексируй так: PHP код:
|
Ответ: Чат: PHP + MySQLi или что то другое?
16 мб на одну запись же.
|
Ответ: Чат: PHP + MySQLi или что то другое?
Цитата:
Индексация субдокументов не гибко и ограничено, плюс мешается с индексацией самого документа. В итоге лишь усложняет без какой-либо весомой причины. В чем выгода держать сообщения субдокументом комнаты? Я вижу только недостатоки. |
Ответ: Чат: 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 - список групп В общем виде вот так выглядят записи в этих хранилищах: Достать список групп - легко. Достать просто список друзей (чисто список _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); Пытался делать выхов в коллбэке, тогда работает, но код выглядит просто ужасно (ещё ужаснее чем сейчас). 2) связанно с первым, так как в функции friendCallback я делаю ещё один запрос для раширенных данных: Код:
function friendCallback(err, results){ Сейчас в результате всего этого дела пользователю приходит ответ с пустыми массивами. Код:
{"data":[{"type":0,"code":0,"friends":[],"groups":[]}],"error":null} Код:
SELECT u.id, зыЖ наверно это выглядит как тупое клянчанье "напишите за меня код", но нет, код не прошу. Прошу теорию, которую я, увы, не знаю. |
Ответ: Чат: PHP + MySQLi или что то другое?
Ой. Ты совсем не туда "пошел".
Во первых - асинхронное программирование, это концепт который ты видимо пока не понял. Суть заключается в том что у тебя есть только один поток выполнения кода. И есть асинхронный паттерн, где ты типо говоришь: сделай А и когда будет готов ответ, вызови функцию Б. Таким образом естественно у тебя два вызова и ответ на запрос будут обработаны сразу один за другим, не дожидаясь callback'ов. Это не PHP, где каждый вызов блокируется - что на самом деле очень плохо. Во первых делать API запросы которые возвращают много разной информации - очень плохо. Лучше сделать два запроса. Таким образом в разных частях кода, когда тебе нужны будут первые или вторые данные, ты сможешь использовать уже имеющийся API route. Пару советов по дизайну API: 1. Запросы должны быть конкретными и простыми. 2. Любой запрос должен выдавать нормальные ошибки и учитывать возможность не валидных данных от клиента. 3. Консистентный формат ответа - также помогает упростить реализацию API и клиента. Вот пример с двумя частями. Первая часть - это middleware для аутентификации по token'у пользователя - распространенный метод аутентификации с API. Этот токен не должен знать только клиент, и создается при аутентификации по логину паролю. Он также может иметь срок годности после последнего использования. При logout'е, token также должен удаляться. Каждая аутентификация должна создавать свой новый token. Это по сути token клиента/сессии. По token'у мы востанавливаем данные о пользователе. Далее имея пользователя, мы можем выполнять запрос от него. Т.к. этот middleware загружает пользователя нам, удобно сделать список полей которые мы запрашиваем, чтобы не делать дополнительный запрос после. Вторая часть кода, пример как получить список друзей и выслать ответ в виде JSON'а. Подрузумевается что список ID храниться в массиве friends у пользователя. Таким образом запросить список его друзей, очень просто. PHP код:
Также тебе нужно позаботиться в будущем о pagination - страницы, для infinite scrolling или просто системы страниц, т.к. если у кого-то слишком много друзей, грузить один большой список - это может быть не слишком хорошо. ИМХО, ты опять спешишь, и забегаешь слишком вперед. Старайся начинать с более простых примеров и задачек, и экспериментов. |
Ответ: Чат: PHP + MySQLi или что то другое?
Цитата:
Цитата:
Цитата:
2. Это прям сразу реализовал, до этого вёл довольно "крупный" проект по размещению данных в социальные сети (работал в рекламном агенстве, для чего писалось своё API над разными API социальных сетей (facebook, twitter, instagram, vk.com, livejournal) и целых ботов для некоторых соц.сетей (мой мир, одноклассники, google+). Поэтому ошибки которые были допущены там стараюсь не повторять и сразу предугадывать. 3. Это тоже стараюсь делать сразу, все ответы сводятся к JSON-массиву обязательно выглядящему либо так (отправляются запрошенные данные): Код:
{ Код:
{ Цитата:
Цитата:
Цитата:
За код спасибо, он для меня местами выглядит страшно, но вполне читаемо и понятно. Меня прёт в "реляционность", хоть тресни. Ещё не освоился с удобствами монги, поэтому сделал отдельную коллекцию friends со связками (users связаны с users в отношении многие-ко-многим через коллекцию friends), заместо того, чтобы хранить список id друзей у самого пользователя. Меня несёт не туда, а нода и монга попутно ещё и ломают мои взгляды на программирование в целом :-D. |
Ответ: Чат: PHP + MySQLi или что то другое?
Цитата:
Цитата:
Код простой - атомарный запрос с увеличением счетчика. Также вставит новую запись если ее уже нету: PHP код:
Цитата:
Я использовал middleware паттерн у express'а для этого. В общем запросы: app.get('/path', middlewareA, middlewareB, ..); Идея в том что middleware может быть сколько угодно, и они будут запускаться по очереди. Чтобы соблюдать очередь уважая возможную асинхронность в них, нужно самому вызывать next метод в каждом middleware. При этом можно вызвать next(new Error('...')); что закончит очередь, и вызовет middleware для ответа с ошибкой. Это удобно как в примере выше я привел для аутентификации например. Также и для pagination. Можно сделать что-то типо: PHP код:
Цитата:
Цитата:
В будущем выучить новый язык для тебя не составит труда вообще. :) |
Ответ: Чат: PHP + MySQLi или что то другое?
Вложений: 1
И тут неожиданно возникла странность. Нужно сделать удаление пользователей из списка, и казалось бы всё просто, но...
В общем, опишу сначала добавление друзей в список контактов. Происходит оно у меня так: ищем друга через поиск, жмакаем кнопку "добавить", обращаемся к соответствующему 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}}}); Так вот, этот этап я прекрасно пережил, всё шикарно и жизнь вроде бы удалась, удаление пользователя из списка друзей не должно стать какой то проблемой, но... Есть одно требование, которое надо было выполнить, что я и сделал. Заключается оно в том, что один друг может у текущего пользователя числиться сразу в нескольких группах. Это я реализовал тоже довольно просто - ещё один push в массив friends, с group_id нужной группы, всё работает. Соответственно с этого момента появляется 2 пути удаления: 1) полное удаление из списка (легко и не принуждённо ставим state = 0 всем текущим записям с _id друга в массиве friends пользователя) 2) удаление из какой то определённой группы (вот тут то и возникла проблема) Контактный список выглядит сейчас вот так: Вложение 22003 Проблема с удалением заключается в том, что когда я нажимаю удалить выделенного пользователя (Алексея) - у меня удаляется (state = 0) ПЕРВЫЙ пользователь в этой группе (то есть удаляется Людмила, а не Алексей). Запрос на удаление вот такой: Код:
collection.update({_id:owner,"friends._id":friend,"friends.group_id":group_id}, {$set:{"friends.$.state":0}}) Код:
collection.update({_id:owner,"friends._id":friend,"friends.group_id":group_id}, {$set:{"friends.$.state":1,"friends.$.add_date":addDate}}); Я понимаю что проблема скорее всего в неправильном использовании мной вот этой конструкции: Код:
"friends.$.state":1 На php+mysql, кстати, работает :-) Но там я делал несколько по другому, ведь была отдельная таблица friends, соответственно там надо было просто найти запись с нужным friend_id и group_id, и, поставить ей state = 0. У меня есть вариант как это сделать на ноде+монго чтоб 100% работало, но мне от чего-то кажется что он слегка черезжопный. Заключается вариант в том, что надо не пытаться апдейтить элемент массива friends в базе, а вытащить этот массив, "вживую" его проапдейтить и записать полностью на место старого. Реализовал сейчас временно такое "удаление", работает. Но не верю что нельзя вот так апдейтить как я выше пытался, явно должен быть способ. |
Ответ: Чат: PHP + MySQLi или что то другое?
Добавление group_id для меня кажется сомнительной затеей, по пару причинам:
1. friends содержит больше информации чем просто друзья 2. разные места хранения group_id, что тоже не збсь 3. Возможная дупликация данных, и необходимость это все менеджить. По этому упрости лучше себе жизнь, и в friends тупо храни список друзей. Если проще делать, то просто храни массив ID чтобы было проще индексировать. Если таки хочешь объект держать, то не используй массив, а используй реальный объект: PHP код:
Но все равно тебе прийдется иметь отдельно массив со списком всех ID в нем для индексации, т.к. индексировать key'и объекта нельзя. В итоге дополнительное поле будет: PHP код:
Ты изначально себе усложнил задачу используя массив. Также не храни там group_id, храни их в одной коллекции отдельно, чтобы было в разы проще оперировать этим делом. Обновить стейт друга 64 у пользователя 32: PHP код:
PHP код:
|
Ответ: Чат: PHP + MySQLi или что то другое?
Цитата:
Код:
friend = 64; //64 нам пришло из POST запроса. А обратиться так надо кровь из носу. И вставлять как? Вставка в массив происходила до безобразия просто $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}}); |
Ответ: Чат: PHP + MySQLi или что то другое?
Вставлять:
PHP код:
PHP код:
PHP код:
|
Ответ: Чат: 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 текущего пользователя. Надо будет попробовать так сделать. |
Ответ: Чат: PHP + MySQLi или что то другое?
Данные о сокетах, хранить нужно только в ОЗУ node.js сервера. Бд об этом знать не нужно вообще.
Вот я написал пример кода, как хранить в ОЗУ список пользователей, их списки соединений и кэш друзей и т.п. Твоя задача следить за кешем, чтобы он был всегда обновлен. Когда делаешь обновления через API добавляя/убирая там друзей, важно держать кеш обновленным тоже. По сути users кеш - это все пользователи онлайн. т.к. они хранятся по ключу, то проверка на онлайн и доступ к ним очень быстрый. Вот код, думаю идея понятна. PHP код:
|
Ответ: Чат: PHP + MySQLi или что то другое?
по ВРЕМЕНИ сортируется ровно как я задаю, а по ДАТЕ сортируется прямо противоположно. ЧЯДНТ? То есть имея несколько документов Код:
{...,date:ISODate("2015-11-01T12:58:53.724Z"),...} Код:
db.collection.find(...).sort({date:1}) Код:
{...,date:ISODate("2015-11-02T10:58:53.724Z"),...} Эмм... ПРошу меня простить, всё работает как надо. Не пойму в чём была проблема, но сейчас всё пучком и не запутано. |
Часовой пояс GMT +4, время: 06:17. |
vBulletin® Version 3.6.5.
Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
Перевод: zCarot