uni-app 点赞系统
核心知识点讲解
1. 点赞系统概述
点赞系统是现代应用中常见的互动功能,它可以:
- 衡量内容的受欢迎程度
- 增强用户参与感和互动性
- 为内容推荐系统提供数据支持
- 鼓励优质内容的创作
2. 点赞系统架构
一个完整的点赞系统通常包含以下组件:
2.1 前端组件
- 点赞按钮:用户触发点赞操作的界面元素
- 点赞状态管理:管理用户的点赞状态
- 点赞计数:显示内容的点赞数量
- 动画效果:提供视觉反馈的动画
2.2 后端服务
- 点赞 API:处理点赞和取消点赞的请求
- 点赞存储:数据库设计和存储策略
- 缓存机制:提高点赞计数的读取性能
- 防作弊:防止恶意刷点赞的措施
3. 点赞数据结构
合理的点赞数据结构设计对于系统的性能和可扩展性非常重要:
3.1 点赞记录结构
{
id: String, // 点赞ID
userId: String, // 用户ID
targetId: String, // 目标ID(如文章ID、评论ID)
targetType: String, // 目标类型(如 article、comment)
createdAt: Timestamp, // 创建时间
updatedAt: Timestamp // 更新时间
}3.2 目标对象结构
{
id: String, // 目标ID
likes: Number, // 点赞数
likedBy: Array, // 点赞用户ID列表(可选,用于快速判断)
// 其他字段...
}4. 点赞功能实现
4.1 前端实现要点
- 状态管理:使用 Vuex 或组件级状态管理点赞状态
- 乐观更新:先更新本地状态,再同步到服务器
- 防重复点击:添加点击防抖,防止重复操作
- 动画效果:添加点赞动画,提升用户体验
4.2 后端实现要点
- 幂等性处理:确保重复请求不会导致错误
- 事务处理:保证点赞操作的原子性
- 缓存更新:及时更新点赞计数缓存
- 数据统计:记录点赞行为数据,用于分析
5. 点赞状态管理
点赞状态管理是前端实现的核心部分,需要考虑以下几点:
- 本地状态:使用 Vuex 或组件 data 存储点赞状态
- 持久化:可选地将点赞状态持久化到本地存储
- 状态同步:确保多端点赞状态的一致性
- 批量处理:优化批量点赞状态的获取和更新
6. 点赞计数更新
点赞计数的更新需要考虑性能和一致性:
- 实时更新:前端立即更新计数,提供即时反馈
- 异步同步:后台异步同步到数据库
- 缓存策略:使用 Redis 等缓存提高计数读取性能
- 计数一致性:定期校准缓存和数据库中的计数
7. 防作弊措施
为了防止恶意刷点赞,需要实现以下措施:
- 用户认证:确保只有登录用户可以点赞
- 速率限制:限制单个用户的点赞频率
- 设备指纹:识别和限制异常设备
- 行为分析:检测和阻止异常点赞行为
实用案例分析
案例:实现完整的点赞系统
功能需求
我们需要实现一个包含以下功能的点赞系统:
- 内容点赞和取消点赞
- 点赞状态管理
- 点赞计数更新
- 点赞动画效果
- 防作弊措施
实现方案
1. 点赞 API 服务
首先,我们需要实现点赞相关的 API 服务:
// api/like.js
// 导入请求封装
import request from './request';
// 点赞 API
export const likeApi = {
// 点赞
like: (data) => {
return request({
url: '/api/likes',
method: 'POST',
data
});
},
// 取消点赞
unlike: (data) => {
return request({
url: '/api/likes/cancel',
method: 'POST',
data
});
},
// 获取点赞状态
getLikeStatus: (params) => {
return request({
url: '/api/likes/status',
method: 'GET',
params
});
},
// 批量获取点赞状态
getBatchLikeStatus: (data) => {
return request({
url: '/api/likes/batch-status',
method: 'POST',
data
});
}
};2. 点赞组件
实现点赞按钮组件,支持动画效果:
<template>
<view class="like-button" @click="toggleLike">
<view
class="like-icon"
:class="{ 'liked': isLiked }"
:style="iconStyle"
>
<text class="like-emoji">{{ isLiked ? '❤️' : '🤍' }}</text>
</view>
<view class="like-count" :class="{ 'liked': isLiked }">
{{ likeCount }}
</view>
</view>
</template>
<script>
export default {
props: {
// 目标ID
targetId: {
type: String,
required: true
},
// 目标类型
targetType: {
type: String,
default: 'article'
},
// 初始点赞数
count: {
type: Number,
default: 0
},
// 初始点赞状态
liked: {
type: Boolean,
default: false
},
// 是否显示计数
showCount: {
type: Boolean,
default: true
}
},
data() {
return {
isLiked: this.liked,
likeCount: this.count,
isAnimating: false,
iconScale: 1
};
},
computed: {
iconStyle() {
return {
transform: `scale(${this.iconScale})`
};
}
},
watch: {
liked: {
handler(newVal) {
this.isLiked = newVal;
},
immediate: true
},
count: {
handler(newVal) {
this.likeCount = newVal;
},
immediate: true
}
},
methods: {
// 切换点赞状态
toggleLike() {
if (this.isAnimating) return;
// 触发点赞动画
this.triggerLikeAnimation();
// 切换本地状态
this.isLiked = !this.isLiked;
this.likeCount += this.isLiked ? 1 : -1;
// 触发自定义事件
this.$emit('like-change', {
targetId: this.targetId,
targetType: this.targetType,
isLiked: this.isLiked,
likeCount: this.likeCount
});
},
// 触发点赞动画
triggerLikeAnimation() {
this.isAnimating = true;
// 点赞动画
this.iconScale = 1.2;
setTimeout(() => {
this.iconScale = 1;
setTimeout(() => {
this.isAnimating = false;
}, 150);
}, 150);
}
}
};
</script>
<style scoped>
.like-button {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.like-icon {
margin-right: 8rpx;
transition: transform 0.3s ease;
}
.like-emoji {
font-size: 32rpx;
transition: all 0.3s ease;
}
.like-icon.liked .like-emoji {
color: #FF3B30;
filter: drop-shadow(0 0 4rpx rgba(255, 59, 48, 0.5));
}
.like-count {
font-size: 24rpx;
color: #666;
transition: all 0.3s ease;
}
.like-count.liked {
color: #FF3B30;
}
/* 禁用状态 */
.like-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>3. 点赞状态管理
使用 Vuex 管理全局点赞状态:
// store/modules/like.js
import { likeApi } from '@/api/like';
export default {
namespaced: true,
state: {
// 点赞状态映射 { targetType: { targetId: isLiked } }
likeStatus: {},
// 点赞计数映射 { targetType: { targetId: count } }
likeCounts: {},
// 加载状态
loading: false
},
mutations: {
// 设置点赞状态
SET_LIKE_STATUS(state, { targetType, targetId, isLiked }) {
if (!state.likeStatus[targetType]) {
state.likeStatus[targetType] = {};
}
state.likeStatus[targetType][targetId] = isLiked;
},
// 批量设置点赞状态
SET_BATCH_LIKE_STATUS(state, { targetType, statusMap }) {
if (!state.likeStatus[targetType]) {
state.likeStatus[targetType] = {};
}
state.likeStatus[targetType] = {
...state.likeStatus[targetType],
...statusMap
};
},
// 设置点赞计数
SET_LIKE_COUNT(state, { targetType, targetId, count }) {
if (!state.likeCounts[targetType]) {
state.likeCounts[targetType] = {};
}
state.likeCounts[targetType][targetId] = count;
},
// 更新点赞计数
UPDATE_LIKE_COUNT(state, { targetType, targetId, delta }) {
if (!state.likeCounts[targetType]) {
state.likeCounts[targetType] = {};
}
if (state.likeCounts[targetType][targetId] === undefined) {
state.likeCounts[targetType][targetId] = 0;
}
state.likeCounts[targetType][targetId] += delta;
},
// 设置加载状态
SET_LOADING(state, loading) {
state.loading = loading;
}
},
actions: {
// 点赞
async like({ commit, state }, { targetId, targetType = 'article' }) {
try {
commit('SET_LOADING', true);
// 调用 API
await likeApi.like({ targetId, targetType });
// 更新状态
commit('SET_LIKE_STATUS', { targetType, targetId, isLiked: true });
commit('UPDATE_LIKE_COUNT', { targetType, targetId, delta: 1 });
return true;
} catch (error) {
console.error('点赞失败:', error);
// 恢复原状态
commit('SET_LIKE_STATUS', { targetType, targetId, isLiked: state.likeStatus[targetType]?.[targetId] || false });
commit('UPDATE_LIKE_COUNT', { targetType, targetId, delta: -1 });
return false;
} finally {
commit('SET_LOADING', false);
}
},
// 取消点赞
async unlike({ commit, state }, { targetId, targetType = 'article' }) {
try {
commit('SET_LOADING', true);
// 调用 API
await likeApi.unlike({ targetId, targetType });
// 更新状态
commit('SET_LIKE_STATUS', { targetType, targetId, isLiked: false });
commit('UPDATE_LIKE_COUNT', { targetType, targetId, delta: -1 });
return true;
} catch (error) {
console.error('取消点赞失败:', error);
// 恢复原状态
commit('SET_LIKE_STATUS', { targetType, targetId, isLiked: state.likeStatus[targetType]?.[targetId] || true });
commit('UPDATE_LIKE_COUNT', { targetType, targetId, delta: 1 });
return false;
} finally {
commit('SET_LOADING', false);
}
},
// 获取点赞状态
async getLikeStatus({ commit }, { targetId, targetType = 'article' }) {
try {
const res = await likeApi.getLikeStatus({ targetId, targetType });
commit('SET_LIKE_STATUS', { targetType, targetId, isLiked: res.data.isLiked });
commit('SET_LIKE_COUNT', { targetType, targetId, count: res.data.count });
return res.data;
} catch (error) {
console.error('获取点赞状态失败:', error);
return { isLiked: false, count: 0 };
}
},
// 批量获取点赞状态
async getBatchLikeStatus({ commit }, { targetIds, targetType = 'article' }) {
try {
const res = await likeApi.getBatchLikeStatus({ targetIds, targetType });
commit('SET_BATCH_LIKE_STATUS', { targetType, statusMap: res.data.statusMap });
// 更新计数
Object.keys(res.data.countMap).forEach(targetId => {
commit('SET_LIKE_COUNT', { targetType, targetId, count: res.data.countMap[targetId] });
});
return res.data;
} catch (error) {
console.error('批量获取点赞状态失败:', error);
return { statusMap: {}, countMap: {} };
}
}
},
getters: {
// 获取点赞状态
getLikeStatus: (state) => (targetId, targetType = 'article') => {
return state.likeStatus[targetType]?.[targetId] || false;
},
// 获取点赞计数
getLikeCount: (state) => (targetId, targetType = 'article') => {
return state.likeCounts[targetType]?.[targetId] || 0;
},
// 获取批量点赞状态
getBatchLikeStatus: (state) => (targetIds, targetType = 'article') => {
const statusMap = {};
targetIds.forEach(targetId => {
statusMap[targetId] = state.likeStatus[targetType]?.[targetId] || false;
});
return statusMap;
},
// 获取批量点赞计数
getBatchLikeCount: (state) => (targetIds, targetType = 'article') => {
const countMap = {};
targetIds.forEach(targetId => {
countMap[targetId] = state.likeCounts[targetType]?.[targetId] || 0;
});
return countMap;
}
}
};4. 点赞系统使用示例
在内容列表中使用点赞组件:
<template>
<view class="content-list">
<view
v-for="item in contentList"
:key="item.id"
class="content-item"
>
<!-- 内容标题 -->
<text class="content-title">{{ item.title }}</text>
<!-- 内容摘要 -->
<text class="content-summary">{{ item.summary }}</text>
<!-- 内容操作 -->
<view class="content-actions">
<!-- 点赞按钮 -->
<like-button
:target-id="item.id"
:target-type="'article'"
:count="likeCount(item.id)"
:liked="isLiked(item.id)"
@like-change="handleLikeChange"
/>
<!-- 其他操作 -->
<view class="content-action">
<text class="action-icon">💬</text>
<text class="action-text">{{ item.commentCount }}</text>
</view>
<view class="content-action">
<text class="action-icon">🔗</text>
<text class="action-text">分享</text>
</view>
</view>
</view>
</view>
</template>
<script>
import LikeButton from '@/components/LikeButton.vue';
import { mapGetters, mapActions } from 'vuex';
export default {
components: {
LikeButton
},
data() {
return {
contentList: [
{
id: '1',
title: 'uni-app 开发最佳实践',
summary: '本文介绍 uni-app 开发中的一些最佳实践,帮助你提高开发效率和应用性能。',
commentCount: 23
},
{
id: '2',
title: 'uni-app 性能优化技巧',
summary: '本文分享一些 uni-app 性能优化的技巧,帮助你打造更流畅的应用体验。',
commentCount: 45
}
]
};
},
computed: {
...mapGetters('like', [
'getLikeStatus',
'getLikeCount'
])
},
methods: {
...mapActions('like', [
'like',
'unlike'
]),
// 获取点赞状态
isLiked(targetId) {
return this.getLikeStatus(targetId, 'article');
},
// 获取点赞计数
likeCount(targetId) {
return this.getLikeCount(targetId, 'article');
},
// 处理点赞状态变化
async handleLikeChange({ targetId, targetType, isLiked }) {
if (isLiked) {
// 点赞
await this.like({ targetId, targetType });
} else {
// 取消点赞
await this.unlike({ targetId, targetType });
}
}
},
mounted() {
// 批量获取点赞状态
const targetIds = this.contentList.map(item => item.id);
this.$store.dispatch('like/getBatchLikeStatus', {
targetIds,
targetType: 'article'
});
}
};
</script>
<style scoped>
.content-list {
padding: 20rpx;
}
.content-item {
background-color: white;
border-radius: 10rpx;
padding: 20rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
}
.content-title {
font-size: 28rpx;
font-weight: bold;
margin-bottom: 15rpx;
display: block;
color: #333;
}
.content-summary {
font-size: 24rpx;
line-height: 1.5;
margin-bottom: 20rpx;
display: block;
color: #666;
}
.content-actions {
display: flex;
align-items: center;
border-top: 1rpx solid #F0F0F0;
padding-top: 15rpx;
}
.content-action {
display: flex;
align-items: center;
margin-left: 40rpx;
font-size: 22rpx;
color: #666;
}
.action-icon {
margin-right: 8rpx;
font-size: 24rpx;
}
/* 响应式设计 */
@media (max-width: 750rpx) {
.content-list {
padding: 15rpx;
}
.content-item {
padding: 15rpx;
margin-bottom: 15rpx;
}
.content-title {
font-size: 26rpx;
}
.content-summary {
font-size: 22rpx;
}
}
</style>5. 防作弊措施
为了防止恶意刷点赞,我们可以实现以下措施:
// utils/anti-cheat.js
// 点赞限制配置
const likeLimits = {
maxPerMinute: 10, // 每分钟最大点赞数
maxPerHour: 50, // 每小时最大点赞数
maxPerDay: 200 // 每天最大点赞数
};
// 用户点赞记录
export const userLikeRecord = {
// 记录用户点赞
recordLike: (userId) => {
const key = `like_record_${userId}`;
let record = uni.getStorageSync(key) || [];
// 添加新记录
record.push({
timestamp: Date.now()
});
// 清理过期记录(超过24小时)
const now = Date.now();
record = record.filter(item => now - item.timestamp < 24 * 60 * 60 * 1000);
// 保存记录
uni.setStorageSync(key, record);
return record;
},
// 检查用户点赞是否超过限制
checkLikeLimit: (userId) => {
const key = `like_record_${userId}`;
const record = uni.getStorageSync(key) || [];
const now = Date.now();
// 计算不同时间窗口内的点赞数
const minuteLikes = record.filter(item => now - item.timestamp < 60 * 1000).length;
const hourLikes = record.filter(item => now - item.timestamp < 60 * 60 * 1000).length;
const dayLikes = record.filter(item => now - item.timestamp < 24 * 60 * 60 * 1000).length;
// 检查限制
if (minuteLikes > likeLimits.maxPerMinute) {
return { allowed: false, message: '点赞过于频繁,请稍后再试' };
}
if (hourLikes > likeLimits.maxPerHour) {
return { allowed: false, message: '每小时点赞数已达上限' };
}
if (dayLikes > likeLimits.maxPerDay) {
return { allowed: false, message: '每天点赞数已达上限' };
}
return { allowed: true };
}
};
// 验证点赞操作
export function validateLikeOperation(userId) {
// 检查用户登录状态
const userInfo = uni.getStorageSync('userInfo');
if (!userInfo) {
return { valid: false, message: '请先登录' };
}
// 检查点赞限制
const limitCheck = userLikeRecord.checkLikeLimit(userId);
if (!limitCheck.allowed) {
return { valid: false, message: limitCheck.message };
}
return { valid: true };
}代码优化建议
1. 点赞缓存优化
使用本地缓存提高点赞状态的读取性能:
// utils/like-cache.js
// 点赞缓存
export const likeCache = {
// 缓存键前缀
prefix: 'like_',
// 缓存点赞状态
setLikeStatus: (targetType, targetId, isLiked) => {
const key = `${likeCache.prefix}${targetType}_${targetId}`;
uni.setStorageSync(key, isLiked);
},
// 获取点赞状态
getLikeStatus: (targetType, targetId) => {
const key = `${likeCache.prefix}${targetType}_${targetId}`;
return uni.getStorageSync(key) || false;
},
// 批量获取点赞状态
getBatchLikeStatus: (targetType, targetIds) => {
const statusMap = {};
targetIds.forEach(targetId => {
statusMap[targetId] = likeCache.getLikeStatus(targetType, targetId);
});
return statusMap;
},
// 清除点赞状态缓存
clearLikeStatus: (targetType, targetId) => {
const key = `${likeCache.prefix}${targetType}_${targetId}`;
uni.removeStorageSync(key);
},
// 清除所有点赞状态缓存
clearAllLikeStatus: () => {
// 获取所有缓存键
const keys = uni.getStorageInfoSync().keys;
// 过滤出点赞缓存键
const likeKeys = keys.filter(key => key.startsWith(likeCache.prefix));
// 清除缓存
likeKeys.forEach(key => {
uni.removeStorageSync(key);
});
}
};2. 点赞动画优化
使用 CSS 动画提高点赞动画的性能:
<template>
<view class="like-button" @click="toggleLike">
<view
class="like-icon"
:class="{ 'liked': isLiked, 'animating': isAnimating }"
>
<text class="like-emoji">{{ isLiked ? '❤️' : '🤍' }}</text>
</view>
<view class="like-count" :class="{ 'liked': isLiked }">
{{ likeCount }}
</view>
</view>
</template>
<script>
export default {
// 组件逻辑...
methods: {
// 切换点赞状态
toggleLike() {
if (this.isAnimating) return;
// 触发点赞动画
this.isAnimating = true;
setTimeout(() => {
this.isAnimating = false;
}, 300);
// 其他逻辑...
}
}
};
</script>
<style scoped>
.like-button {
display: flex;
align-items: center;
cursor: pointer;
user-select: none;
}
.like-icon {
margin-right: 8rpx;
transition: all 0.3s ease;
}
.like-emoji {
font-size: 32rpx;
transition: all 0.3s ease;
}
.like-icon.liked .like-emoji {
color: #FF3B30;
filter: drop-shadow(0 0 4rpx rgba(255, 59, 48, 0.5));
}
.like-icon.animating {
animation: likeAnimation 0.3s ease;
}
.like-count {
font-size: 24rpx;
color: #666;
transition: all 0.3s ease;
}
.like-count.liked {
color: #FF3B30;
}
/* 点赞动画 */
@keyframes likeAnimation {
0% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
100% {
transform: scale(1);
}
}
/* 禁用状态 */
.like-button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
</style>3. 批量点赞处理
优化批量点赞状态的获取和更新:
// store/modules/like.js
// 批量获取点赞状态
export const getBatchLikeStatus = async (targetIds, targetType = 'article') => {
try {
// 先从缓存获取
const cachedStatus = likeCache.getBatchLikeStatus(targetType, targetIds);
// 检查缓存是否完整
const cachedIds = Object.keys(cachedStatus);
const uncachedIds = targetIds.filter(id => !cachedIds.includes(id));
if (uncachedIds.length === 0) {
// 所有状态都在缓存中
return cachedStatus;
}
// 从服务器获取未缓存的状态
const res = await likeApi.getBatchLikeStatus({ targetIds: uncachedIds, targetType });
// 更新缓存
Object.keys(res.data.statusMap).forEach(targetId => {
likeCache.setLikeStatus(targetType, targetId, res.data.statusMap[targetId]);
});
// 合并缓存和服务器数据
return {
...cachedStatus,
...res.data.statusMap
};
} catch (error) {
console.error('批量获取点赞状态失败:', error);
// 失败时返回缓存数据
return likeCache.getBatchLikeStatus(targetType, targetIds);
}
};4. 点赞防抖
添加点赞操作的防抖,防止重复点击:
// utils/debounce.js
// 防抖函数
export function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 应用到点赞操作
const debouncedLike = debounce(async (targetId, targetType) => {
// 点赞逻辑
}, 1000);总结
本教程详细介绍了 uni-app 点赞系统的实现方法,包括:
- 点赞系统架构:前端组件和后端服务的设计
- 点赞数据结构:合理的数据结构设计
- 点赞功能实现:点赞和取消点赞的功能
- 点赞状态管理:使用 Vuex 管理点赞状态
- 点赞计数更新:实时更新点赞计数
- 防作弊措施:防止恶意刷点赞
通过本教程的学习,您应该能够:
- 理解点赞系统的核心概念和实现原理
- 掌握点赞系统的前端和后端实现方法
- 开发功能完整的点赞系统
- 优化点赞系统的性能和安全性
点赞系统是增强用户互动的重要工具,一个好的点赞系统可以显著提升应用的用户体验和社区氛围。在实现点赞系统时,需要平衡功能丰富性、性能和安全性,为用户提供一个公平、流畅的互动体验。