基于 Next.js 14 + Supabase 的全栈 SaaS 项目实战
本文最后更新于13 天前,其中的信息可能已经过时,如有错误请发送邮件到big_fw@foxmail.com

一、项目背景

开发一个能赚钱的网站:接入国内个人支付(无需营业执照)

为什么要做这个项目?

随着 AI 工具的普及,越来越多的人想要学习 AI 编程。但传统的编程学习曲线陡峭,让很多零基础学员望而却步。这个项目的目标是:

  • 🎯 降低编程门槛 – 借助 AI 编程工具,让零基础学员也能快速上手
  • 💻 实战驱动学习 – 通过实际项目练习,边做边学
  • 🌐 社区化学习 – 建立学习社区,互相交流进步
  • 💰 可持续运营 – 通过订阅制实现商业化,持续提供价值

二、技术栈

前端技术栈

{
  "framework": "Next.js 14.2.x",
  "language": "TypeScript 5.x",
  "styling": "Tailwind CSS 3.x",
  "animation": "AOS (Animate On Scroll)",
  "font": "Inter + Cabinet Grotesk"
}

为什么选择 Next.js 14?

特性优势本项目应用
App Router支持服务端组件,更好的性能所有页面默认服务端渲染
Layout 嵌套复用 UI 结构(auth)(default) 路由组
Streaming流式渲染,首屏更快Dashboard 数据加载
API Routes前后端一体化/api/checkout 支付接口
Vercel 部署零配置,自动优化一键部署,全球 CDN

后端技术栈

{
  "database": "Supabase (PostgreSQL)",
  "auth": "Supabase Auth",
  "orm": "Supabase JS Client",
  "payment": "ZPay 支付平台"
}

为什么选择 Supabase?

对比项SupabaseFirebase自建后端
开源✅ 完全开源❌ 闭源
数据库PostgreSQLNoSQL自选
实时订阅需自己实现
行级权限✅ RLS需自己实现
国内访问⚠️ 需代理❌ 被墙
价格💰 免费额度高💰 较贵💰 服务器成本

三、项目架构

整体构架图

目录结构详解

happyaicoding-template-starter/
│
├── .env.local                    # 本地环境变量(不提交到 Git)
├── next.config.mjs               # Next.js 配置
├── package.json                  # 依赖管理
├── tailwind.config.js            # Tailwind 配置
├── tsconfig.json                 # TypeScript 配置
│
├── app/                          # Next.js App Router
│   ├── layout.tsx                # 根布局(全局字体、SEO)
│   │
│   ├── (default)/                # 默认路由组(公开页面)
│   │   ├── layout.tsx            # 默认布局(Header + Footer)
│   │   ├── page.tsx              # 首页
│   │   └── ...                   # 其他公开页面
│   │
│   ├── (auth)/                   # 认证路由组(登录/注册)
│   │   ├── layout.tsx            # 认证页面布局
│   │   ├── signin/
│   │   │   └── page.tsx          # 登录页
│   │   ├── signup/
│   │   │   └── page.tsx          # 注册页
│   │   └── reset-password/
│   │       └── page.tsx          # 重置密码
│   │
│   ├── dashboard/                # 用户仪表盘(需登录)
│   │   ├── layout.tsx            # 仪表盘布局
│   │   └── page.tsx              # 仪表盘首页
│   │
│   ├── payment/                  # 支付相关页面
│   │   ├── layout.tsx
│   │   └── success/
│   │       └── page.tsx          # 支付成功页
│   │
│   ├── api/                      # API 路由
│   │   ├── products/
│   │   │   └── route.ts          # 产品列表接口
│   │   └── checkout/
│   │       └── providers/
│   │           └── zpay/
│   │               ├── url/
│   │               │   └── route.ts    # 获取支付链接
│   │               └── webhook/
│   │                   └── route.ts    # 支付回调处理
│   │
│   └── auth/
│       └── callback/
│           └── route.ts          # OAuth 回调处理
│
├── components/                   # React 组件
│   ├── ui/                       # UI 基础组件
│   │   ├── header.tsx            # 头部导航
│   │   └── footer.tsx            # 页脚
│   ├── hero.tsx                  # 首页 Hero 区域
│   ├── pricing.tsx               # 定价组件
│   ├── faqs.tsx                  # FAQ 组件
│   └── ...
│
├── utils/
│   └── supabase/                 # Supabase 工具函数
│       ├── server.ts             # 服务端客户端(带 Cookie)
│       ├── client.ts             # 客户端客户端
│       └── middleware.ts         # 中间件认证逻辑
│
├── public/                       # 静态资源
│   ├── fonts/                    # 自定义字体
│   └── images/                   # 图片资源
│
└── styles/
    └── css/
        └── style.css             # 全局样式

