Campsite 开源项目开发指南:打造现代团队协作平台
项目架构解析与开发实践
Campsite 是基于 monorepo 架构的团队协作平台,集成实时通讯、文档协作等核心功能。以下是详细实现方案:
技术选型说明
| 模块 | 实现技术 | 功能定位 |
|---|---|---|
| Web 界面 | Next.js + React + TypeScript | 主应用交互层 |
| 后端服务 | Ruby on Rails | 业务逻辑处理 |
| 实时通信 | Pusher + 100ms | 消息推送与视频会议 |
| 文档编辑 | TipTap + Yjs | 协同编辑功能 |
| 文件存储 | AWS S3 + Imgix | 媒体资源管理 |
开发环境搭建
# 环境检测
node --version # >= 20.0.0
ruby --version # 3.x
pnpm --version # >= 8.6.0
# 初始化流程
git clone https://gitcode.com/gh_mirrors/ca/campsite.git
cd campsite
pnpm install
script/setup
cd api
script/credentials development
核心配置参数
# AWS 认证配置
aws:
access_key_id: YOUR_ACCESS_KEY
secret_access_key: YOUR_SECRET_KEY
region: us-east-1
media_bucket: campsite-media-dev
# 实时通信设置
pusher:
app_id: YOUR_APP_ID
key: YOUR_KEY
secret: YOUR_SECRET
cluster: mt1
# 图片处理配置
imgix:
url: https://your-domain.imgix.net
source_id: YOUR_SOURCE_ID
api_key: YOUR_API_KEY
API 接口实现
class MessagesController < BaseController
before_action :authenticate_user!
before_action :set_channel
def create
message = @channel.messages.build(message_params)
message.sender = current_user
if message.save
MessagesChannel.broadcast_to(@channel, message: serialize_message(message))
render json: serialize_message(message), status: :created
else
render json: { errors: message.errors }, status: :unprocessable_entity
end
end
private
def set_channel
@channel = current_organization.channels.find(params[:channel_id])
end
def message_params
params.require(:message).permit(:content_markdown, :reply_to_id)
end
def serialize_message(message)
MessageSerializer.new(message).as_json
end
end
前端组件实现
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import * as z from 'zod'
const messageSchema = z.object({
content: z.string().min(1, '请输入消息内容'),
channelId: z.string().uuid()
})
export function MessageComposer({ channelId }: { channelId: string }) {
const { register, handleSubmit, reset } = useForm({
resolver: zodResolver(messageSchema),
defaultValues: { content: '', channelId }
})
const onSubmit = async (data: z.infer<typeof messageSchema>) => {
try {
await fetch('/api/v2/messages', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getApiKey()}`
},
body: JSON.stringify({
content_markdown: data.content,
channel_id: data.channelId
})
})
reset()
} catch (error) {
console.error('消息发送失败:', error)
}
}
return (
<form onSubmit={handleSubmit(onSubmit)} className="message-composer">
<textarea
{...register('content')}
placeholder="输入消息..."
className="w-full p-3 border rounded-lg"
/>
<button type="submit" className="bg-blue-500 text-white px-4 py-2 rounded">
发送
</button>
</form>
)
}
部署与优化
# 生产环境部署
pnpm build
cd apps/web && npx vercel --prod
cd api && bundle exec rails assets:precompile
# 数据库索引优化
CREATE INDEX index_messages_on_channel_id_and_created_at
ON messages(channel_id, created_at DESC);
CREATE INDEX index_users_on_online_status
ON users(online_status) WHERE online_status = 1;
测试方案
class MessageTest < ActiveSupport::TestCase
test "验证消息内容" do
message = Message.new
assert_not message.valid?
assert_includes message.errors[:content], "不能为空"
end
test "验证广播机制" do
assert_broadcast_on(@channel) do
@channel.messages.create!(content: "测试", sender: users(:gomez))
end
end
end