Easy Vibe Coding 学习心得(四):后端之力——从数据库到全栈应用
Easy Vibe Coding 学习心得(四):后端之力——从数据库到全栈应用
一、从"前端"到"全栈"——认知的升级
1.1 前一篇的回顾
在上一篇文章中,我学习了前端设计的艺术。从 Figma 设计工具到组件库,从提示词美化到 Skills 插件,我做出了一个精美的霍格沃茨魔法画像页面。
那个项目能看、能聊、能变化表情,界面充满魔法世界的氛围。
但朋友试用后问了我几个问题:
“聊天记录换个设备就没了?”
“我想和朋友分享这个应用,怎么部署上线?”
“如果很多人同时用,服务器扛得住吗?”
那一刻我意识到:前端再美,也只是"面子";后端才是"里子"。
1.2 新的问题
我开始思考一个完整应用需要解决的后端问题:
数据持久化:
- 用户数据存在哪里?
- 如何保证数据安全?
- 换设备后数据能同步吗?
用户系统:
- 如何注册登录?
- 如何管理用户权限?
- 如何防止恶意攻击?
业务逻辑:
- 复杂的计算放在哪里?
- 如何调用第三方 API?
- 如何处理并发请求?
部署运维:
- 应用部署在哪里?
- 如何监控运行状态?
- 出问题了怎么排查?
带着这些问题,我进入了 Stage 3 的后端部分——学习如何构建全栈应用。
二、后端技术选型:从简单到复杂
2.1 后端技术的演变
教程里对后端技术的演变讲得很清楚:
第一代:传统后端(2000-2010)
- Java + Spring、.NET、PHP
- 需要自己买服务器、配置环境
- 部署复杂,运维成本高
第二代:云服务时代(2010-2020)
- AWS、Azure、阿里云
- 按需付费,弹性伸缩
- 但仍需要运维知识
第三代:Serverless 时代(2020-至今)
- Vercel、Zeabur、Supabase
- 零运维,自动扩缩容
- 按使用量付费,成本极低
Vibe Coding 时代的选择:
- 前端托管:Vercel、Netlify、Zeabur
- 后端服务:Supabase、Firebase、Appwrite
- AI 能力:DeepSeek、SiliconFlow、Dify
2.2 我的技术栈选择
基于教程推荐和实际需求,我选择了以下技术栈:
| 需求 | 选择 | 理由 |
|---|---|---|
| 前端框架 | Vue 3 | 学习曲线平缓,中文文档完善 |
| UI 组件库 | Element Plus | 企业级组件,开箱即用 |
| 前端托管 | Zeabur | 国内访问快,免费额度够用 |
| 数据库 | Supabase | PostgreSQL 内核,功能完整 |
| 用户认证 | Supabase Auth | 内置多种登录方式 |
| AI 文本 | DeepSeek | 性价比高,中文能力强 |
| AI 图像 | SiliconFlow | 生成速度快,效果好 |
总成本:
- 开发阶段:$0(全部使用免费额度)
- 小规模使用:约 $10/月
- 大规模使用:按需付费
三、Supabase 入门:5 分钟搭建后端
3.1 什么是 Supabase
教程里有个比喻很形象:
“Supabase 就是’开源版的 Firebase’,提供数据库、用户认证、文件存储、实时订阅等一站式后端服务。”
Supabase 提供什么?
- Database:PostgreSQL 数据库
- Auth:用户注册登录
- Storage:文件存储
- Edge Functions:后端函数
- Realtime:实时数据推送
3.2 创建第一个项目
第一步:注册账号
- 访问 supabase.com
- 用 GitHub 账号登录
- 创建新项目
第二步:创建数据表
在 Supabase 控制台执行 SQL:
-- 创建博客文章表
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT,
author_id UUID REFERENCES auth.users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建行级安全策略
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;
-- 允许任何人读取
CREATE POLICY "允许任何人读取" ON posts
FOR SELECT USING (true);
-- 允许登录用户创建
CREATE POLICY "允许登录用户创建" ON posts
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
-- 允许作者更新自己的文章
CREATE POLICY "允许作者更新" ON posts
FOR UPDATE USING (auth.uid() = author_id);
第三步:获取 API Key
在设置中找到:
- Project URL:
https://xxx.supabase.co - Anon Key:
eyJhbGc...(前端使用) - Service Role Key:
eyJhbGc...(后端使用,不要泄露)
3.3 前端接入 Supabase
第一步:安装 SDK
npm install @supabase/supabase-js
第二步:创建客户端
// utils/supabase.js
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = 'https://xxx.supabase.co'
const supabaseKey = 'your-anon-key'
export const supabase = createClient(supabaseUrl, supabaseKey)
第三步:实现用户登录
// 邮箱密码登录
async function signInWithEmail(email, password) {
const { data, error } = await supabase.auth.signInWithPassword({
email,
password
})
if (error) {
console.error('登录失败:', error.message)
return null
}
return data.user
}
// 注册
async function signUp(email, password) {
const { data, error } = await supabase.auth.signUp({
email,
password
})
if (error) {
console.error('注册失败:', error.message)
return null
}
return data.user
}
// GitHub 登录(一键登录)
async function signInWithGitHub() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: window.location.origin
}
})
}
// 退出登录
async function signOut() {
await supabase.auth.signOut()
}
// 获取当前用户
function getCurrentUser() {
return supabase.auth.getUser()
}
第四步:监听登录状态
// 监听认证状态变化
supabase.auth.onAuthStateChange((event, session) => {
if (event === 'SIGNED_IN') {
console.log('用户已登录')
// 更新 UI
} else if (event === 'SIGNED_OUT') {
console.log('用户已退出')
// 更新 UI
}
})
四、项目实战:AI 个人博客系统
4.1 项目背景
学完 Supabase 后,我决定做一个完整的全栈项目来实践所学。
项目目标:
- 用户可以注册登录
- 输入一个主题,AI 自动生成博客文章
- 文章保存到数据库
- 支持编辑、删除、分享
技术栈:
- 前端:Vue 3 + Element Plus
- 后端:Supabase(数据库 + 认证)
- AI:DeepSeek(文章生成)
- 部署:Zeabur