四、核心功能实现

认证流转图

4.1 用户登录/注册认证

1. 功能描述

实现用户邮箱密码登录、注册功能,通过 Supabase Auth 管理用户身份,登录状态持久化存储,支持跨页面状态同步。

2. 实现思路

用户输入凭证 → Supabase Auth 验证 → 返回 JWT Token
            → 写入 HttpOnly Cookie → 客户端监听 auth 状态变化
            → 更新 UI 显示登录状态 → 受保护路由自动放行

关键点:

  • 使用 createClient() 创建客户端 Supabase 实例
  • 通过 onAuthStateChange 监听登录状态变化
  • 登录成功后调用 router.refresh() 刷新服务端 session

3. 关键代码

// app/(auth)/signin/page.tsx
'use client'

import { useState } from 'react'
import { useRouter } from 'next/navigation'
import { createClient } from '@/utils/supabase/client'

export default function SignIn() {
  const [email, setEmail] = useState('')
  const [password, setPassword] = useState('')
  const [error, setError] = useState<string | null>(null)
  const [loading, setLoading] = useState(false)
  const router = useRouter()
  const supabase = createClient()

  const handleSignIn = async (e: React.FormEvent) => {
    e.preventDefault()
    setError(null)
    setLoading(true)

    try {
      const { data, error } = await supabase.auth.signInWithPassword({
        email,
        password
      })

      if (error) throw error

      // 登录成功,刷新页面获取新的 session
      router.refresh()
      router.push('/')
    } catch (error: any) {
      setError(error.message || '登录失败,请检查您的邮箱和密码')
    } finally {
      setLoading(false)
    }
  }

  return (
    <form onSubmit={handleSignIn}>
      {error && <div className="error">{error}</div>}
      <input type="email" value={email} onChange={(e) => setEmail(e.target.value)} placeholder="邮箱" />
      <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="密码" />
      <button type="submit" disabled={loading}>
        {loading ? '登录中...' : '登录账号'}
      </button>
    </form>
  )
}

4. 效果展示

  • 用户输入邮箱密码点击登录
  • 登录中按钮显示加载状态
  • 登录成功后跳转首页,Header 显示”个人中心”
  • 登录失败时红色错误提示框显示错误信息

4.2 路由权限保护(Middleware)

1. 功能描述

通过 Next.js Middleware 在请求到达页面前拦截,检查用户登录状态,未登录用户自动重定向到登录页,已登录用户访问登录页自动跳转首页。

2. 实现思路

请求进入 → Middleware 拦截 → 创建 Supabase 客户端 
        → 获取当前用户 Session → 判断路由类型
        ├─ 保护路由 (/dashboard) 且未登录 → 重定向 /signin
        ├─ 认证页面 (/signin) 且已登录 → 重定向 /
        └─ 其他情况 → 放行

3. 关键代码

// middleware.ts
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'

