2024 桐庐半程马拉松
00:00:00
时间
0.00
距离(公里)
--:--
配速
--
步频
--
心率 (bpm)
--
配速
步频
|
share-image
ESC

构建博客评论系统:多层安全防护与智能审核的Go+Gin实战

本文详细记录了一个具备多层安全防护、智能内容审核、多层限流防刷的博客评论系统的完整构建过程,涵盖Gin框架搭建、JWT认证、IP/邮箱/内容多重限流、人机验证、AI内容审核等核心技术的实现。

目录

  1. 项目概述与架构设计
  2. 技术栈选型与项目结构
  3. 多层安全防护体系
  4. 数据库设计与模型关系
  5. 评论核心功能实现
  6. 智能内容审核系统
  7. 限流与防刷机制
  8. 部署与运维实践
  9. 总结与展望

1. 项目概述与架构设计

1.1 项目背景

随着博客内容的不断丰富,需要一个安全可靠、功能完善的评论系统来与读者互动。传统的第三方评论系统(如Disqus、Gitment)存在数据不可控、加载慢、隐私问题等缺陷。因此决定自研一个评论系统,需要满足以下核心需求:

  • 评论功能:发布评论、回复评论、分页展示
  • 多层安全防护:Referer校验、人机验证、IP限流、邮箱限流、内容去重
  • 内容审核:AI自动审核 + 人工审核双重保障
  • 管理后台:管理员登录、评论审核、删除、回复
  • 数据安全:JWT认证、软删除、邮箱脱敏

1.2 整体架构

┌─────────────────────────────────────────────────────────────────┐
│ 客户端层 │
│ 博客前端 (Hexo) │ 管理后台 │
└──────────────────────────────┼──────────────────────────────────┘
│ HTTPS
┌──────────────────────────────▼──────────────────────────────────┐
│ Nginx 反向代理 │
│ SSL终止 │ 静态资源 │ 限流(1r/m) │ 日志记录 │
└──────────────────────────────┬──────────────────────────────────┘

┌──────────────────────────────▼──────────────────────────────────┐
│ Go Gin Server (8083) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Comment │ │ Message │ │ Auth │ │ Admin │ │
│ │ (评论管理) │ │ (留言管理) │ │ (JWT认证) │ │ (后台管理) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ┌──────▼───────────────▼───────────────▼──────────────▼─────┐ │
│ │ Middleware Layer (中间件层) │ │
│ │ Referer │ Turnstile │ RateLimit │ JWTAuth │ CORS │ Audit │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘


┌─────────────────────┐
│ MySQL 8
│ (评论/留言/用户) │
└─────────────────────┘

1.3 核心特性

特性 技术实现 说明
来源校验 Referer中间件 只允许指定域名提交评论
人机验证 Cloudflare Turnstile 无感知验证码替代传统CAPTCHA
多层限流 Token Bucket IP + 邮箱 + 内容指纹三重限流
内容审核 阿里云百炼 AI智能识别违规内容
认证授权 JWT 无状态认证,7天有效期
数据脱敏 运行时处理 邮箱、IP等敏感信息脱敏展示

2. 技术栈选型与项目结构

2.1 核心技术栈

// go.mod
module blog-api

go 1.21

require (
github.com/gin-gonic/gin v1.9.1 // Web框架
github.com/golang-jwt/jwt/v5 v5.2.0 // JWT认证
github.com/google/uuid v1.6.0 // UUID生成
golang.org/x/time v0.5.0 // 限流器
gorm.io/driver/mysql v1.5.4 // MySQL驱动
gorm.io/gorm v1.25.7 // ORM框架
)

2.2 项目结构

