Без системи відгуків додаток втрачає один з головних інструментів соціального доказу — користувачі не бачать чужого досвіду та не залишають свого. Ми стикалися з проєктами, де агрегований рейтинг перекошений через кілька ранніх відгуків, фотографії завантажуються 4 секунди, а модерацію обходять боти. Розробляємо систему відгуків під ключ за строк від 3 робочих днів — зірковий рейтинг, текстові відгуки, модерація, фото та відповіді бізнесу. Отримайте консультацію — оцінимо ваш проєкт безкоштовно.
Що зазвичай ламається в самописних реалізаціях
Агрегація та оновлення рейтингу
Найпоширеніша помилка — рахувати середній рейтинг на льоту SELECT AVG(rating) по всій таблиці відгуків при кожному запиті сторінки продукту. При 50 000 відгуків це починає гальмувати. Правильний підхід: денормалізоване поле average_rating та reviews_count на стороні сервера, яке оновлюється через тригер або чергу (Celery/Sidekiq/BullMQ) при додаванні/зміні/видаленні відгуку. Клієнт отримує вже готове значення.
На мобілці рейтинг потрібно відображати як зірковий індикатор — iOS та Android реалізують його по-різному. В UIKit будуємо кастомний UIView з CALayer-масками або збираємо з п'яти UIImageView зі статами .full, .half, .empty. В Jetpack Compose — Row з Icon та обчисленням через floor/ceil дробового значення. Анімацію заповнення при першому завантаженні робимо через withAnimation (Compose) або UIView.animate зі зміною ширини clip-mask.
Пагінація та infinite scroll у списку відгуків
Класичний OFFSET/LIMIT працює погано при великій кількості відгуків — на 10 000-й сторінці база все одно сканує весь індекс до потрібного зміщення. Використовуємо cursor-based pagination: сортуємо за created_at DESC, id DESC, у відповіді повертаємо next_cursor (base64 від останнього id + timestamp), наступний запит передає його як параметр. Cursor-based pagination працює в 10 разів швидше OFFSET/LIMIT на вибірках від 10 000 записів.
На iOS список будуємо на UICollectionView з UICollectionViewDiffableDataSource — додавання нової сторінки через applySnapshot без мерехтіння. prefetchDataSource запитує наступну сторінку коли до кінця залишається 3-4 комірки. На Android — LazyColumn з LazyPagingItems з Paging 3.
Фото до відгуку
Завантаження фото напряму через основний API — антипатерн. Правильна схема: клієнт запитує presigned URL у S3-сумісного сховища (AWS S3, Cloudflare R2, MinIO), завантажує файл напряму туди, потім передає в API лише ключ об'єкта. Стискання перед завантаженням — на клієнті: iOS через UIImage.jpegData(compressionQuality: 0.75), Android через Bitmap.compress(Bitmap.CompressFormat.JPEG, 75, outputStream). Ліміт — 2-3 фото, максимум 5 МБ на файл після стискання.
Відображення — через Kingfisher (iOS) або Coil (Android) з placeholder та crossfade 200ms. Для галереї при тапі — модальний UIPageViewController або HorizontalPager в Compose з пінч-зумом.
Як влаштована повна реалізація
Структура даних. Відгук містить: user_id, entity_id (продукт, послуга), entity_type, rating (1-5), body (текст, опціонально), photos[], status (pending/approved/rejected), helpful_count, created_at. Індекси: (entity_id, entity_type, status, created_at DESC) для вибірки схвалених відгуків за об'єктом.
Модерація. Автоматичний pre-filter через профаніті-фільтр (бібліотека bad-words або кастомний список на беку) + прапорець на ручну перевірку для відгуків з ключовими словами. Фото проходять через AWS Rekognition Moderation Labels або Google Cloud Vision SafeSearch перед публікацією. У панелі модератора — черга з approve/reject і можливістю відповісти на відгук.
Відповідь на відгук. Бізнес відповідає на відгук — це окрема сутність review_reply (один до одного з review). При публікації відповіді — push-сповіщення автору через FCM/APNs з deeplink на відгук.
Голосування «корисно». helpful_votes — окрема таблиця (user_id, review_id, UNIQUE). Ліміт: один голос з одного акаунта. На клієнті — оптимістичне оновлення лічильника з відкатом при помилці.
Верифікація покупки. Якщо платформа дозволяє — позначаємо відгуки від реальних покупців значком «Підтверджена покупка», перевіряючи наявність закритого замовлення з user_id та entity_id.
| Компонент | Технологія | Особливість |
|---|---|---|
| Зірковий рейтинг | SwiftUI / Jetpack Compose | Денормалізоване поле, тригер оновлення |
| Пагінація | cursor-based | Стабільна швидкість при 1M+ записів |
| Модерація | bad-words + AWS Rekognition | Автоматичний pre-filter + ручна черга |
| Фото | presigned URL + S3/Coil/Kingfisher | Стискання на клієнті до 5 МБ |
| Відповіді бізнесу | review_reply, push-сповіщення | FCM/APNs з deeplink |
| Голосування | UNIQUE (user, review) | Оптимістичне оновлення |
Чому cursor-based pagination краща за OFFSET/LIMIT?
Cursor-based pagination гарантує стабільну швидкість незалежно від кількості сторінок. При 1 000 000 відгуків запит з курсором виконується за ті самі мілісекунди, що й на першій сторінці. Дослідження PostgreSQL показує, що OFFSET на великих вибірках призводить до повного сканування індексу до зміщення. На мобілці це критично — користувач не повинен чекати підвантаження відгуків більше 200 мс.
Як організована модерація фото?
Автоматична перевірка через AWS Rekognition Moderation Labels або Google Cloud Vision SafeSearch виявляє NSFW-контент. У разі спрацювання — відгук позначається на ручну перевірку. Модератор у панелі бачить фото та текст, може approve або reject. Це знижує навантаження на команду та виключає появу небажаного контенту.
Етапи роботи
- Аудит поточної реалізації (якщо є).
- Проєктування схеми даних та API.
- Розробка бекенд-частини.
- Мобільний UI (обидві платформи або одна).
- Інтеграція модерації.
- Тестування навантаженням (Artillery/k6 на сценарій «500 одночасних відгуків»).
- Публікація.
Для Flutter-проєктів весь UI — один раз, логіка винесена в ReviewBloc (BLoC) або ReviewNotifier (Riverpod).
Що входить у роботу
- Проєктування схеми даних (ER-діаграма, опис індексів)
- REST/GraphQL API з документацією (Swagger/GraphQL Playground)
- Мобільний UI під iOS/Android або Flutter
- Інтеграція модерації (автоматична + ручна)
- Навантажувальне тестування та оптимізація
- Доступ до репозиторію, CI/CD-пайплайн
- Навчання команди замовника (1 сесія)
- Підтримка 2 тижні після релізу
Строки
Базова система (зірковий рейтинг, текстовий відгук, список з пагінацією, модерація через статус) — 3-5 робочих днів. З фотографіями, відповідями бізнесу, голосуванням та верифікацією покупки — 8-12 днів. Вартість розраховується індивідуально після аналізу вимог.
Типові помилки при реалізації
- Рахувати рейтинг на льоту без кешу - Використовувати OFFSET/LIMIT для пагінації - Не стискати фото перед завантаженням - Пропустити модерацію фото з NSFW-контентом - Не додати унікальність голосівЗверніться до нас — ми реалізували 20+ систем відгуків для маркетплейсів та сервісів більше 5 років. Оцінимо ваш проєкт за 1 день. Отримайте консультацію зараз.