export async function middleware(request: NextRequest) {
  let response = NextResponse.next({ request: { headers: request.headers } })

  const supabase = createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) { return request.cookies.get(name)?.value },
        set(name: string, value: string, options: any) {
          request.cookies.set({ name, value, ...options })
          response = NextResponse.next({ request: { headers: request.headers } })
          response.cookies.set({ name, value, ...options })
        },
      },
    }
  )

  // 刷新 session
  await supabase.auth.getSession()
  const { data: { user } } = await supabase.auth.getUser()

  const protectedPaths = ['/dashboard', '/payment']
  const authPaths = ['/signin', '/signup', '/reset-password']

  // 已登录访问认证页面 → 跳转首页
  if (authPaths.some(p => request.nextUrl.pathname.startsWith(p)) && user) {
    return NextResponse.redirect(new URL('/', request.url))
  }

  // 未登录访问保护路由 → 跳转登录页
  if (protectedPaths.some(p => request.nextUrl.pathname.startsWith(p)) && !user) {
    return NextResponse.redirect(new URL('/signin', request.url))
  }

  return response
}

export const config = {
  matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)'],
}

4. 效果展示

  • 未登录访问 /dashboard → 自动跳转到 /signin
  • 登录后访问 /signin → 自动跳转到 /
  • 保护路由无需在每个页面重复验证逻辑

4.3 支付链接获取接口

1. 功能描述

用户点击购买后,调用此接口生成支付订单,返回 ZPay 支付链接。支持订阅产品时间累加计算。

2. 实现思路

接收请求 → 验证用户登录 → 查询产品信息 → 生成订单号
        → 计算订阅时间(如有活跃订阅则累加)→ 构建支付参数
        → 生成 MD5 签名 → 保存交易记录 → 返回支付 URL

3. 关键代码

// app/api/checkout/providers/zpay/url/route.ts
export async function POST(request: NextRequest) {
  const { productId, paymentMethod } = await request.json()

  // 1. 验证用户登录
  const supabase = createServerSupabaseClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) {
    return NextResponse.json({ msg: '请先登录' }, { status: 401 })
  }

  // 2. 获取产品信息
  const product = products[productId]
  if (!product) {
    return NextResponse.json({ msg: '产品不存在' }, { status: 404 })
  }

  // 3. 生成订单号
  const outTradeNo = generateOutTradeNo()

  // 4. 计算订阅时间(订阅产品自动累加)
  let subscriptionEndDate: Date | null = null
  if (product.isSubscription) {
    const { data: existing } = await supabase
      .from('zpay_transactions')
      .select('subscription_end_date')
      .eq('user_id', user.id)
      .eq('trade_status', 'TRADE_SUCCESS')
      .gt('subscription_end_date', new Date().toISOString())
      .order('subscription_end_date', { ascending: false })
      .limit(1)

    if (existing && existing.length > 0) {
      // 已有订阅,从结束日期累加
      const currentEndDate = new Date(existing[0].subscription_end_date)
      subscriptionEndDate = new Date(currentEndDate)
      subscriptionEndDate.setMonth(subscriptionEndDate.getMonth() + 1)
    } else {
      // 新订阅,从现在开始
      subscriptionEndDate = new Date()
      subscriptionEndDate.setMonth(subscriptionEndDate.getMonth() + 1)
    }
  }

  // 5. 生成签名
  const signParams = { money, name: product.name, out_trade_no: outTradeNo, ... }
  const sign = generateSign(signParams, process.env.ZPAY_KEY!)

  // 6. 保存交易记录
  await supabase.from('zpay_transactions').insert({
    out_trade_no: outTradeNo,
    user_id: user.id,
    product_id: productId,
    trade_status: 'WAIT_BUYER_PAY',
    subscription_end_date: subscriptionEndDate?.toISOString(),
  })

  // 7. 返回支付链接
  const paymentUrl = `https://zpayz.cn/submit.php?${params.toString()}`
  return NextResponse.json({ code: 'success', paymentUrl })
}

4. 效果展示

  • 用户在定价页点击”立即购买”
  • 选择支付方式(支付宝/微信)
  • 前端调用接口获取支付 URL
  • 自动跳转/打开二维码完成支付

4.4 支付回调 Webhook

1. 功能描述

接收 ZPay 支付平台的异步通知,验证签名、更新订单状态、处理订阅时间,确保支付结果安全可靠。

2. 实现思路