blog_api/
├── cmd/
│ └── main.go # 程序入口
├── internal/
│ ├── config/ # 配置管理
│ │ └── config.go # 环境变量配置
│ ├── handlers/ # HTTP处理器
│ │ ├── auth.go # 认证处理器
│ │ ├── comment.go # 评论处理器
│ │ └── message.go # 留言处理器
│ ├── middleware/ # 中间件
│ │ ├── auth.go # JWT鉴权
│ │ ├── cors.go # 跨域处理
│ │ ├── error.go # 错误处理
│ │ └── referer.go # Referer检查
│ ├── models/ # 数据模型
│ │ ├── comment.go # 评论模型
│ │ └── message.go # 留言模型
│ ├── routes/ # 路由配置
│ │ └── routes.go # 路由注册
│ └── services/ # 业务服务
│ ├── audit.go # 阿里云审核服务
│ └── turnstile.go # Turnstile验证服务
├── pkg/ # 公共包
│ ├── jwt/ # JWT工具
│ │ └── jwt.go
│ └── ratelimit/ # 限流工具
│ ├── ratelimit.go # 通用IP限流
│ └── comment_limiter.go # 评论专用限流
├── scripts/
│ └── init_db.sql # 数据库初始化
├── blog-api.service # systemd服务文件
├── nginx.conf # Nginx配置
├── config.yaml # 配置文件
└── go.mod

3. 多层安全防护体系

3.1 Referer来源检查

防止恶意网站跨站提交评论,只允许指定域名访问:

package middleware

import (
"net/http"
"strings"
"github.com/gin-gonic/gin"
)

// RefererCheck Referer检查中间件
func RefererCheck(allowedReferer string) gin.HandlerFunc {
return func(c *gin.Context) {
referer := c.GetHeader("Referer")
origin := c.GetHeader("Origin")

// 检查Referer
if referer != "" && !strings.Contains(referer, allowedReferer) {
c.JSON(http.StatusForbidden, gin.H{"error": "非法来源"})
c.Abort()
return
}

// 检查Origin
if origin != "" && !strings.Contains(origin, allowedReferer) {
c.JSON(http.StatusForbidden, gin.H{"error": "非法来源"})
c.Abort()
return
}

c.Next()
}
}

3.2 Cloudflare Turnstile人机验证

替代传统CAPTCHA,提供更好的用户体验:

package services

import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)

const turnstileVerifyURL = "https://challenges.cloudflare.com/turnstile/v0/siteverify"

type TurnstileService struct {
secretKey string
client *http.Client
}

