深夜提醒

现在是深夜,建议您注意休息,不要熬夜哦~

🏮 🏮 🏮

新年快乐

祝君万事如意心想事成!

share-image
中
ESC

Building a Blog Comment System: A Go+Gin Hands-On Guide to Multi-Layer Security and Smart Moderation

English 阅读中文版

This article documents in detail the complete construction of a blog comment system with multi-layer security, smart content moderation, and multi-layer anti-spam rate limiting, covering the implementation of core technologies including setting up the Gin framework, JWT authentication, multiple rate limits by IP/email/content, human verification, and AI content moderation.

Table of Contents

  1. Project Overview and Architecture Design
  2. Tech Stack Selection and Project Structure
  3. The Multi-Layer Security System
  4. Database Design and Model Relationships
  5. Core Comment Feature Implementation
  6. Smart Content Moderation System
  7. Rate Limiting and Anti-Spam Mechanism
  8. Deployment and Operations Practice
  9. Summary and Outlook

1. Project Overview and Architecture Design

1.1 Project Background

As the blog’s content kept growing, I needed a safe, reliable, and feature-complete comment system to interact with readers. Traditional third-party comment systems (like Disqus and Gitment) have flaws such as uncontrollable data, slow loading, and privacy issues. So I decided to build my own comment system, which needed to meet the following core requirements:

  • Comment features: post comments, reply to comments, paginated display
  • Multi-layer security: Referer validation, human verification, IP rate limiting, email rate limiting, content deduplication
  • Content moderation: AI automatic moderation + manual moderation as a dual safeguard
  • Admin backend: admin login, comment moderation, deletion, replies
  • Data security: JWT authentication, soft delete, email masking

1.2 Overall Architecture

┌─────────────────────────────────────────────────────────────────┐
│ Client Layer │
│ Blog frontend (Hexo) │ Admin backend │
└──────────────────────────────┼──────────────────────────────────┘
│ HTTPS
┌──────────────────────────────▼──────────────────────────────────┐
│ Nginx reverse proxy │
│ SSL termination │ static assets │ rate limit(1r/m) │ logging │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────┐
│ Go Gin Server (8083) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐ │
│ │ Comment │ │ Message │ │ Auth │ │ Admin │ │
│ │ (comment) │ │ (messages) │ │ (JWT auth) │ │ (admin) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └─────┬─────┘ │
│ │ │ │ │ │
│ ┌──────▼───────────────▼───────────────▼──────────────▼─────┐ │
│ │ Middleware Layer │ │
│ │ Referer │ Turnstile │ RateLimit │ JWTAuth │ CORS │ Audit │ │
│ └────────────────────────────────────────────────────────────┘ │
└──────────────────────────────┬──────────────────────────────────┘
│
▼
┌─────────────────────┐
│ MySQL 8 │
│ (comments/messages/ │
│ users) │
└─────────────────────┘

1.3 Core Features

Feature Technical implementation Description
Origin validation Referer middleware Only allow comments from designated domains
Human verification Cloudflare Turnstile Invisible CAPTCHA replacing traditional CAPTCHA
Multi-layer rate limiting Token Bucket Triple rate limiting by IP + email + content fingerprint
Content moderation Alibaba Cloud Bailian AI-powered detection of violating content
Authentication/authorization JWT Stateless authentication, 7-day validity
Data masking Runtime processing Mask sensitive info like email and IP when displaying

2. Tech Stack Selection and Project Structure

2.1 Core Tech Stack

// go.mod
module blog-api

go 1.21

require (
github.com/gin-gonic/gin v1.9.1 // Web framework
github.com/golang-jwt/jwt/v5 v5.2.0 // JWT authentication
github.com/google/uuid v1.6.0 // UUID generation
golang.org/x/time v0.5.0 // rate limiter
gorm.io/driver/mysql v1.5.4 // MySQL driver
gorm.io/gorm v1.25.7 // ORM framework
)

2.2 Project Structure

blog_api/
├── cmd/
│ └── main.go # Program entry
├── internal/
│ ├── config/ # Configuration management
│ │ └── config.go # Environment variable config
│ ├── handlers/ # HTTP handlers
│ │ ├── auth.go # Auth handlers
│ │ ├── comment.go # Comment handlers
│ │ └── message.go # Message handlers
│ ├── middleware/ # Middleware
│ │ ├── auth.go # JWT auth
│ │ ├── cors.go # CORS handling
│ │ ├── error.go # Error handling
│ │ └── referer.go # Referer check
│ ├── models/ # Data models
│ │ ├── comment.go # Comment model
│ │ └── message.go # Message model
│ ├── routes/ # Route configuration
│ │ └── routes.go # Route registration
│ └── services/ # Business services
│ ├── audit.go # Alibaba Cloud moderation service
│ └── turnstile.go # Turnstile verification service
├── pkg/ # Common packages
│ ├── jwt/ # JWT utilities
│ │ └── jwt.go
│ └── ratelimit/ # Rate-limiting utilities
│ ├── ratelimit.go # General IP rate limiting
│ └── comment_limiter.go # Comment-specific rate limiting
├── scripts/
│ └── init_db.sql # Database initialization
├── blog-api.service # systemd service file
├── nginx.conf # Nginx configuration
├── config.yaml # Configuration file
└── go.mod