收到回调 → 提取参数 → 验证签名 → 验证商户 ID
        → 检查支付状态 → 查询订单记录 → 验证金额一致性
        → 防止重复处理 → 更新订单状态 → 处理订阅逻辑
        → 返回 success 确认接收

3. 关键代码

// app/api/checkout/providers/zpay/webhook/route.ts

// 验证签名函数
function verifySign(params: Record<string, string>, key: string): boolean {
  const { sign, ...restParams } = params
  const filteredParams = Object.entries(restParams)
    .filter(([_, v]) => v && v !== '')
    .sort((a, b) => a[0].localeCompare(b[0]))
  const prestr = filteredParams.map(([k, v]) => `${k}=${v}`).join('&')
  const calculatedSign = crypto.createHash('md5').update(prestr + key).digest('hex')
  return calculatedSign === sign.toLowerCase()
}

export async function GET(request: NextRequest) {
  const params = Object.fromEntries(request.nextUrl.searchParams)
  
  // 1. 验证签名
  if (!verifySign(params, process.env.ZPAY_KEY!)) {
    console.error('签名验证失败')
    return new NextResponse('fail', { status: 200 })
  }

  // 2. 验证商户 ID
  if (params.pid !== process.env.ZPAY_PID) {
    return new NextResponse('fail', { status: 200 })
  }

  // 3. 检查支付状态
  if (params.trade_status !== 'TRADE_SUCCESS') {
    return new NextResponse('success', { status: 200 })
  }

  // 4. 查询订单
  const supabase = createServerAdminClient()
  const { data: order } = await supabase
    .from('zpay_transactions')
    .select('*')
    .eq('out_trade_no', params.out_trade_no)
    .single()

  // 5. 验证金额一致性(防止"假通知")
  const expectedMoney = parseFloat(order.money.toString())
  const receivedMoney = parseFloat(params.money)
  if (Math.abs(expectedMoney - receivedMoney) > 0.01) {
    return new NextResponse('fail', { status: 200 })
  }

  // 6. 防止重复处理
  if (order.notify_success && order.trade_status === 'TRADE_SUCCESS') {
    return new NextResponse('success', { status: 200 })
  }

  // 7. 更新订单状态
  await supabase.from('zpay_transactions').update({
    zpay_trade_no: params.trade_no,
    trade_status: 'TRADE_SUCCESS',
    notify_success: true,
    paid_at: new Date().toISOString(),
  }).eq('out_trade_no', params.out_trade_no)

  // 8. 处理订阅(更新用户权限)
  if (order.is_subscription) {
    // 可在此更新 user_subscriptions 表或 users.premium_expire_at
  }

  return new NextResponse('success', { status: 200 })
}

4. 效果展示

  • 用户完成支付后,ZPay 后台自动发送回调通知
  • 系统验证签名、更新订单状态
  • Dashboard 显示”已支付”状态
  • 订阅产品自动延长会员到期时间

4.5 Header 用户状态同步

1. 功能描述

全局 Header 组件实时显示用户登录状态,未登录显示”登录/注册”按钮,已登录显示”个人中心”入口,支持状态自动同步。

2. 实现思路

组件挂载 → 检查当前用户状态 → 监听 auth 状态变化
        → 状态变化自动更新 UI → 清理订阅(防止内存泄漏)
        → 路由预加载优化体验 → 导航时显示加载动画

3. 关键代码

// components/ui/header.tsx
"use client"

import { useState, useEffect } from 'react'
import { createClient } from '@/utils/supabase/client'
import { useRouter } from 'next/navigation'