4.2 需求分析
核心功能:
- 用户系统:注册、登录、退出
- 文章生成:输入主题,AI 生成文章
- 文章管理:查看、编辑、删除
- 分享功能:生成分享链接
非功能需求:
- 响应式设计,支持手机和电脑
- 数据实时同步
- 加载状态友好提示
4.3 数据库设计
数据表结构:
-- 用户资料表
CREATE TABLE profiles (
id UUID REFERENCES auth.users PRIMARY KEY,
username VARCHAR(50) UNIQUE,
avatar_url TEXT,
bio TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
-- 博客文章表
CREATE TABLE posts (
id BIGSERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
summary VARCHAR(500),
cover_image TEXT,
author_id UUID REFERENCES profiles(id),
view_count INTEGER DEFAULT 0,
published BOOLEAN DEFAULT false,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 文章标签表
CREATE TABLE tags (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) UNIQUE NOT NULL
);
-- 文章标签关联表
CREATE TABLE post_tags (
post_id BIGINT REFERENCES posts(id) ON DELETE CASCADE,
tag_id BIGINT REFERENCES tags(id) ON DELETE CASCADE,
PRIMARY KEY (post_id, tag_id)
);
-- 创建触发器:用户注册时自动创建资料
CREATE OR REPLACE FUNCTION handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO profiles (id, username, avatar_url)
VALUES (NEW.id, NEW.email, NEW.raw_user_meta_data->>'avatar_url');
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER on_auth_user_created
AFTER INSERT ON auth.users
FOR EACH ROW EXECUTE FUNCTION handle_new_user();
4.4 前端实现
项目结构:
ai-blog-generator/
├── src/
│ ├── components/
│ │ ├── LoginModal.vue # 登录弹窗
│ │ ├── PostEditor.vue # 文章编辑器
│ │ ├── PostList.vue # 文章列表
│ │ └── PostCard.vue # 文章卡片
│ ├── composables/
│ │ ├── useAuth.js # 认证逻辑
│ │ ├── usePosts.js # 文章逻辑
│ │ └── useAI.js # AI 生成逻辑
│ ├── views/
│ │ ├── Home.vue # 首页
│ │ ├── Editor.vue # 编辑器页面
│ │ └── Profile.vue # 个人主页
│ ├── utils/
│ │ └── supabase.js # Supabase 客户端
│ └── App.vue
├── package.json
└── vite.config.js
核心代码:
<!-- App.vue -->
<template>
<div id="app">
<Navbar :user="currentUser" @login="showLogin = true" @logout="handleLogout" />
<main class="container">
<router-view />
</main>
<LoginModal v-if="showLogin" @close="showLogin = false" @login="handleLogin" />
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { supabase } from './utils/supabase'
import Navbar from './components/Navbar.vue'
import LoginModal from './components/LoginModal.vue'
const currentUser = ref(null)
const showLogin = ref(false)
// 检查登录状态
onMounted(async () => {
const { data: { session } } = await supabase.auth.getSession()
currentUser.value = session?.user || null
})
// 处理登录
function handleLogin(user) {
currentUser.value = user
showLogin.value = false
}
// 处理退出
async function handleLogout() {
await supabase.auth.signOut()
currentUser.value = null
}
</script>
// composables/usePosts.js
import { ref } from 'vue'
import { supabase } from '../utils/supabase'
export function usePosts() {
const posts = ref([])
const loading = ref(false)
const error = ref(null)
// 获取所有文章
async function fetchPosts() {
loading.value = true
error.value = null
try {
const { data, error: err } = await supabase
.from('posts')
.select(`
*,
profiles (username, avatar_url)
`)
.eq('published', true)
.order('created_at', { ascending: false })
if (err) throw err
posts.value = data
} catch (err) {
error.value = err.message
} finally {
loading.value = false
}
}
// 创建文章
async function createPost(title, content, summary, coverImage) {
const { data: { user } } = await supabase.auth.getUser()
if (!user) {
error.value = '请先登录'
return null
}
const { data, error: err } = await supabase
.from('posts')
.insert({
title,
content,
summary,
cover_image: coverImage,
author_id: user.id
})
.select()
.single()
if (err) {
error.value = err.message
return null
}
await fetchPosts()
return data
}
// 删除文章
async function deletePost(id) {
const { error: err } = await supabase
.from('posts')
.delete()
.eq('id', id)
if (err) {
error.value = err.message
return false
}
await fetchPosts()
return true
}
return {
posts,
loading,
error,
fetchPosts,
createPost,
deletePost
}
}
// composables/useAI.js
import { ref } from 'vue'
const DEEPSEEK_API_KEY = import.meta.env.VITE_DEEPSEEK_API_KEY
export function useAI() {
const generating = ref(false)
const generateError = ref(null)
// 生成文章
async function generateArticle(topic) {
generating.value = true
generateError.value = null
try {
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${DEEPSEEK_API_KEY}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是一位专业的博客作者,擅长撰写高质量的技术文章。请根据用户提供的主题,生成一篇结构清晰、内容详实的博客文章。文章应包含:标题、摘要、正文(分多个小节)、总结。使用 Markdown 格式。'
},
{
role: 'user',
content: `请以"${topic}"为主题,生成一篇博客文章。`
}
],
temperature: 0.7,
max_tokens: 2000
})
})
const data = await response.json()
if (!response.ok) {
throw new Error(data.error?.message || '生成失败')
}
const content = data.choices[0].message.content
// 解析 Markdown,提取标题和摘要
const lines = content.split('\n')
const title = lines.find(line => line.startsWith('# '))?.replace('# ', '') || topic
const summary = lines.find(line => line.length > 50 && line.length < 200)?.substring(0, 200) + '...' || ''
return {
title,
content,
summary,
cover_image: await generateCoverImage(topic)
}
} catch (err) {
generateError.value = err.message
return null
} finally {
generating.value = false
}
}
// 生成封面图(调用 SiliconFlow)
async function generateCoverImage(topic) {
const SILICONFLOW_API_KEY = import.meta.env.VITE_SILICONFLOW_API_KEY
try {
const response = await fetch('https://api.siliconflow.cn/v1/images/generations', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${SILICONFLOW_API_KEY}`
},
body: JSON.stringify({
model: 'black-forest-labs/FLUX.1-schnell',
prompt: `A beautiful blog cover image about ${topic}, minimalist style, high quality`,
size: '1024x1024',
n: 1
})
})
const data = await response.json()
return data.data?.[0]?.url || null
} catch (err) {
console.error('图片生成失败:', err)
return null
}
}
return {
generating,
generateError,
generateArticle
}
}
4.5 用户认证实现
<!-- components/LoginModal.vue -->
<template>
<div class="modal-overlay" @click="$emit('close')">
<div class="modal-content" @click.stop>
<div class="modal-header">
<h2>登录 / 注册</h2>
<button class="close-btn" @click="$emit('close')">×</button>
</div>
<div class="modal-body">
<!-- GitHub 一键登录 -->
<button class="github-btn" @click="handleGitHubLogin">
<svg class="github-icon" viewBox="0 0 24 24">
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/>
</svg>
使用 GitHub 登录
</button>
<div class="divider">
<span>或</span>
</div>
<!-- 邮箱登录 -->
<form @submit.prevent="handleEmailLogin">
<div class="form-group">
<label>邮箱</label>
<input
type="email"
v-model="email"
placeholder="your@email.com"
required
/>
</div>
<div class="form-group">
<label>密码</label>
<input
type="password"
v-model="password"
placeholder="至少 6 位"
minlength="6"
required
/>
</div>
<button type="submit" class="submit-btn" :disabled="loading">
{{ isRegister ? '注册' : '登录' }}
</button>
</form>
<p class="toggle-text">
{{ isRegister ? '已有账号?' : '没有账号?' }}
<a href="#" @click.prevent="isRegister = !isRegister">
{{ isRegister ? '去登录' : '去注册' }}
</a>
</p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { supabase } from '../utils/supabase'
const emit = defineEmits(['close', 'login'])
const email = ref('')
const password = ref('')
const isRegister = ref(false)
const loading = ref(false)
// GitHub 登录
async function handleGitHubLogin() {
const { data, error } = await supabase.auth.signInWithOAuth({
provider: 'github',
options: {
redirectTo: window.location.origin
}
})
if (error) {
alert('登录失败:' + error.message)
}
}
// 邮箱登录
async function handleEmailLogin() {
loading.value = true
try {
const { data, error } = isRegister.value
? await supabase.auth.signUp({ email: email.value, password: password.value })
: await supabase.auth.signInWithPassword({ email: email.value, password: password.value })
if (error) throw error
emit('login', data.user)
emit('close')
} catch (error) {
alert(error.message)
} finally {
loading.value = false
}
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #1a1a2e;
border-radius: 20px;
padding: 30px;
width: 90%;
max-width: 400px;
border: 1px solid rgba(255, 255, 255, 0.1);
}
.github-btn {
width: 100%;
padding: 12px;
background: #24292e;
color: white;
border: none;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
cursor: pointer;
font-size: 1em;
transition: all 0.3s ease;
}
.github-btn:hover {
background: #2f363d;
}
.github-icon {
width: 20px;
height: 20px;
fill: white;
}
.divider {
display: flex;
align-items: center;
margin: 20px 0;
color: #888;
}
.divider::before,
.divider::after {
content: '';
flex: 1;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.divider span {
padding: 0 10px;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
color: #888;
}
.form-group input {
width: 100%;
padding: 10px 15px;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 8px;
color: white;
}
.submit-btn {
width: 100%;
padding: 12px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: white;
border: none;
border-radius: 10px;
cursor: pointer;
font-size: 1em;
}
.toggle-text {
text-align: center;
margin-top: 15px;
color: #888;
}
.toggle-text a {
color: #667eea;
}
</style>
4.6 部署上线
第一步:推送到 GitHub
git init
git add .
git commit -m "Initial commit: AI Blog Generator"
git remote add origin git@github.com:username/ai-blog-generator.git
git push -u origin main
第二步:部署到 Zeabur
- 访问 zeabur.com
- 用 GitHub 账号登录
- 点击 “New Project”
- 选择 “Deploy from GitHub”
- 选择项目仓库
- 配置环境变量:
VITE_SUPABASE_URLVITE_SUPABASE_ANON_KEYVITE_DEEPSEEK_API_KEYVITE_SILICONFLOW_API_KEY
- 点击 “Deploy”
第三步:配置自定义域名(可选)
- 在 Zeabur 控制台添加域名
- 在域名服务商处添加 CNAME 记录
- 等待 DNS 生效
4.7 成果展示
功能清单:
- ✅ 用户注册登录(GitHub / 邮箱)
- ✅ 输入主题,AI 自动生成文章
- ✅ 文章保存到数据库
- ✅ 文章列表展示
- ✅ 文章详情查看
- ✅ 作者个人主页
- ✅ 响应式设计
性能数据:
- 文章生成:平均 5-8 秒
- 页面加载:<1 秒
- 首屏渲染:<500ms
成本估算:
- Supabase:免费额度(500MB 数据库,50000 月活用户)
- Zeabur:免费额度(每月 100 万积分)
- DeepSeek:约 ¥0.01/篇(按 token 计费)
- SiliconFlow:约 ¥0.02/张(按生成次数计费)
五、踩过的坑和解决方案
5.1 行级安全策略(RLS)
问题: 创建文章时报错 “permission denied for table posts”。
原因: Supabase 默认开启行级安全策略,需要显式配置权限。
解决:
-- 允许登录用户创建文章
CREATE POLICY "允许登录用户创建" ON posts
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
-- 允许作者更新自己的文章
CREATE POLICY "允许作者更新" ON posts
FOR UPDATE USING (auth.uid() = author_id);
-- 允许任何人读取已发布的文章
CREATE POLICY "允许任何人读取" ON posts
FOR SELECT USING (published = true);
5.2 API Key 泄露风险
问题: 把 API Key 写在前端代码里,有泄露风险。
解决:
// 使用环境变量
const DEEPSEEK_API_KEY = import.meta.env.VITE_DEEPSEEK_API_KEY
// .env 文件(不要提交到 Git)
VITE_DEEPSEEK_API_KEY=sk-xxxxx
VITE_SILICONFLOW_API_KEY=sk-xxxxx
// .gitignore
.env
.env.local
更好的方案: 使用 Supabase Edge Functions 作为代理层。
// edge-functions/generate-article/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
serve(async (req) => {
const { topic } = await req.json()
const response = await fetch('https://api.deepseek.com/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${Deno.env.get('DEEPSEEK_API_KEY')}`
},
body: JSON.stringify({
model: 'deepseek-chat',
messages: [
{
role: 'system',
content: '你是一位专业的博客作者...'
},
{
role: 'user',
content: `请以"${topic}"为主题,生成一篇博客文章。`
}
]
})
})
const data = await response.json()
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' }
})
})
5.3 数据库关联查询
问题: 查询文章时,同时获取作者信息,怎么写?
解决: 使用 Supabase 的关联查询。
const { data, error } = await supabase
.from('posts')
.select(`
*,
profiles (
username,
avatar_url
)
`)
.eq('published', true)
5.4 图片存储方案
问题: AI 生成的图片 URL 会过期,怎么办?
解决: 下载到 Supabase Storage。
// 下载并保存图片
async function saveCoverImage(url, postId) {
const response = await fetch(url)
const blob = await response.blob()
const { data, error } = await supabase.storage
.from('cover-images')
.upload(`posts/${postId}.jpg`, blob)
if (error) throw error
// 获取公开访问 URL
const { data: { publicUrl } } = supabase.storage
.from('cover-images')
.getPublicUrl(`posts/${postId}.jpg`)
return publicUrl
}
六、关键收获
6.1 全栈思维:从"页面"到"系统"
以前我只关注前端页面:
- “这个页面怎么布局”
- “这个动画怎么做”
现在我学会了系统思考:
- “数据存在哪里”
- “如何保证数据安全”
- “用户如何认证”
- “如何部署上线”
全栈不是什么都懂,而是能把各个部分连接起来。
6.2 数据思维:从"静态"到"动态"
以前我的应用数据是静态的:
- 写死在 HTML 里
- 存在 LocalStorage
现在我的应用数据是动态的:
- 存在云端数据库
- 多设备同步
- 实时更新
数据是应用的核心,设计好数据结构,应用就成功了一半。
6.3 安全思维:从"信任"到"验证"
以前我相信用户不会乱来:
- 前端验证就够了
- API Key 直接写代码里
现在我学会了防御性编程:
- 后端验证必不可少
- 行级安全策略保护数据
- API Key 放在服务端
安全不是功能,而是底线。
6.4 工程思维:从"本地"到"线上"
以前我只在本地运行:
- “在我电脑上能跑就行”
现在我学会了工程化部署:
- 环境变量管理
- CI/CD 流程
- 日志监控
- 错误追踪
只有线上稳定运行的代码,才是有价值的代码。
七、下一步计划
7.1 短期优化(1 周内)
- 添加文章评论功能
- 添加点赞/收藏功能
- 添加文章搜索功能
- 优化移动端体验
7.2 中期优化(1 个月内)
- 添加付费订阅功能(Stripe)
- 添加文章统计分析(阅读量、来源)
- 添加 RSS 订阅
- 添加 SEO 优化
7.3 长期规划
- 支持多语言
- 添加协作编辑功能
- 开发桌面应用(Electron)
- 开发小程序版本
八、写给同样想学习的你
8.1 关于"后端太难"
很多人觉得后端很难,我刚开始也这么想。
但学完后我发现:后端的核心就三件事:
- 存数据(数据库)
- 认用户(认证授权)
- 调接口(业务逻辑)
用 Supabase 这样的工具,这三件事都变得非常简单。
工具选对了,难度就降下来了。
8.2 关于"学不完"
技术永远学不完,关键是学以致用。
我的建议是:
- 先确定一个想做的项目
- 遇到不会的技术再去学
- 学了马上用到项目里
项目驱动学习,效率最高。
8.3 关于"做不好"
我做的第一个全栈应用,漏洞百出:
- 数据库没有安全策略
- API Key 直接暴露
- 错误处理一塌糊涂
但没关系,先做出来,再慢慢优化。
每个优秀的开发者,都是从写出烂代码开始的。
8.4 最后的话
学完后端这部分,我最大的感受是:
全栈开发不再是少数人的专利,而是每个前端开发者都能掌握的技能。
Supabase 这样的工具,让后端开发变得像调用 API 一样简单。
我们不需要成为数据库专家,不需要精通运维,只需要专注于业务逻辑和用户体验。
这就是 Vibe Coding 的力量——让复杂的事情变简单。
完成比完美更重要。
这句话我会一直记着。
下一篇:《学习心得(五):综合实战——我的 AI 原生应用上线了》
⚠️ 免责声明:本文由 AI 智能体辅助创作,内容仅供参考。文中涉及的代码示例、技术方案请在实际应用前自行验证。观点仅代表作者个人,不构成任何形式的投资或技术决策建议。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐




所有评论(0)