从零打造滑板文化社区平台:React 19 + Node.js + AI 微服务全栈实战
本文记录了我们参加 2026 中国大学生计算机设计大赛的作品------Skateboard Hub 滑板文化社区平台的完整技术架构与开发实践。项目涵盖 React 19 前端、Node.js 后端、Python AI 微服务、3D WebGL 定制器以及高德地图智能导览,希望能为全栈开发的同学提供一些参考。
一、项目背景与定位
滑板运动在 2020 年东京奥运会正式入奥,国内滑板文化正处于快速发展期。然而,现有的滑板类平台大多分散在微信公众号、B 站、小红书等不同渠道,缺乏一个集资讯、社区、教学、装备定制、场地导览于一体的综合性平台。
我们的目标是打造一款面向滑板爱好者的"一站式数字家园",核心功能包括:
- 📰 资讯中心:滑板历史、入奥专题、职业滑手故事
- 💬 社区互动:发帖、评论、点赞、关注、私信
- 🎨 3D 滑板定制器:WebGL 实时预览,支持板面/轮子/支架自定义
- 🗺️ 智能地图:高德地图 3D 场景 + AI 场地分析(豆包大模型)
- 🤖 AI 助手:智能问答与滑板图像生成
- 🧑💼 个人主页与管理后台:完整的用户体系与内容审核
二、整体技术架构
我们采用了前后端分离 + AI 微服务 + Docker 容器化的架构设计:
scss
┌─────────────────────────────────────────────────────────┐
│ 前端层 (React 19) │
│ Vite + TypeScript + React Router + Zustand + WebGL │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ API 网关层 (Express) │
│ JWT 认证 / CORS / 限流 / 错误处理 / 反向代理 │
└─────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────────────┐
│ MongoDB │ │ Redis │ │ Python Services │
│ (主数据) │ │ (缓存) │ │ Flask × 3 │
└──────────┘ └──────────┘ │ · AI 生图 │
│ · 地图 AI 分析 │
│ · 智能聊天助手 │
└──────────────────┘
技术选型理由:
| 层级 | 技术 | 选型理由 |
|---|---|---|
| 前端框架 | React 19 + TS | 生态成熟、类型安全、并发特性 |
| 构建工具 | Vite | 极速冷启动、原生 ESM、HMR |
| 状态管理 | Zustand | 轻量、无样板代码、TypeScript 友好 |
| 后端 | Express + MongoDB | 快速开发、Schema 灵活、JSON 原生支持 |
| 缓存 | Redis | 会话存储、热点数据缓存、Rate Limit |
| AI 服务 | Flask | Python AI 生态成熟,快速接入大模型 API |
| 部署 | Docker Compose | 一键启动 6 个容器,环境一致性 |
三、前端架构设计
3.1 React 19 + TypeScript + Vite 工程化
项目采用 Vite 作为构建工具,相比 CRA 有显著的性能提升。我们配置了路径别名 @/ 指向 src/ 目录,避免相对路径地狱:
tsx
// tsconfig.json
"paths": {
"@/*": ["./src/*"]
}
// 使用示例
import { useAuthStore } from '@/store/authStore'
import { api } from '@/lib/api'
路由采用 react-router-dom v7 的 BrowserRouter,所有页面通过统一的 Layout 组件包裹,实现导航栏和全局状态的共享:
tsx
function App() {
return (
<BrowserRouter>
<Routes>
<Route element={<Layout />}>
<Route path="/" element={<HomePage />} />
<Route path="/community" element={<CommunityPage />} />
<Route path="/customizer" element={<CustomizerPage />} />
<Route path="/map" element={<MapPage />} />
{/* ... 共 17 个路由 */}
</Route>
</Routes>
</BrowserRouter>
)
}
3.2 Zustand 状态管理实践
我们选择了 Zustand 而非 Redux,因为它足够轻量且无需 Provider 包裹。
以用户认证状态为例,我们实现了一个完整的 AuthStore,包含登录、注册、登出、Token 自动续期等功能:
ts
export const useAuthStore = create<AuthState>((set, get) => ({
token: localStorage.getItem('token'),
user: JSON.parse(localStorage.getItem('currentUser') || 'null'),
isLoggedIn: !!localStorage.getItem('token'),
async login(username, password) {
const data = await api.post('/api/auth/login', { username, password })
if (data.success) {
get().setToken(data.data.token)
get().setUser(data.data.user)
}
return data
},
logout() {
localStorage.removeItem('token')
localStorage.removeItem('currentUser')
set({ token: null, user: null, isLoggedIn: false })
},
// ...
}))
亮点设计:头像动态生成。当用户未上传头像时,自动生成基于用户名的 SVG 头像,减少无意义的首屏请求:
ts
function getAvatarUrl(user: User | null): string {
if (user?.avatar) return user.avatar
if (user?.defaultAvatar) {
const { text, color } = user.defaultAvatar
const svg = `<svg...>${text}</svg>`
return 'data:image/svg+xml;base64,' + btoa(svg)
}
return '/assets/images/avatars/小狗头像.jpg'
}
四、后端架构与安全设计
4.1 Express 工程化结构
后端采用经典的 MVC 分层:
bash
server/
├── app.js # 入口:中间件注册 → 路由挂载 → 静态文件 → 全局错误处理
├── routes/ # 路由层:按模块拆分
├── models/ # 数据层:Mongoose Schema
├── middleware/ # 中间件:认证、权限、上传、错误处理
└── utils/ # 工具层:邮件、缓存、分页、软删除
app.js 的核心逻辑非常清晰:
js
// 1. 数据库连接
connectDB()
// 2. CORS 白名单(生产环境严格限制来源)
app.use(cors({
origin: function (origin, callback) {
if (!origin) return callback(null, true)
if (ALLOWED_ORIGINS.includes(origin)) return callback(null, true)
callback(null, false) // 静默拒绝,不暴露服务器信息
},
credentials: true,
}))
// 3. 请求体大小限制,防止 DoS
app.use(express.json({ limit: '1mb' }))
// 4. API 路由
app.use('/api/auth', require('./routes/auth'))
app.use('/api/posts', require('./routes/posts'))
// ...
// 5. SPA 回退:非 API 路由返回 index.html
app.get('*', (req, res) => {
if (req.path.startsWith('/api')) {
return res.status(404).json({ success: false, message: 'API 接口不存在' })
}
res.sendFile(path.join(staticRoot, 'index.html'))
})
// 6. 全局错误处理
app.use(errorHandler)
4.2 JWT 认证的安全强化
认证是社区类应用的核心,我们在标准 JWT 基础上做了多层加固:
1. 启动时强制校验密钥强度
js
if (!process.env.JWT_SECRET || process.env.JWT_SECRET.length < 32) {
console.error('[FATAL] JWT_SECRET 未设置或长度不足 32 字符,服务器拒绝启动')
process.exit(1)
}
2. 强制算法为 HS256
避免 none 算法攻击或算法混淆攻击:
js
jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] })
3. Token 黑名单机制
用户登出后,将 Token 加入 Redis 黑名单,即使 Token 未过期也无法继续使用:
js
const token = authHeader.split(' ')[1]
if (await isBlacklisted(token)) {
return res.status(401).json({ message: 'Token 已失效,请重新登录' })
}
4. 可选认证模式
社区帖子列表允许游客浏览,但点赞/评论需要登录。我们通过 optionalAuth 中间件实现这一需求:
js
async function optionalAuth(req, res, next) {
try {
// 尝试解析 Token,失败也不拦截
const user = await verifyToken(req)
if (user) req.user = user
} catch (e) { /* ignore */ }
next()
}
4.3 其他安全措施
- bcrypt 密码哈希:salt rounds = 12,抵抗彩虹表攻击
- 文件上传校验:MIME 类型 + 扩展名双重检查
- Admin 搜索转义:正则表达式转义,防止 ReDoS
- 代理路由统一认证:内部 Python 服务不直接暴露,通过 Express 反向代理并校验 Token
五、AI 微服务设计
我们将 AI 能力拆分为 3 个独立的 Flask 微服务,通过 Docker Compose 编排:
| 服务 | 端口 | 职责 |
|---|---|---|
image-generator-web |
5000 | 基于 Stable Diffusion / 火山引擎的滑板图像生成 |
map-server-docker |
5001 | 接收地图坐标与描述,调用豆包大模型进行场地分析 |
ai-assistant |
5002 | 通用聊天助手,支持滑板知识问答 |
为什么拆分为微服务?
- 技术异构:Python 在 AI 领域生态更成熟(OpenCV、Pillow、requests)
- 独立扩展:AI 服务通常计算密集型,可单独扩容 GPU 实例
- 故障隔离:AI 服务崩溃不影响主站 API
- 团队并行:前后端 + AI 可独立开发部署
Express 侧通过 express-http-proxy 将请求转发到对应服务:
js
app.use('/api/ai/*', authMiddleware, proxy('http://ai:5000'))
app.use('/api/map/*', authMiddleware, proxy('http://map:5001'))
六、3D 滑板定制器的技术实现
3D 定制器是项目的亮点功能之一。用户可以在网页上实时更换板面图案、轮子颜色、支架样式,并 360° 旋转查看效果。
技术方案:
- 模型格式:GLTF/GLB(轻量、支持 PBR 材质)
- 渲染引擎:Three.js(WebGL 封装,社区生态丰富)
- 材质替换 :通过
TextureLoader动态加载用户选择的图案,替换mesh.material.map - 交互控制 :
OrbitControls实现旋转/缩放/平移
核心流程:
js
// 1. 加载 GLTF 模型
const loader = new GLTFLoader()
loader.load('/assets/models/skateboard.gltf', (gltf) => {
const skateboard = gltf.scene
scene.add(skateboard)
})
// 2. 用户选择新图案时,动态替换贴图
const texture = new THREE.TextureLoader().load(newPatternUrl)
texture.flipY = false // GLTF 使用 OpenGL 坐标系
boardMesh.material.map = texture
boardMesh.material.needsUpdate = true
为了兼顾加载速度,我们使用了 Draco 压缩和 WebP 纹理,将模型体积控制在 3MB 以内。
七、智能地图与 AI 场地分析
地图模块基于高德地图 JS API 2.0,集成了以下能力:
- 3D 地图视图 :
AMap.Map开启viewMode: '3D',展示城市建筑立体效果 - 地点搜索与 POI 检索:输入"滑板公园"自动检索周边场地
- 路径规划:集成公交/驾车/步行路线规划
- 天气查询:显示目标城市的实时天气,辅助用户决定是否出门滑板
- AI 场地分析 :用户点击地图上的场地后,前端将坐标和场地名称发送到
map-server-docker,由豆包大模型生成场地评测报告(如"该场地坡度适中,适合练习 Ollie,但周末人流较多")
js
// 前端调用示例
const response = await fetch('/api/map/analyze', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify({
location: [116.397428, 39.90923],
name: '朝阳滑板公园'
})
})
const { analysis } = await response.json()
// analysis: "该场地拥有专业的碗池和街式区,地面平整度高..."
八、Docker 容器化部署
为了让评委老师能"一键跑起来",我们编写了完整的 Docker Compose 配置:
yaml
services:
mongo:
image: mongo:7
container_name: skateboard-mongo
command: ["mongod", "--replSet", "rs0", "--auth", "--keyFile", "/data/configdb/keyfile"]
volumes:
- mongo_data:/data/db
redis:
image: redis:7-alpine
container_name: skateboard-redis
web:
build: ./server
container_name: skateboard-web
ports:
- "3000:3000"
volumes:
- ./server:/app
- /app/node_modules
- ./server/public/uploads:/app/public/uploads
- ./client/dist:/app/dist
depends_on:
- mongo
- redis
ai:
build: ./image-generator-web
container_name: skateboard-ai
map:
build:
context: .
dockerfile: map-server-docker/Dockerfile
container_name: skateboard-map
ai-assistant:
build: ./ai-assistant
container_name: skateboard-ai-assistant
ports:
- "5002:5002"
关键设计点:
- 数据持久化 :MongoDB 使用命名卷
mongo_data,容器重启数据不丢失 - 代码热更新 :
server目录挂载到容器,开发时无需重新构建镜像 - 上传文件持久化 :
public/uploads挂载到宿主机,防止用户头像丢失 - 前端产物挂载 :
client/dist直接挂载到容器,实现前后端一体化部署
启动命令极简:
bash
chmod +x start.sh
./start.sh
# 等价于 docker compose up -d --build
九、开发过程中的挑战与解决方案
挑战 1:前端静态页面与 React SPA 的融合
项目中既有历史遗留的 HTML 静态页面(如教学页面、资讯页面),又有新的 React SPA 路由。我们采取的方案是:
- React 构建产物
client/dist作为首选静态资源根目录 - 原始 HTML 页面放在
server/public/pages/下作为降级方案 - Express 优先查找
dist/index.html,找不到再回退到public/ - 原始页面通过
<a href="/pages/xxx.html">跳转,保持 URL 统一
挑战 2:CORS 与 Cookie 的跨域问题
开发时前端跑在 localhost:5173,后端在 localhost:3000。我们配置了动态 CORS 白名单,并开启 credentials: true:
js
app.use(cors({
origin: (origin, callback) => {
if (!origin || ALLOWED_ORIGINS.includes(origin)) {
callback(null, true)
} else {
callback(null, false)
}
},
credentials: true,
}))
挑战 3:MongoDB 副本集与认证
Docker 中的 MongoDB 需要开启认证,而某些 Mongoose 功能(如事务)需要副本集。我们在 docker-compose.yml 中配置了 --replSet rs0 和 --keyFile,并在启动脚本中自动初始化副本集:
bash
# start.sh 片段
docker compose exec mongo mongosh --eval "rs.initiate()"
十、总结与收获
这次比赛让我们完整走通了"需求分析 → 技术选型 → 架构设计 → 编码实现 → 容器化部署"的全流程。
技术层面的收获:
- React 19 的新特性:实际体验了 React 19 的并发渲染和自动批处理,配合 Zustand 状态管理非常流畅
- 安全意识的提升:JWT 黑名单、CORS 白名单、bcrypt 哈希、请求体限制,这些"防御性编程"实践让系统更健壮
- 微服务通信:通过 Express 反向代理统一暴露 AI 服务,既保持了前端的简单调用,又实现了后端的安全隔离
- Docker 工程化:多容器编排、数据卷管理、环境变量配置,让项目真正做到了"开箱即用"
产品层面的思考:
技术最终要服务于用户。我们在设计 3D 定制器时发现,滑板玩家对"个性化"有极强的诉求------每一块板都是滑手自我表达的延伸。因此我们在 UI 上花了大量时间打磨交互细节,确保图案替换的响应时间在 100ms 以内,旋转视角的帧率稳定在 60fps。
附录:项目地址与技术栈
- 仓库地址 :gitee.com/zhang-huair...
- 技术栈:React 19 + TypeScript + Vite + Express + MongoDB + Redis + Python Flask + Docker
- 部署方式:Docker Compose 一键启动
- 许可证:MIT
如果您对项目有任何问题,欢迎在评论区留言交流!🛹
作者:张怀睿
赛事:2026 中国大学生计算机设计大赛
标签:
ReactNode.jsMongoDBDockerWebGLAI全栈开发