export default function Header() {
  const [user, setUser] = useState<User | null>(null)
  const [loading, setLoading] = useState(true)
  const [isNavigating, setIsNavigating] = useState(false)
  const supabase = createClient()
  const router = useRouter()

  useEffect(() => {
    // 检查登录状态
    const checkUser = async () => {
      const { data: { user } } = await supabase.auth.getUser()
      setUser(user || null)
      setLoading(false)

      // 监听状态变化
      const { data: { subscription } } = await supabase.auth.onAuthStateChange((_event, session) => {
        setUser(session?.user || null)
      })

      return () => subscription.unsubscribe()
    }
    checkUser()
  }, [])

  const handleDashboardClick = (e: React.MouseEvent) => {
    e.preventDefault()
    setIsNavigating(true)
    router.prefetch('/dashboard')
    setTimeout(() => router.push('/dashboard'), 100)
  }

  return (
    <header>
      {loading ? (
        // 骨架屏加载
        <div className="h-8 w-20 bg-gray-200 rounded-md animate-pulse" />
      ) : user ? (
        // 已登录
        <a href="/dashboard" onClick={handleDashboardClick}>
          {isNavigating ? <Spinner /> : '个人中心'}
        </a>
      ) : (
        // 未登录
        <>
          <Link href="/signin">登录</Link>
          <Link href="/signup">注册</Link>
        </>
      )}
    </header>
  )
}

4. 效果展示

  • 页面加载时 Header 显示骨架屏动画
  • 加载完成后显示登录/注册按钮
  • 登录后实时显示”个人中心”
  • 点击个人中心显示加载 Spinner,预加载路由

4.6 订阅时间累加计算

1. 功能描述

用户购买订阅产品时,如果已有活跃订阅,新订阅时间从当前订阅结束日期开始累加,而不是从当前时间计算。

2. 实现思路

用户购买订阅 → 查询用户是否有未过期订阅
            ├─ 有 → 从订阅结束日期开始累加(月付 +1 月,年付 +1 年)
            └─ 无 → 从现在开始计算
            → 保存新的订阅结束日期 → 返回支付链接

3. 关键代码

// 计算订阅结束日期
async function calculateSubscriptionEndDate(
  userId: string,
  productId: string,
  subscriptionPeriod: 'monthly' | 'yearly'
) {
  const supabase = createServerAdminClient()

  // 查询是否有活跃订阅
  const { data: existingTransactions } = await supabase
    .from('zpay_transactions')
    .select('subscription_end_date, trade_status')
    .eq('user_id', userId)
    .eq('product_id', productId)
    .eq('trade_status', 'TRADE_SUCCESS')
    .gt('subscription_end_date', new Date().toISOString())
    .order('subscription_end_date', { ascending: false })
    .limit(1)

  let subscriptionEndDate: Date

  if (existingTransactions && existingTransactions.length > 0) {
    // 已有活跃订阅,从结束日期累加
    const currentEndDate = new Date(existingTransactions[0].subscription_end_date)
    subscriptionEndDate = new Date(currentEndDate)

    if (subscriptionPeriod === 'monthly') {
      subscriptionEndDate.setMonth(subscriptionEndDate.getMonth() + 1)
    } else if (subscriptionPeriod === 'yearly') {
      subscriptionEndDate.setFullYear(subscriptionEndDate.getFullYear() + 1)
    }
  } else {
    // 新订阅,从现在开始
    subscriptionEndDate = new Date()
    if (subscriptionPeriod === 'monthly') {
      subscriptionEndDate.setMonth(subscriptionEndDate.getMonth() + 1)
    } else if (subscriptionPeriod === 'yearly') {
      subscriptionEndDate.setFullYear(subscriptionEndDate.getFullYear() + 1)
    }
  }

  return subscriptionEndDate
}

4. 效果展示

  • 用户 1 月 1 日购买月卡,到期时间 2 月 1 日
  • 用户 1 月 15 日再次购买月卡,到期时间自动变为 3 月 1 日(而非 2 月 15 日)
  • Dashboard 显示”会员到期时间:2026-03-01″

五、安全机制

1. 认证安全

措施说明实现
HttpOnly Cookie防止 XSS 攻击读取Supabase SSR 自动设置
JWT 过期时间降低令牌泄露风险默认 1 小时
Refresh Token无感知续期onAuthStateChange 监听
密码哈希数据库不存明文Supabase Auth 内置

2. 支付安全

