Nền tảng Đào tạo Doanh nghiệp
Xây dựng nền tảng LMS tùy chỉnh với video hosting, quiz system và certificate generation cho đào tạo nội bộ

Nền tảng Đào tạo Doanh nghiệp
Dự án phát triển Learning Management System (LMS) tùy chỉnh cho doanh nghiệp, thay thế quy trình đào tạo thủ công bằng nền tảng tự động hóa với tracking tiến độ và certificate generation.
Chi tiết Dự án:
- Khách hàng: Enterprise Corp (Fortune 500 company)
- Ngành: Dịch vụ Doanh nghiệp / Tư vấn
- Thời gian: 2 tháng (Tháng 6 - Tháng 7 2024)
- Quy mô Team: 3 developers, 1 designer, 1 PM
- Vai trò: Full-stack Developer / Technical Lead
Thách thức
Enterprise Corp là một công ty lớn với hơn 1000 nhân viên, đang gặp khó khăn với quy trình đào tạo nội bộ truyền thống.
Bối cảnh
Quy trình đào tạo hiện tại gặp nhiều vấn đề:
- Training thủ công: Tổ chức các lớp offline tốn thời gian và chi phí
- Không có tracking: Không theo dõi được tiến độ học tập của nhân viên
- Khó scale: Khó tổ chức training cho nhiều địa điểm khác nhau
- Không có certification: Không có hệ thống cấp chứng chỉ tự động
- Nội dung lỗi thời: Khó cập nhật và duy trì training materials
Yêu cầu Cụ thể
- Video-based Learning: Platform để host và stream training videos
- Progress Tracking: Theo dõi tiến độ học tập của từng nhân viên
- Quiz System: Đánh giá kiến thức sau mỗi module
- Certificate Generation: Tự động tạo certificate khi hoàn thành
- Admin Dashboard: Quản lý courses, users và reports
- Mobile Support: Học tập trên mọi thiết bị
Thách thức Chính
- Video hosting: Cần solution cost-effective cho việc host hàng trăm video
- Performance: Streaming mượt mà cho nhiều users đồng thời
- User engagement: Thiết kế UX/UI để khuyến khích nhân viên hoàn thành courses
- Reporting: Analytics chi tiết cho HR department
- Time constraint: Phải hoàn thành trong 2 tháng để kịp đợt training lớn
Giải pháp
Chúng tôi phát triển một Learning Management System (LMS) tùy chỉnh với Next.js, tối ưu hóa cho video streaming và user engagement.
Approach và Phương pháp
- Phương pháp: Agile với 1-week sprints
- Phát triển: Rapid prototyping và iterative development
- Testing: Continuous testing với real users
- Deployment: Vercel cho fast deployment và scaling
Kiến trúc Hệ thống
Các Thành phần Chính:
-
Frontend: Next.js 15 với App Router
- Server-side rendering cho better SEO
- Optimistic UI updates
- Progressive Web App (PWA) capabilities
-
Backend: Next.js API Routes + PostgreSQL
- RESTful API cho course management
- User progress tracking
- Certificate generation logic
-
Video Storage: AWS S3 + CloudFront CDN
- Cost-effective video hosting
- Global content delivery
- Adaptive bitrate streaming
-
Database: PostgreSQL với Prisma ORM
- User management
- Course data và progress tracking
- Quiz results và analytics
-
File Processing: FFmpeg cho video processing
- Video transcoding
- Thumbnail generation
- Multiple quality versions
-
Authentication: NextAuth.js với corporate SSO
- Single Sign-On với Azure AD
- Role-based access control (RBAC)
- Session management
Quyết định Công nghệ
Tại sao Next.js:
- Fast development với App Router
- API routes cho backend logic
- Excellent performance out-of-the-box
- Easy deployment với Vercel
Tại sao AWS S3 + CloudFront:
- Cost-effective cho video storage
- Global CDN cho fast delivery
- Scalable để handle growth
- Secure với signed URLs
Tại sao PostgreSQL:
- Reliable cho critical business data
- Complex queries cho reporting
- ACID compliance
- Good performance với proper indexing
Tại sao FFmpeg:
- Industry-standard video processing
- Multiple format support
- Quality optimization
- Thumbnail generation
Tính năng Chính
-
Course Management
- Course creation và organization
- Module và lesson structure
- Rich text editor cho descriptions
- File attachments (PDFs, documents)
-
Video Learning
- Adaptive bitrate streaming
- Resume playback functionality
- Video bookmarks
- Playback speed control
- Subtitle support
-
Quiz System
- Multiple choice questions
- True/false questions
- Passing score requirements
- Instant feedback
- Retry options
-
Progress Tracking
- Real-time progress updates
- Course completion percentage
- Time spent tracking
- Learning analytics
-
Certificate Generation
- Automatic certificate creation
- PDF download
- Digital signatures
- Verification system
-
Admin Dashboard
- User management
- Course analytics
- Completion reports
- Performance metrics
- Bulk user import
Triển khai
Phase 1: Foundation (Tuần 1-2)
Mục tiêu:
- Setup project infrastructure
- Design database schema
- Implement authentication
- Basic course structure
Tuần 1: Project Setup
// Database schema design với Prisma
model User {
id String @id @default(uuid())
email String @unique
name String
role Role @default(EMPLOYEE)
enrollments Enrollment[]
quizAttempts QuizAttempt[]
certificates Certificate[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Course {
id String @id @default(uuid())
title String
description String @db.Text
thumbnail String?
modules Module[]
enrollments Enrollment[]
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Module {
id String @id @default(uuid())
courseId String
course Course @relation(fields: [courseId], references: [id])
title String
order Int
lessons Lesson[]
createdAt DateTime @default(now())
}
model Lesson {
id String @id @default(uuid())
moduleId String
module Module @relation(fields: [moduleId], references: [id])
title String
type LessonType // VIDEO, QUIZ, READING
videoUrl String?
content String? @db.Text
duration Int? // Duration in seconds
order Int
progresses LessonProgress[]
createdAt DateTime @default(now())
}
model Enrollment {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id])
courseId String
course Course @relation(fields: [courseId], references: [id])
progress Int @default(0) // Percentage
completedAt DateTime?
certificateId String?
lessonProgress LessonProgress[]
enrolledAt DateTime @default(now())
}
model LessonProgress {
id String @id @default(uuid())
enrollmentId String
enrollment Enrollment @relation(fields: [enrollmentId], references: [id])
lessonId String
lesson Lesson @relation(fields: [lessonId], references: [id])
completed Boolean @default(false)
timeSpent Int @default(0) // Seconds
lastPosition Int? // For video resume
completedAt DateTime?
updatedAt DateTime @updatedAt
}
Tuần 2: Authentication & Basic UI
Implement SSO authentication:
// NextAuth.js configuration với Azure AD
import NextAuth from 'next-auth';
import AzureADProvider from 'next-auth/providers/azure-ad';
export const authOptions = {
providers: [
AzureADProvider({
clientId: process.env.AZURE_AD_CLIENT_ID!,
clientSecret: process.env.AZURE_AD_CLIENT_SECRET!,
tenantId: process.env.AZURE_AD_TENANT_ID!,
}),
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},
async session({ session, token }) {
if (session.user) {
session.user.role = token.role;
}
return session;
},
},
};
export default NextAuth(authOptions);
Phase 2: Core Features (Tuần 3-5)
Tuần 3: Video Upload & Processing
Implement video upload với AWS S3:
// API route cho video upload
import { NextRequest, NextResponse } from 'next/server';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3Client = new S3Client({
region: process.env.AWS_REGION!,
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
},
});
export async function POST(req: NextRequest) {
try {
const { fileName, fileType } = await req.json();
const key = `videos/${Date.now()}-${fileName}`;
// Generate presigned URL cho upload
const command = new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET!,
Key: key,
ContentType: fileType,
});
const uploadUrl = await getSignedUrl(s3Client, command, {
expiresIn: 3600, // 1 hour
});
return NextResponse.json({
uploadUrl,
key,
});
} catch (error) {
return NextResponse.json(
{ error: 'Upload failed' },
{ status: 500 }
);
}
}
Video player component với progress tracking:
// VideoPlayer component
'use client';
import { useRef, useEffect } from 'react';
import { useRouter } from 'next/navigation';
interface VideoPlayerProps {
videoUrl: string;
lessonId: string;
lastPosition?: number;
onProgress: (position: number) => void;
onComplete: () => void;
}
export default function VideoPlayer({
videoUrl,
lessonId,
lastPosition = 0,
onProgress,
onComplete,
}: VideoPlayerProps) {
const videoRef = useRef<HTMLVideoElement>(null);
const progressInterval = useRef<NodeJS.Timeout>();
useEffect(() => {
// Resume từ last position
if (videoRef.current && lastPosition > 0) {
videoRef.current.currentTime = lastPosition;
}
// Track progress mỗi 5 giây
progressInterval.current = setInterval(() => {
if (videoRef.current) {
onProgress(videoRef.current.currentTime);
}
}, 5000);
return () => {
if (progressInterval.current) {
clearInterval(progressInterval.current);
}
};
}, [lastPosition, onProgress]);
const handleEnded = () => {
onComplete();
};
return (
<div className="relative w-full aspect-video bg-black rounded-lg overflow-hidden">
<video
ref={videoRef}
src={videoUrl}
controls
className="w-full h-full"
onEnded={handleEnded}
>
Your browser does not support video playback.
</video>
</div>
);
}
Tuần 4: Quiz System
Quiz component với instant feedback:
// Quiz component
'use client';
import { useState } from 'react';
import { useRouter } from 'next/navigation';
interface QuizProps {
questions: Question[];
lessonId: string;
passingScore: number;
}
export default function Quiz({ questions, lessonId, passingScore }: QuizProps) {
const [currentQuestion, setCurrentQuestion] = useState(0);
const [answers, setAnswers] = useState<Record<string, string>>({});
const [showResults, setShowResults] = useState(false);
const router = useRouter();
const handleAnswer = (questionId: string, answerId: string) => {
setAnswers(prev => ({
...prev,
[questionId]: answerId,
}));
};
const handleSubmit = async () => {
// Calculate score
let correct = 0;
questions.forEach(q => {
if (answers[q.id] === q.correctAnswerId) {
correct++;
}
});
const score = (correct / questions.length) * 100;
const passed = score >= passingScore;
// Save attempt
await fetch('/api/quiz/attempt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
lessonId,
answers,
score,
passed,
}),
});
if (passed) {
// Mark lesson as complete
await fetch(`/api/lessons/${lessonId}/complete`, {
method: 'POST',
});
router.push('/dashboard');
} else {
setShowResults(true);
}
};
if (showResults) {
return (
<div className="text-center py-12">
<h2 className="text-2xl font-bold text-red-600 mb-4">
Score too low. Please try again.
</h2>
<button
onClick={() => {
setAnswers({});
setCurrentQuestion(0);
setShowResults(false);
}}
className="bg-blue-600 text-white px-6 py-2 rounded-lg"
>
Retry Quiz
</button>
</div>
);
}
const question = questions[currentQuestion];
return (
<div className="max-w-2xl mx-auto py-8">
<div className="mb-4">
<span className="text-sm text-gray-600">
Question {currentQuestion + 1} of {questions.length}
</span>
<div className="w-full bg-gray-200 h-2 rounded-full mt-2">
<div
className="bg-blue-600 h-2 rounded-full transition-all"
style={{
width: `${((currentQuestion + 1) / questions.length) * 100}%`,
}}
/>
</div>
</div>
<h3 className="text-xl font-semibold mb-6">{question.text}</h3>
<div className="space-y-3">
{question.options.map(option => (
<button
key={option.id}
onClick={() => handleAnswer(question.id, option.id)}
className={`w-full text-left p-4 border-2 rounded-lg transition-all ${
answers[question.id] === option.id
? 'border-blue-600 bg-blue-50'
: 'border-gray-300 hover:border-gray-400'
}`}
>
{option.text}
</button>
))}
</div>
<div className="mt-8 flex justify-between">
<button
onClick={() => setCurrentQuestion(prev => Math.max(0, prev - 1))}
disabled={currentQuestion === 0}
className="px-6 py-2 bg-gray-200 rounded-lg disabled:opacity-50"
>
Previous
</button>
{currentQuestion === questions.length - 1 ? (
<button
onClick={handleSubmit}
className="px-6 py-2 bg-blue-600 text-white rounded-lg"
>
Submit Quiz
</button>
) : (
<button
onClick={() => setCurrentQuestion(prev => prev + 1)}
className="px-6 py-2 bg-blue-600 text-white rounded-lg"
>
Next
</button>
)}
</div>
</div>
);
}
Tuần 5: Certificate Generation
Certificate generation với PDF:
// Certificate generation API
import { NextRequest, NextResponse } from 'next/server';
import PDFDocument from 'pdfkit';
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
export async function POST(req: NextRequest) {
try {
const { userId, courseId, userName, courseName } = await req.json();
// Generate PDF certificate
const doc = new PDFDocument({
size: 'A4',
layout: 'landscape',
});
const chunks: Buffer[] = [];
doc.on('data', chunk => chunks.push(chunk));
// Design certificate
doc.fontSize(48)
.font('Helvetica-Bold')
.text('Certificate of Completion', 50, 100, { align: 'center' });
doc.fontSize(20)
.font('Helvetica')
.text('This is to certify that', 50, 200, { align: 'center' });
doc.fontSize(32)
.font('Helvetica-Bold')
.text(userName, 50, 250, { align: 'center' });
doc.fontSize(20)
.font('Helvetica')
.text('has successfully completed the course', 50, 320, { align: 'center' });
doc.fontSize(28)
.font('Helvetica-Bold')
.text(courseName, 50, 370, { align: 'center' });
doc.fontSize(16)
.font('Helvetica')
.text(`Date: ${new Date().toLocaleDateString()}`, 50, 450, { align: 'center' });
doc.end();
// Wait for PDF generation
await new Promise(resolve => doc.on('end', resolve));
const pdfBuffer = Buffer.concat(chunks);
// Upload to S3
const s3Client = new S3Client({ region: process.env.AWS_REGION! });
const key = `certificates/${userId}-${courseId}-${Date.now()}.pdf`;
await s3Client.send(new PutObjectCommand({
Bucket: process.env.AWS_S3_BUCKET!,
Key: key,
Body: pdfBuffer,
ContentType: 'application/pdf',
}));
const certificateUrl = `https://${process.env.AWS_S3_BUCKET}.s3.${process.env.AWS_REGION}.amazonaws.com/${key}`;
// Save certificate record
// ... database logic ...
return NextResponse.json({ certificateUrl });
} catch (error) {
return NextResponse.json(
{ error: 'Certificate generation failed' },
{ status: 500 }
);
}
}
Phase 3: Admin Dashboard & Analytics (Tuần 6-7)
Admin dashboard với reports:
// Admin dashboard với analytics
'use client';
import { useEffect, useState } from 'react';
import { Chart } from 'react-chartjs-2';
export default function AdminDashboard() {
const [stats, setStats] = useState({
totalUsers: 0,
activeUsers: 0,
completedCourses: 0,
averageCompletionRate: 0,
});
useEffect(() => {
fetch('/api/admin/stats')
.then(res => res.json())
.then(setStats);
}, []);
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-8">Admin Dashboard</h1>
{/* Stats cards */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-8">
<StatsCard
title="Total Users"
value={stats.totalUsers}
icon="👥"
/>
<StatsCard
title="Active Users"
value={stats.activeUsers}
icon="✅"
/>
<StatsCard
title="Completed Courses"
value={stats.completedCourses}
icon="🎓"
/>
<StatsCard
title="Avg Completion Rate"
value={`${stats.averageCompletionRate}%`}
icon="📊"
/>
</div>
{/* Charts and detailed analytics */}
{/* ... more components ... */}
</div>
);
}
Phase 4: Testing & Launch (Tuần 8)
Testing với real users:
- Internal beta testing với 50 employees
- Gather feedback và fix issues
- Performance optimization
- Final deployment
Kết quả
Sau 2 tháng phát triển và 3 tháng vận hành, nền tảng đã mang lại kết quả ấn tượng.
Metrics Chính
| Metric | Target | Achieved | Status | |--------|--------|----------|--------| | Employees Trained | 300+ | 500+ | ✅ +67% | | Completion Rate | 80% | 95% | ✅ +19% | | Training Cost per Employee | - | -60% | ✅ Achieved | | Training Time | - | -40% | ✅ Achieved | | Platform Uptime | 99% | 99.8% | ✅ Exceeded | | User Satisfaction | 4.0/5 | 4.7/5 | ✅ Exceeded |
Business Impact
-
500+ nhân viên được đào tạo: Trong 3 tháng đầu tiên
- Vượt target ban đầu 67%
- Tất cả departments được coverage
- Training materials standardized
-
95% completion rate:
- Cao hơn nhiều so với traditional training (60-70%)
- Engaging UX/UI giúp retention
- Flexible learning schedule
-
60% cost savings:
- Không cần venue rental
- Không cần trainer costs
- Không cần travel expenses
- Content reusable nhiều lần
-
40% reduction in training time:
- Self-paced learning
- On-demand access
- No scheduling conflicts
- Watch on any device
-
Automated reporting:
- Real-time analytics
- Export reports easily
- Track individual progress
- Identify struggling employees
Phản hồi từ Khách hàng
"Platform này đã transform cách chúng tôi đào tạo nhân viên. Từ manual, time-consuming process, giờ chúng tôi có automated system với complete tracking. ROI vượt kỳ vọng nhiều."
— HR Director, Enterprise Corp
"Employees yêu thích flexibility của nền tảng. Họ có thể học vào thời gian rảnh, trên bất kỳ thiết bị nào. Completion rate 95% là minh chứng rõ ràng nhất."
— Learning & Development Manager, Enterprise Corp
Bài học Kinh nghiệm
Technical Learnings:
-
Video optimization is critical:
- Multiple quality levels cải thiện experience
- Adaptive streaming giảm buffering
- CDN distribution là must-have
-
Progress tracking phải real-time:
- Users appreciate seeing progress
- Gamification increases engagement
- Instant feedback motivates completion
-
Mobile-first design:
- 60% traffic từ mobile devices
- Responsive design is not optional
- PWA capabilities add value
Process Learnings:
-
User testing early:
- Beta testing reveal critical UX issues
- Real user feedback invaluable
- Iterate based on data
-
Keep it simple:
- Simple UI increases adoption
- Don't over-engineer
- Focus on core features first
-
Automated reporting saves time:
- HR appreciates easy exports
- Real-time data helps decisions
- Visual analytics tell story better
Kế hoạch Phát triển
Phase 5: Advanced Features (Q4 2024)
- Discussion forums
- Peer-to-peer learning
- Live webinar integration
- Advanced analytics
Phase 6: Mobile Apps (Q1 2025)
- Native iOS và Android apps
- Offline viewing capability
- Push notifications
- Better mobile experience
Phase 7: AI Integration (Q2 2025)
- AI-powered course recommendations
- Automatic quiz generation
- Personalized learning paths
- Chatbot support
Công nghệ Sử dụng
Frontend
- Framework: Next.js 15.5.4 (App Router)
- Language: TypeScript 5.9
- Styling: Tailwind CSS 4.1
- UI Library: React 19.0
- State Management: Zustand
Backend
- Runtime: Node.js 20+
- API: Next.js API Routes
- Authentication: NextAuth.js với Azure AD SSO
- Video Processing: FFmpeg
Database & Storage
- Database: PostgreSQL 15 với Prisma ORM
- Video Storage: AWS S3
- CDN: AWS CloudFront
- File Storage: AWS S3
Integrations
- SSO: Azure Active Directory
- Email: Resend API
- Analytics: Custom analytics dashboard
- PDF Generation: PDFKit
DevOps
- Hosting: Vercel
- Database Hosting: AWS RDS
- CI/CD: GitHub Actions
- Monitoring: Sentry + Vercel Analytics
Testing
- Unit Tests: Jest
- E2E Tests: Playwright
- Code Quality: ESLint + Prettier
Quan tâm đến nền tảng training tương tự? Liên hệ với chúng tôi để thảo luận cách chúng tôi có thể giúp tổ chức của bạn.
Quan tâm đến dự án tương tự?
Hãy liên hệ với tôi để trao đổi về dự án của bạn. Tôi sẵn sàng giúp bạn biến ý tưởng thành hiện thực.
Liên Hệ Ngay