3. The Multi-Layer Security System

3.1 Referer Origin Check

Prevents malicious websites from submitting comments cross-site; only the designated domain is allowed:

package middleware

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

// RefererCheck is a Referer-check middleware
func RefererCheck(allowedReferer string) gin.HandlerFunc {
return func(c *gin.Context) {
referer := c.GetHeader("Referer")
origin := c.GetHeader("Origin")

// Check the Referer
if referer != "" && !strings.Contains(referer, allowedReferer) {
c.JSON(http.StatusForbidden, gin.H{"error": "Illegal origin"})
c.Abort()
return
}

// Check the Origin
if origin != "" && !strings.Contains(origin, allowedReferer) {
c.JSON(http.StatusForbidden, gin.H{"error": "Illegal origin"})
c.Abort()
return
}

c.Next()
}
}

3.2 Cloudflare Turnstile Human Verification

Replaces traditional CAPTCHA, offering a better user experience:

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 is the Turnstile verification response
type TurnstileVerifyResponse struct {
Success bool `json:"success"`
ChallengeTS string `json:"challenge_ts"`
Hostname string `json:"hostname"`
ErrorCodes []string `json:"error-codes"`
}

// NewTurnstileService creates a Turnstile verification service
func NewTurnstileService(secretKey string) *TurnstileService {
return &TurnstileService{
secretKey: secretKey,
client: &http.Client{Timeout: 10 * time.Second},
}
}

// VerifyToken verifies a 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 Authentication Middleware

Admin operations require JWT authentication:

package middleware

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

// Auth is a JWT authentication middleware
func Auth() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Missing authentication information"})
c.Abort()
return
}

parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid authentication format"})
c.Abort()
return
}

claims, err := jwt.ParseToken(parts[1])
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
c.Abort()
return
}

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

4. Database Design and Model Relationships

4.1 Data Model Design

package models

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

// Comment is the comment model
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:'post ID/path'"`
ParentID *string `json:"parent_id,omitempty" gorm:"type:char(36);index;comment:'parent comment ID, for replies'"`
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-pending 1-approved 2-rejected'"`
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"`

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

// BeforeCreate generates a UUID before creation
func (c *Comment) BeforeCreate(tx *gorm.DB) error {
if c.ID == "" {
c.ID = uuid.New().String()
}
return nil
}

// TableName specifies the table name
func (Comment) TableName() string {
return "comments"
}

4.2 Data Masking

// ToResponse converts to the response format (masked)
func (c *Comment) ToResponse() CommentResponse {
return CommentResponse{
ID: c.ID,
PostID: c.PostID,
ParentID: c.ParentID,
Nickname: c.Nickname,
Email: maskEmail(c.Email), // mask the email
Website: c.Website,
Content: c.Content,
Status: c.Status,
IsAdmin: c.IsAdmin,
CreatedAt: c.CreatedAt,
Replies: convertReplies(c.Replies),
}
}

// maskEmail hides part of the email
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)
}
// Output: ab********@example.com

5. Core Comment Feature Implementation

5.1 Creating a Comment (with the full validation flow)

package handlers

// CreateComment creates a comment (asynchronous auto-moderation)
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": "Invalid request parameters: " + err.Error()})
return
}

// 1. Verify Turnstile human verification
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. Get client information
clientIP := c.ClientIP()
userAgent := c.GetHeader("User-Agent")

// 3. Create the comment (status pending moderation)
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, // pending moderation
IsAdmin: false,
}

if err := h.db.Create(comment).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create comment"})
return
}

// 4. Perform AI moderation asynchronously (without blocking the user)
if h.auditService != nil {
auditText := req.Nickname + ": " + req.Content
go h.asyncAuditComment(comment.ID, auditText)
}

c.JSON(http.StatusCreated, gin.H{
"message": "Comment submitted successfully; it will display automatically once approved",
"data": comment.ToResponse(),
})
}

5.2 Getting the Comment List (nested replies)

// GetComments gets the comment list for a post (public endpoint)
func (h *CommentHandler) GetComments(c *gin.Context) {
postID := c.Query("post_id")
if postID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "Missing post ID parameter"})
return
}

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

// Query approved root comments
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": "Failed to fetch comments"})
return
}

// Convert to the response format
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. Smart Content Moderation System

6.1 Alibaba Cloud Bailian AI Moderation

Using the Alibaba Cloud Bailian app for smart content moderation:

package services

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

// AliyunAuditService is the Alibaba Cloud Bailian moderation service
type AliyunAuditService struct {
apiKey string
appID string
client *http.Client
}