// 多层验证
1. 签名验证 → 确保请求来自 ZPay
2. 商户 ID 验证 → 确保通知发送给正确的商户
3. 金额验证 → 防止篡改支付金额
4. 订单号验证 → 确保订单存在
5. 防重复处理 → 避免重复发放权益

3. RLS 行级权限

-- 用户只能访问自己的数据
CREATE POLICY "select_own_data" ON users
  FOR SELECT USING (auth.uid() = id);

CREATE POLICY "update_own_data" ON users
  FOR UPDATE USING (auth.uid() = id);

-- 交易记录:用户可查看自己的记录
CREATE POLICY "select_own_transactions" ON zpay_transactions
  FOR SELECT USING (auth.uid() = user_id);

4. 环境变量管理

# .env.local(本地开发)
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...
NEXT_PUBLIC_BASE_URL=http://localhost:3000
ZPAY_PID=xxx
ZPAY_KEY=xxx

# Vercel 环境变量(生产环境)
# 在 Dashboard → Settings → Environment Variables 中配置
# SUPABASE_SERVICE_ROLE_KEY 仅服务端可访问

六、部署与运维

Vercel 部署步骤

1. 安装 Vercel CLI
   npm i -g vercel

2. 登录 Vercel
   vercel login

3. 首次部署
   vercel

4. 生产部署
   vercel --prod

环境变量配置

变量名环境说明
NEXT_PUBLIC_SUPABASE_URLAllSupabase 项目 URL
NEXT_PUBLIC_SUPABASE_ANON_KEYAll匿名密钥(可暴露)
SUPABASE_SERVICE_ROLE_KEYServer Only管理员密钥(绝不可暴露)
NEXT_PUBLIC_BASE_URLAll项目基础 URL
ZPAY_PIDServer Only支付商户 ID
ZPAY_KEYServer Only支付签名密钥

数据库迁移

# 使用 Supabase CLI 进行迁移
supabase migration new create_users_table
supabase migration new create_transactions_table
supabase db push

监控与日志

// 错误日志记录
console.error('支付回调错误:', error);

// 性能监控(可接入)
// - Vercel Analytics
// - Supabase Query Stats
// - Sentry 错误追踪

七、性能优化

1. 路由预加载

// Dashboard 链接预加载
<a
  href="/dashboard"
  onMouseEnter={() => router.prefetch('/dashboard')}
  onClick={() => router.push('/dashboard')}
>
  个人中心
</a>

2. 组件懒加载

// 客户端组件按需加载
const DashboardClient = dynamic(() => import('./dashboard/client'), {
  loading: () => <DashboardSkeleton />
});

3. 数据缓存

// API 响应缓存
const response = await fetch(url, {
  cache: 'force-cache',  // SSG
  // next: { revalidate: 3600 }  // ISR
});

4. 图片优化

// 使用 Next.js Image 组件
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority
  sizes="(max-width: 768px) 100vw, 1200px"
/>

5. 字体优化

const inter = Inter({
  subsets: ['latin'],
  variable: '--font-inter',
  display: 'swap',  // 防止 FOIT
});

八、总结与展望

项目亮点总结

方面实现
技术栈Next.js 14 + Supabase + TypeScript
认证系统JWT + Cookie + RLS 三重保护
支付系统完整的支付流程 + 安全验证
订阅管理自动累加订阅时间
部署Vercel 一键部署 + 全球 CDN

待优化功能

  •  添加邮件通知(支付成功/订阅到期)
  •  集成 Stripe 作为备选支付
  •  添加管理员后台
  •  数据看板(收入/用户统计)
  •  博客系统(CMS 集成)
  •  单元测试 + E2E 测试

遇到的问题

1.订阅逻辑错误,如果多次订阅,没有将最远的一次订阅作为开始时间?

查询用户是否有未过期的活跃订阅  -->   已有活跃订阅,从结束日期累加  -->  已有活跃订阅,从结束日期累加

GitHub:https://github.com/qiuxuezhe345/template-payment-toturial
作者: houzhibin
博客: (houzhibin.top
发布时间: 2026 年 3 月

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