// TurnstileVerifyResponse Turnstile验证响应
type TurnstileVerifyResponse struct {
Success bool `json:"success"`
ChallengeTS string `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes []string `json:"error-codes"`
}

// NewTurnstileService 创建Turnstile验证服务
func NewTurnstileService(secretKey string) *TurnstileService {
return &TurnstileService{
secretKey: secretKey,
client: &http.Client{Timeout: 10 * time.Second},
}
}

// VerifyToken 验证Turnstile token
func (s *TurnstileService) VerifyToken(token string, remoteIP string) (*TurnstileVerifyResponse, error) {
data := url.Values{}
data.Set("secret", s.secretKey)
data.Set("response", token)
if remoteIP != "" {
data.Set("remoteip", remoteIP)
}

resp, err := s.client.PostForm(turnstileVerifyURL, data)
if err != nil {
return nil, fmt.Errorf("failed to verify turnstile: %w", err)
}
defer resp.Body.Close()

var result TurnstileVerifyResponse
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, err
}
return &result, nil
}

3.3 JWT认证中间件

管理员操作需要JWT认证:

package middleware

import (
"net/http"
"strings"
"blog-api/pkg/jwt"
"github.com/gin-gonic/gin"
)

// Auth JWT鉴权中间件
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "缺少认证信息"})
c.Abort()
return
}

parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "认证格式无效"})
c.Abort()
return
}

claims, err := jwt.ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "token无效或已过期"})
c.Abort()
return
}

c.Set("userID", claims.UserID)
c.Set("username", claims.Username)
c.Next()
}
}

4. 数据库设计与模型关系

4.1 数据模型设计

package models

import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)

// Comment 评论模型
type Comment struct {
ID string `json:"id" gorm:"type:char(36);primary_key"`
PostID string `json:"post_id" gorm:"type:varchar(200);not null;index;comment:'文章ID/路径'"`
ParentID *string `json:"parent_id,omitempty" gorm:"type:char(36);index;comment:'父评论ID,用于回复'"`
Nickname string `json:"nickname" gorm:"type:varchar(50);not null"`
Email string `json:"email" gorm:"type:varchar(100);not null"`
Website string `json:"website" gorm:"type:varchar(200)"`
Content string `json:"content" gorm:"type:text;not null"`
IP string `json:"ip" gorm:"type:varchar(50);not null"`
UserAgent string `json:"user_agent" gorm:"type:varchar(500)"`
Status int `json:"status" gorm:"type:tinyint;default:0;comment:'0-待审核 1-已通过 2-已拒绝'"`
IsAdmin bool `json:"is_admin" gorm:"default:false"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`

// 关联
Replies []Comment `json:"replies,omitempty" gorm:"foreignKey:ParentID"`
}

// BeforeCreate 在创建前生成UUID
func (c *Comment) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}

// TableName 指定表名
func (Comment) TableName() string {
return "comments"
}

4.2 数据脱敏处理

// ToResponse 转换为响应格式(脱敏)
func (c *Comment) ToResponse() CommentResponse {
return CommentResponse{
ID: c.ID,
PostID: c.PostID,
ParentID: c.ParentID,
Nickname: c.Nickname,
Email: maskEmail(c.Email), // 脱敏邮箱
Website: c.Website,
Content: c.Content,
Status: c.Status,
IsAdmin: c.IsAdmin,
CreatedAt: c.CreatedAt,
Replies: convertReplies(c.Replies),
}
}

// maskEmail 隐藏邮箱部分内容
func maskEmail(email string) string {
if len(email) <= 3 {
return email
}
parts := make([]rune, 0, len(email))
for i, ch := range email {
if i < 2 || ch == '@' {
parts = append(parts, ch)
} else {
parts = append(parts, '*')
}
}
return string(parts)
}
// 输出: ab********@example.com

5. 评论核心功能实现

5.1 创建评论(带完整验证流程)

package handlers

// CreateComment 创建评论(异步自动审核)
func (h *CommentHandler) CreateComment(c *gin.Context) {
var req models.CreateCommentRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数错误: " + err.Error()})
return
}

// 1. 验证 Turnstile 人机验证
if h.turnstileService != nil {
clientIP := c.ClientIP()
result, err := h.turnstileService.VerifyToken(req.TurnstileToken, clientIP)
if err != nil || !result.Success {
c.JSON(http.StatusBadRequest, gin.H{
"error": h.turnstileService.GetErrorMessage(result.ErrorCodes),
})
return
}
}

// 2. 获取客户端信息
clientIP := c.ClientIP()
userAgent := c.GetHeader("User-Agent")

// 3. 创建评论(状态为待审核)
comment := &models.Comment{
PostID: req.PostID,
ParentID: req.ParentID,
Nickname: req.Nickname,
Email: req.Email,
Website: req.Website,
Content: req.Content,
IP: clientIP,
UserAgent: userAgent,
Status: 0, // 待审核
IsAdmin: false,
}

if err := h.db.Create(comment).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "创建评论失败"})
return
}

// 4. 异步进行AI审核(不阻塞用户)
if h.auditService != nil {
auditText := req.Nickname + ": " + req.Content
go h.asyncAuditComment(comment.ID, auditText)
}

c.JSON(http.StatusCreated, gin.H{
"message": "评论提交成功,审核通过后将自动显示",
"data": comment.ToResponse(),
})
}

5.2 获取评论列表(嵌套回复)

// GetComments 获取文章评论列表(公开接口)
func (h *CommentHandler) GetComments(c *gin.Context) {
postID := c.Query("post_id")
if postID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少文章ID参数"})
return
}

page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))

// 查询已通过审核的根评论
var comments []models.Comment
err := h.db.Where("post_id = ? AND status = ? AND parent_id IS NULL", postID, 1).
Order("created_at DESC").
Offset((page - 1) * pageSize).
Limit(pageSize).
Preload("Replies", func(db *gorm.DB) *gorm.DB {
return db.Where("status = ?", 1).Order("created_at ASC")
}).
Find(&comments).Error

if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "获取评论失败"})
return
}

// 转换为响应格式
var responses []models.CommentResponse
for _, comment := range comments {
responses = append(responses, comment.ToResponse())
}

c.JSON(http.StatusOK, gin.H{
"data": responses,
"pagination": gin.H{
"page": page,
"page_size": pageSize,
},
})
}

6. 智能内容审核系统

6.1 阿里云百炼AI审核

使用阿里云百炼应用进行智能内容审核:

package services

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)

// AliyunAuditService 阿里云百炼审核服务
type AliyunAuditService struct {
apiKey string
appID string
client *http.Client
}

// NewAliyunAuditService 创建审核服务
func NewAliyunAuditService(apiKey, appID string) *AliyunAuditService {
return &AliyunAuditService{
apiKey: apiKey,
appID: appID,
client: &http.Client{Timeout: 30 * time.Second},
}
}

// AuditComment 审核评论内容
// 返回 true 表示通过,false 表示拒绝
func (s *AliyunAuditService) AuditComment(content string) (bool, error) {
url := fmt.Sprintf("https://dashscope.aliyuncs.com/api/v1/apps/%s/completion", s.appID)

requestBody := map[string]interface{}{
"input": map[string]interface{}{
"prompt": content,
"biz_params": map[string]interface{}{
"user_defined_params": map[string]interface{}{
"content": content,
},
},
},
"parameters": map[string]interface{}{},
"debug": map[string]interface{}{},
}

jsonData, _ := json.Marshal(requestBody)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
req.Header.Set("Authorization", "Bearer "+s.apiKey)
req.Header.Set("Content-Type", "application/json")

resp, err := s.client.Do(req)
if err != nil {
return false, err
}
defer resp.Body.Close()

body, _ := io.ReadAll(resp.Body)

var apiResp struct {
Output struct {
Text string `json:"text"`
} `json:"output"`
}

if err := json.Unmarshal(body, &apiResp); err != nil {
return false, err
}

// 解析审核结果
var auditResult struct {
Status bool `json:"status"`
}
if err := json.Unmarshal([]byte(apiResp.Output.Text), &auditResult); err != nil {
return false, err
}

return auditResult.Status, nil
}

6.2 异步审核流程

// asyncAuditComment 异步审核评论
func (h *CommentHandler) asyncAuditComment(commentID string, auditText string) {
passed, err := h.auditService.AuditComment(auditText)
if err != nil {
log.Printf("Audit failed for comment %s: %v", commentID, err)
return
}

var status int
if passed {
status = 1 // 审核通过
log.Printf("Comment %s audit passed", commentID)
} else {
status = 2 // 审核拒绝
log.Printf("Comment %s audit rejected", commentID)
}

// 更新评论状态
if err := h.db.Model(&models.Comment{}).Where("id = ?", commentID).Update("status", status).Error; err != nil {
log.Printf("Failed to update comment %s status: %v", commentID, err)
}
}

7. 限流与防刷机制

7.1 评论专用限流器(IP + 邮箱 + 内容)

package ratelimit

import (
"crypto/sha256"
"encoding/hex"
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
"golang.org/x/time/rate"
)

// CommentRateLimiter 评论专用限流器
type CommentRateLimiter struct {
ipLimiters map[string]*Visitor
ipMu sync.RWMutex
ipRate rate.Limit
ipBurst int

emailLimiters map[string]*Visitor
emailMu sync.RWMutex
emailRate rate.Limit
emailBurst int

contentCache map[string]time.Time // 内容指纹去重
contentMu sync.RWMutex
contentTTL time.Duration
}

// NewCommentRateLimiter 创建评论专用限流器
func NewCommentRateLimiter(ipPerMinute, emailPerMinute int) *CommentRateLimiter {
c := &CommentRateLimiter{
ipLimiters: make(map[string]*Visitor),
ipRate: rate.Every(time.Minute / time.Duration(ipPerMinute)),
ipBurst: 1, // 严格限制
emailLimiters: make(map[string]*Visitor),
emailRate: rate.Every(time.Minute / time.Duration(emailPerMinute)),
emailBurst: 1,
contentCache: make(map[string]time.Time),
contentTTL: time.Minute * 10,
}
go c.cleanup()
return c
}

// LimitMiddleware 返回Gin中间件
func (c *CommentRateLimiter) LimitMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
if ctx.Request.Method != http.MethodPost {
ctx.Next()
return
}

// 1. 检查IP限流
ip := ctx.ClientIP()
ipLimiter := c.GetIPLimiter(ip)
if !ipLimiter.Allow() {
ctx.JSON(http.StatusTooManyRequests, gin.H{
"error": "提交过于频繁,请稍后再试(IP限制)",
})
ctx.Abort()
return
}

// 2. 检查邮箱限流
email := c.extractEmail(ctx)
if email != "" {
emailLimiter := c.GetEmailLimiter(email)
if !emailLimiter.Allow() {
ctx.JSON(http.StatusTooManyRequests, gin.H{
"error": "该邮箱提交过于频繁,请稍后再试",
})
ctx.Abort()
return
}
}

ctx.Next()
}
}

7.2 内容重复检测

// CheckDuplicateContent 检查内容是否重复
func (c *CommentRateLimiter) CheckDuplicateContent(content string) bool {
c.contentMu.Lock()
defer c.contentMu.Unlock()

fingerprint := c.generateFingerprint(content)

if lastTime, exists := c.contentCache[fingerprint]; exists {
if time.Since(lastTime) < c.contentTTL {
return true // 重复内容
}
}

c.contentCache[fingerprint] = time.Now()
return false
}

// generateFingerprint 生成内容指纹
func (c *CommentRateLimiter) generateFingerprint(content string) string {
cleaned := strings.ToLower(strings.TrimSpace(content))
if len(cleaned) > 100 {
cleaned = cleaned[:100]
}
hash := sha256.Sum256([]byte(cleaned))
return hex.EncodeToString(hash[:]) + fmt.Sprintf("_%d", len(content))
}

8. 部署与运维实践

8.1 Nginx多层限流配置

# 评论接口限流:每个IP每分钟1个请求
limit_req_zone $binary_remote_addr zone=comment:10m rate=1r/m;

server {
listen 443 ssl http2;
server_name blogapi.awen.me;

# 评论接口 - Nginx层限流
location /api/v1/comments {
limit_req zone=comment burst=5 nodelay;
limit_req_status 429;

proxy_pass http://127.0.0.1:8083;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# 其他API请求
location / {
proxy_pass http://127.0.0.1:8083;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}

8.2 systemd服务配置

[Unit]
Description=Blog API Server
After=network.target mysqld.service
Requires=mysqld.service

[Service]
Type=simple
User=root
WorkingDirectory=/opt/blog-api
ExecStart=/usr/bin/blog-api

# 服务器配置
Environment="SERVER_PORT=8083"
Environment="SERVER_MODE=production"
Environment="ALLOWED_REFERER=www.awen.me"

# 数据库配置
Environment="DB_HOST=localhost"
Environment="DB_PORT=3306"
Environment="DB_USER=blog_api_user"
Environment="DB_PASSWORD=your_password"
Environment="DB_NAME=blog_api_db"

# JWT配置
Environment="JWT_SECRET=your_jwt_secret_key"
Environment="JWT_EXPIRES_IN=168h"

# 限流配置
Environment="COMMENT_RATE_LIMIT_IP=2"
Environment="COMMENT_RATE_LIMIT_EMAIL=2"

# AI审核配置
Environment="ALIYUN_API_KEY=your_api_key"
Environment="ALIYUN_APP_ID=your_app_id"

# Turnstile配置
Environment="TURNSTILE_SECRET_KEY=your_secret_key"

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

8.3 部署脚本

#!/bin/bash
# deploy-remote.sh

set -e

APP_NAME="blog-api"
SERVER_HOST="your-server"
SERVER_DIR="/opt/blog-api"

echo "=== Building... ==="
go build -o ${APP_NAME} cmd/main.go

echo "=== Deploying to ${SERVER_HOST}... ==="
scp ${APP_NAME} root@${SERVER_HOST}:${SERVER_DIR}/

echo "=== Restarting service... ==="
ssh root@${SERVER_HOST} "systemctl restart blog-api"

echo "=== Checking status... ==="
ssh root@${SERVER_HOST} "systemctl status blog-api --no-pager"

echo "=== Deploy complete! ==="

9. 总结与展望

9.1 项目亮点

  1. 多层安全防护体系

    • Referer来源检查防止跨站提交
    • Cloudflare Turnstile无感知人机验证
    • IP + 邮箱 + 内容指纹三重限流
    • JWT无状态认证
  2. 智能内容审核

    • 阿里云百炼AI自动审核
    • 异步处理不阻塞用户
    • 人工审核兜底
  3. 完善的评论功能

    • 嵌套回复支持
    • 分页展示
    • 数据脱敏保护隐私
  4. 多层限流防护

    • Nginx层限流(1r/m)
    • 应用层IP限流
    • 应用层邮箱限流
    • 内容指纹去重

9.2 性能数据

指标 数值 说明
接口响应时间 < 50ms 本地测试
AI审核耗时 500ms - 2s 异步处理
数据库查询 < 10ms 带索引
并发处理 1000+ QPS 压测数据

9.3 后续优化方向

  • 邮件通知:评论回复时邮件通知用户
  • 敏感词过滤:本地敏感词库 + AI审核双重保障
  • 反垃圾增强:增加设备指纹、行为分析
  • 管理后台:Web可视化后台管理评论
  • 数据导出:支持评论数据导出备份

9.4 源码地址

完整的项目代码已开源在 GitHub:

https://github.com/monkey-wenjun/blog_api

欢迎 Star 和 PR!💬


参考资料

文章作者:阿文
文章链接: https://www.awen.me/post/9f8e2b3c.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客

评论

0 条评论
😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘 🛑📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭 ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️ 🈯💹❇️ ✳️🌐 💠Ⓜ️🌀 💤🏧🚾 🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️
您的评论由 AI 智能审核,一般1分钟内会展示,若不展示请确认你的评论是否符合社区和法律规范
加载中...

留言反馈

😀😃😄 😁😅😂 🤣😊😇 🙂🙃😉 😌😍🥰 😘😗😙 😚😋😛 😝😜🤪 🤨🧐🤓 😎🥸🤩 🥳😏😒 😞😔😟 😕🙁☹️ 😣😖😫 😩🥺😢 😭😤😠 😡🤬🤯 😳🥵🥶 😱😨😰 😥😓🤗 🤔🤭🤫 🤥😶😐 😑😬🙄 😯😦😧 😮😲🥱 😴🤤😪 😵🤐🥴 🤢🤮🤧 😷🤒🤕 🤑🤠😈 👿👹👺 🤡💩👻 💀☠️👽 👾🤖🎃 😺😸😹 😻😼😽 🙀😿😾 👍👎👏 🙌👐🤲 🤝🤜🤛 ✌️🤞🤟 🤘👌🤏 👈👉👆 👇☝️ 🤚🖐️🖖 👋🤙💪 🦾🖕✍️ 🙏💅🤳 💯💢💥 💫💦💨 🕳️💣💬 👁️‍🗨️🗨️🗯️ 💭💤❤️ 🧡💛💚 💙💜🖤 🤍🤎💔 ❣️💕💞 💓💗💖 💘💝💟 ☮️✝️☪️ 🕉️☸️✡️ 🔯🕎☯️ ☦️🛐 🆔⚛️🉑 ☢️☣️📴 📳🈶🈚 🈸🈺🈷️ ✴️🆚💮 🉐㊙️㊗️ 🈴🈵🈹 🈲🅰️🅱️ 🆎🆑🅾️ 🆘 🛑📛 🚫💯💢 ♨️🚷🚯 🚳🚱🔞 📵🚭 ‼️⁉️🔅 🔆〽️⚠️ 🚸🔱⚜️ 🔰♻️ 🈯💹❇️ ✳️🌐 💠Ⓜ️🌀 💤🏧🚾 🅿️🈳 🈂🛂🛃 🛄🛅🛗 🚀🛸🚁 🚉🚆🚅 ✈️🛫🛬 🛩️💺🛰️