// NewAliyunAuditService creates the moderation service
func NewAliyunAuditService(apiKey, appID string) *AliyunAuditService {
return &AliyunAuditService{
apiKey: apiKey,
appID: appID,
client: &http.Client{Timeout: 30 * time.Second},
}
}

// AuditComment moderates comment content
// Returns true for pass, false for reject
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
}

// Parse the moderation result
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 Asynchronous Moderation Flow

// asyncAuditComment moderates a comment asynchronously
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 // approved
log.Printf("Comment %s audit passed", commentID)
} else {
status = 2 // rejected
log.Printf("Comment %s audit rejected", commentID)
}

// Update the comment status
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. Rate Limiting and Anti-Spam Mechanism

7.1 Comment-Specific Rate Limiter (IP + email + content)

package ratelimit

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

// CommentRateLimiter is a comment-specific rate limiter
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 // content fingerprint dedup
contentMu sync.RWMutex
contentTTL time.Duration
}

// NewCommentRateLimiter creates a comment-specific rate limiter
func NewCommentRateLimiter(ipPerMinute, emailPerMinute int) *CommentRateLimiter {
c := &CommentRateLimiter{
ipLimiters: make(map[string]*Visitor),
ipRate: rate.Every(time.Minute / time.Duration(ipPerMinute)),
ipBurst: 1, // strict limit
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 returns the Gin middleware
func (c *CommentRateLimiter) LimitMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
if ctx.Request.Method != http.MethodPost {
ctx.Next()
return
}

// 1. Check the IP rate limit
ip := ctx.ClientIP()
ipLimiter := c.GetIPLimiter(ip)
if !ipLimiter.Allow() {
ctx.JSON(http.StatusTooManyRequests, gin.H{
"error": "Submitting too frequently, please try again later (IP limit)",
})
ctx.Abort()
return
}

// 2. Check the email rate limit
email := c.extractEmail(ctx)
if email != "" {
emailLimiter := c.GetEmailLimiter(email)
if !emailLimiter.Allow() {
ctx.JSON(http.StatusTooManyRequests, gin.H{
"error": "This email is submitting too frequently, please try again later",
})
ctx.Abort()
return
}
}

ctx.Next()
}
}

7.2 Content Duplicate Detection

// CheckDuplicateContent checks whether the content is a duplicate
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 // duplicate content
}
}

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

// generateFingerprint generates a content fingerprint
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. Deployment and Operations Practice

8.1 Nginx Multi-Layer Rate-Limit Configuration

# Comment endpoint rate limit: 1 request per minute per IP
limit_req_zone $binary_remote_addr zone=comment:10m rate=1r/m;

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

# Comment endpoint - rate limit at the Nginx layer
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;
}

# Other API requests
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 Service Configuration

[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

# Server configuration
Environment="SERVER_PORT=8083"
Environment="SERVER_MODE=production"
Environment="ALLOWED_REFERER=www.awen.me"

# Database configuration
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 configuration
Environment="JWT_SECRET=your_jwt_secret_key"
Environment="JWT_EXPIRES_IN=168h"

# Rate-limit configuration
Environment="COMMENT_RATE_LIMIT_IP=2"
Environment="COMMENT_RATE_LIMIT_EMAIL=2"

# AI moderation configuration
Environment="ALIYUN_API_KEY=your_api_key"
Environment="ALIYUN_APP_ID=your_app_id"

# Turnstile configuration
Environment="TURNSTILE_SECRET_KEY=your_secret_key"

Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

8.3 Deployment Script

#!/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. Summary and Outlook

9.1 Project Highlights

  1. Multi-layer security system

    • Referer origin check preventing cross-site submission
    • Cloudflare Turnstile invisible human verification
    • Triple rate limiting by IP + email + content fingerprint
    • JWT stateless authentication
  2. Smart content moderation

    • Alibaba Cloud Bailian AI automatic moderation
    • Asynchronous processing that doesn’t block users
    • Manual moderation as a fallback
  3. Complete comment features

    • Nested reply support
    • Paginated display
    • Data masking protecting privacy
  4. Multi-layer rate-limit protection

    • Nginx-layer rate limiting (1r/m)
    • Application-layer IP rate limiting
    • Application-layer email rate limiting
    • Content fingerprint deduplication

9.2 Performance Data

Metric Value Description
API response time < 50ms Local test
AI moderation time 500ms - 2s Asynchronous processing
Database query < 10ms Indexed
Concurrency 1000+ QPS Load-test data

9.3 Future Optimization Directions

  • Email notifications: email the user when a comment is replied to
  • Sensitive-word filtering: a local sensitive-word dictionary + AI moderation as a dual safeguard
  • Anti-spam enhancement: add device fingerprinting and behavior analysis
  • Admin backend: a web-visualized backend for managing comments
  • Data export: support exporting and backing up comment data

References

文章作者:阿文
文章链接: https://www.awen.me/post/39bb5421.html
版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 阿文的博客!
本文于 2026-02-27 11:30 发布,已超过半年(231天),请注意甄别内容是否已过期。

评论

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

留言反馈

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