引言
微课堂(MicroClass)是一个广泛使用的在线教育平台,其1.4.9版本是一个稳定且功能丰富的版本。本文将深入解析该版本的源码结构,并提供实战应用指南,帮助开发者理解其核心机制并进行二次开发或定制化部署。
1. 源码结构解析
1.1 项目目录概览
微课堂1.4.9版本的源码通常包含以下主要目录:
microclass/
├── app/ # 应用核心代码
│ ├── controllers/ # 控制器层
│ ├── models/ # 数据模型层
│ ├── views/ # 视图层
│ └── services/ # 业务逻辑层
├── config/ # 配置文件
├── public/ # 静态资源(CSS、JS、图片)
├── database/ # 数据库迁移和种子文件
├── tests/ # 测试用例
├── vendor/ # 依赖库(Composer)
├── .env # 环境配置文件
├── composer.json # PHP依赖管理
└── package.json # 前端依赖管理(如果使用Node.js)
1.2 核心文件详解
1.2.1 控制器(Controllers)
控制器处理用户请求,调用模型和视图。例如,CourseController.php 负责课程相关操作:
<?php
// app/controllers/CourseController.php
namespace App\Controllers;
use App\Models\Course;
use App\Services\CourseService;
class CourseController extends BaseController
{
public function show($id)
{
// 从模型获取课程数据
$course = Course::find($id);
// 使用服务层处理业务逻辑
$courseService = new CourseService();
$courseData = $courseService->formatCourseData($course);
// 返回视图
return view('courses.show', ['course' => $courseData]);
}
public function create()
{
// 验证用户权限
if (!auth()->user()->can('create_course')) {
return redirect()->back()->with('error', '无权限创建课程');
}
// 处理表单提交
if (request()->isMethod('post')) {
$data = request()->all();
// 验证数据
$validated = $this->validate($data, [
'title' => 'required|string|max:255',
'description' => 'required|string',
'price' => 'numeric|min:0'
]);
// 创建课程
$course = Course::create($validated);
// 重定向到课程详情页
return redirect()->route('courses.show', $course->id)
->with('success', '课程创建成功');
}
return view('courses.create');
}
}
1.2.2 数据模型(Models)
模型定义数据结构和业务规则。例如,Course.php 模型:
<?php
// app/models/Course.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Course extends Model
{
use SoftDeletes; // 软删除
// 可批量赋值的字段
protected $fillable = [
'title', 'description', 'price', 'user_id', 'status'
];
// 日期字段
protected $dates = ['deleted_at'];
// 关联用户(创建者)
public function user()
{
return $this->belongsTo(User::class);
}
// 关联课程章节
public function chapters()
{
return $this->hasMany(Chapter::class)->orderBy('order');
}
// 关联课程评论
public function comments()
{
return $this->hasMany(Comment::class);
}
// 获取已购买该课程的用户
public function enrolledUsers()
{
return $this->belongsToMany(User::class, 'enrollments')
->withPivot('enrolled_at', 'expires_at');
}
// 作用域:获取已发布的课程
public function scopePublished($query)
{
return $query->where('status', 'published');
}
// 访问器:获取格式化价格
public function getFormattedPriceAttribute()
{
return number_format($this->price, 2) . '元';
}
// 修改器:设置价格时自动保留两位小数
public function setPriceAttribute($value)
{
$this->attributes['price'] = round($value, 2);
}
}
1.2.3 服务层(Services)
服务层封装复杂业务逻辑,使控制器保持简洁。例如,CourseService.php:
<?php
// app/services/CourseService.php
namespace App\Services;
use App\Models\Course;
use App\Models\Enrollment;
use App\Models\User;
class CourseService
{
/**
* 格式化课程数据,用于前端展示
*/
public function formatCourseData(Course $course)
{
return [
'id' => $course->id,
'title' => $course->title,
'description' => $course->description,
'price' => $course->formatted_price,
'instructor' => $course->user->name,
'chapter_count' => $course->chapters->count(),
'enrollment_count' => $course->enrolledUsers->count(),
'average_rating' => $this->calculateAverageRating($course),
'is_enrolled' => $this->isUserEnrolled(auth()->id(), $course->id)
];
}
/**
* 计算课程平均评分
*/
private function calculateAverageRating(Course $course)
{
$ratings = $course->comments->pluck('rating');
if ($ratings->isEmpty()) {
return 0;
}
return round($ratings->sum() / $ratings->count(), 1);
}
/**
* 检查用户是否已报名课程
*/
public function isUserEnrolled($userId, $courseId)
{
return Enrollment::where('user_id', $userId)
->where('course_id', $courseId)
->where('expires_at', '>', now())
->exists();
}
/**
* 创建课程并处理相关逻辑
*/
public function createCourse(array $data, $userId)
{
// 开启数据库事务
DB::beginTransaction();
try {
// 创建课程
$course = Course::create([
'title' => $data['title'],
'description' => $data['description'],
'price' => $data['price'],
'user_id' => $userId,
'status' => 'draft'
]);
// 如果有章节数据,创建章节
if (isset($data['chapters']) && is_array($data['chapters'])) {
foreach ($data['chapters'] as $chapterData) {
$course->chapters()->create([
'title' => $chapterData['title'],
'content' => $chapterData['content'],
'order' => $chapterData['order'] ?? 0
]);
}
}
// 提交事务
DB::commit();
return $course;
} catch (\Exception $e) {
// 回滚事务
DB::rollBack();
throw $e;
}
}
}
1.3 数据库设计
微课堂1.4.9版本使用MySQL数据库,主要表结构如下:
1.3.1 课程表(courses)
CREATE TABLE `courses` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL COMMENT '创建者ID',
`title` varchar(255) NOT NULL COMMENT '课程标题',
`description` text COMMENT '课程描述',
`price` decimal(8,2) NOT NULL DEFAULT '0.00' COMMENT '价格',
`status` enum('draft','published','archived') NOT NULL DEFAULT 'draft' COMMENT '状态',
`cover_image` varchar(255) DEFAULT NULL COMMENT '封面图',
`difficulty` enum('beginner','intermediate','advanced') DEFAULT 'beginner' COMMENT '难度',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`deleted_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `courses_user_id_index` (`user_id`),
KEY `courses_status_index` (`status`),
FULLTEXT KEY `courses_title_description_fulltext` (`title`,`description`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1.3.2 章节表(chapters)
CREATE TABLE `chapters` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`course_id` bigint(20) unsigned NOT NULL COMMENT '课程ID',
`title` varchar(255) NOT NULL COMMENT '章节标题',
`content` text COMMENT '章节内容',
`order` int(11) NOT NULL DEFAULT '0' COMMENT '排序',
`video_url` varchar(255) DEFAULT NULL COMMENT '视频链接',
`duration` int(11) DEFAULT NULL COMMENT '时长(秒)',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `chapters_course_id_index` (`course_id`),
KEY `chapters_order_index` (`order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
1.3.3 报名表(enrollments)
CREATE TABLE `enrollments` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
`course_id` bigint(20) unsigned NOT NULL COMMENT '课程ID',
`enrolled_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '报名时间',
`expires_at` timestamp NULL DEFAULT NULL COMMENT '过期时间',
`payment_status` enum('pending','paid','failed') NOT NULL DEFAULT 'pending' COMMENT '支付状态',
`amount` decimal(8,2) NOT NULL DEFAULT '0.00' COMMENT '支付金额',
`created_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `enrollments_user_course_unique` (`user_id`,`course_id`),
KEY `enrollments_user_id_index` (`user_id`),
KEY `enrollments_course_id_index` (`course_id`),
KEY `enrollments_expires_at_index` (`expires_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
2. 核心功能实现解析
2.1 用户认证系统
微课堂使用JWT(JSON Web Token)进行API认证,以下是认证流程的详细实现:
2.1.1 JWT认证中间件
<?php
// app/Http/Middleware/JwtAuth.php
namespace App\Http\Middleware;
use Closure;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use App\Models\User;
class JwtAuth
{
public function handle($request, Closure $next)
{
// 从请求头获取token
$token = $request->header('Authorization');
if (!$token) {
return response()->json(['error' => '未提供认证令牌'], 401);
}
// 移除"Bearer "前缀
$token = str_replace('Bearer ', '', $token);
try {
// 解码JWT
$decoded = JWT::decode($token, new Key(config('app.jwt_secret'), 'HS256'));
// 查找用户
$user = User::find($decoded->sub);
if (!$user) {
return response()->json(['error' => '用户不存在'], 401);
}
// 将用户信息附加到请求
$request->auth = $user;
} catch (\Exception $e) {
return response()->json(['error' => '无效的令牌'], 401);
}
return $next($request);
}
}
2.1.2 登录控制器
<?php
// app/controllers/AuthController.php
namespace App\Controllers;
use App\Models\User;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator;
class AuthController extends BaseController
{
public function login(Request $request)
{
// 验证请求数据
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'password' => 'required|string'
]);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
// 查找用户
$user = User::where('email', $request->email)->first();
if (!$user || !Hash::check($request->password, $user->password)) {
return response()->json(['error' => '邮箱或密码错误'], 401);
}
// 生成JWT令牌
$payload = [
'sub' => $user->id,
'iat' => time(),
'exp' => time() + (60 * 60 * 24) // 24小时过期
];
$token = JWT::encode($payload, config('app.jwt_secret'), 'HS256');
// 返回令牌和用户信息
return response()->json([
'token' => $token,
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => $user->role
]
]);
}
public function register(Request $request)
{
$validator = Validator::make($request->all(), [
'name' => 'required|string|max:255',
'email' => 'required|email|unique:users',
'password' => 'required|string|min:8|confirmed'
]);
if ($validator->fails()) {
return response()->json(['errors' => $validator->errors()], 422);
}
// 创建用户
$user = User::create([
'name' => $request->name,
'email' => $request->email,
'password' => Hash::make($request->password),
'role' => 'student' // 默认角色
]);
// 生成JWT令牌
$payload = [
'sub' => $user->id,
'iat' => time(),
'exp' => time() + (60 * 60 * 24)
];
$token = JWT::encode($payload, config('app.jwt_secret'), 'HS256');
return response()->json([
'token' => $token,
'user' => [
'id' => $user->id,
'name' => $user->name,
'email' => $user->email,
'role' => $user->role
]
], 201);
}
}
2.2 课程支付系统
微课堂支持多种支付方式,以下是支付宝支付集成的示例:
2.2.1 支付宝支付控制器
<?php
// app/controllers/PaymentController.php
namespace App\Controllers;
use App\Models\Course;
use App\Models\Enrollment;
use App\Services\PaymentService;
use Illuminate\Http\Request;
class PaymentController extends BaseController
{
public function createPayment(Request $request, $courseId)
{
// 验证用户是否已报名
$course = Course::findOrFail($courseId);
$userId = $request->auth->id;
if ($this->isUserEnrolled($userId, $courseId)) {
return response()->json(['error' => '您已报名该课程'], 400);
}
// 创建报名记录(待支付状态)
$enrollment = Enrollment::create([
'user_id' => $userId,
'course_id' => $courseId,
'payment_status' => 'pending',
'amount' => $course->price
]);
// 调用支付服务生成支付链接
$paymentService = new PaymentService();
$paymentResult = $paymentService->createAlipayOrder($enrollment);
return response()->json([
'enrollment_id' => $enrollment->id,
'payment_url' => $paymentResult['url'],
'order_no' => $paymentResult['order_no']
]);
}
public function paymentCallback(Request $request)
{
// 支付宝回调处理
$paymentService = new PaymentService();
$result = $paymentService->handleAlipayCallback($request);
if ($result['success']) {
// 更新报名状态
$enrollment = Enrollment::find($result['enrollment_id']);
$enrollment->update([
'payment_status' => 'paid',
'expires_at' => now()->addYear() // 一年有效期
]);
// 发送通知
$this->sendEnrollmentNotification($enrollment);
return response()->json(['status' => 'success']);
}
return response()->json(['status' => 'failed'], 400);
}
}
2.2.2 支付宝支付服务
<?php
// app/services/PaymentService.php
namespace App\Services;
use App\Models\Enrollment;
use Illuminate\Support\Facades\Http;
class PaymentService
{
private $alipayConfig;
public function __construct()
{
$this->alipayConfig = [
'app_id' => config('alipay.app_id'),
'merchant_private_key' => config('alipay.merchant_private_key'),
'alipay_public_key' => config('alipay.alipay_public_key'),
'gateway' => 'https://openapi.alipay.com/gateway.do',
'notify_url' => url('/api/payments/alipay/callback'),
'return_url' => url('/api/payments/alipay/return')
];
}
/**
* 创建支付宝订单
*/
public function createAlipayOrder(Enrollment $enrollment)
{
// 构建请求参数
$bizContent = [
'out_trade_no' => 'MC' . time() . $enrollment->id,
'product_code' => 'FAST_INSTANT_TRADE_PAY',
'total_amount' => $enrollment->amount,
'subject' => '微课堂课程购买:' . $enrollment->course->title,
'body' => '课程ID:' . $enrollment->course_id,
'timeout_express' => '30m'
];
// 生成签名
$params = [
'app_id' => $this->alipayConfig['app_id'],
'method' => 'alipay.trade.page.pay',
'charset' => 'utf-8',
'sign_type' => 'RSA2',
'timestamp' => date('Y-m-d H:i:s'),
'version' => '1.0',
'biz_content' => json_encode($bizContent),
'notify_url' => $this->alipayConfig['notify_url'],
'return_url' => $this->alipayConfig['return_url']
];
// 签名(简化示例,实际应使用SDK)
$sign = $this->generateSign($params);
$params['sign'] = $sign;
// 构建支付URL
$url = $this->alipayConfig['gateway'] . '?' . http_build_query($params);
return [
'url' => $url,
'order_no' => $bizContent['out_trade_no']
];
}
/**
* 处理支付宝回调
*/
public function handleAlipayCallback($request)
{
// 验证签名
if (!$this->verifySign($request->all())) {
return ['success' => false, 'error' => '签名验证失败'];
}
// 解析回调数据
$tradeStatus = $request->get('trade_status');
$outTradeNo = $request->get('out_trade_no');
// 提取报名ID(从订单号中)
$enrollmentId = substr($outTradeNo, -10);
if ($tradeStatus === 'TRADE_SUCCESS' || $tradeStatus === 'TRADE_FINISHED') {
return [
'success' => true,
'enrollment_id' => $enrollmentId,
'trade_no' => $request->get('trade_no'),
'amount' => $request->get('total_amount')
];
}
return ['success' => false, 'error' => '支付状态异常'];
}
/**
* 生成签名(简化版,实际应使用支付宝SDK)
*/
private function generateSign($params)
{
// 按参数名排序
ksort($params);
// 拼接字符串
$stringToSign = '';
foreach ($params as $key => $value) {
if ($key !== 'sign') {
$stringToSign .= $key . '=' . $value . '&';
}
}
$stringToSign = rtrim($stringToSign, '&');
// 使用私钥签名
$privateKey = $this->alipayConfig['merchant_private_key'];
openssl_sign($stringToSign, $sign, $privateKey, OPENSSL_ALGO_SHA256);
return base64_encode($sign);
}
/**
* 验证签名
*/
private function verifySign($params)
{
// 提取签名
$sign = $params['sign'];
unset($params['sign']);
// 按参数名排序
ksort($params);
// 拼接字符串
$stringToSign = '';
foreach ($params as $key => $value) {
$stringToSign .= $key . '=' . $value . '&';
}
$stringToSign = rtrim($stringToSign, '&');
// 使用支付宝公钥验证
$publicKey = $this->alipayConfig['alipay_public_key'];
$signDecoded = base64_decode($sign);
return openssl_verify($stringToSign, $signDecoded, $publicKey, OPENSSL_ALGO_SHA256) === 1;
}
}
2.3 视频播放与进度跟踪
微课堂支持视频播放和学习进度跟踪,以下是前端和后端的实现:
2.3.1 前端视频播放器(Vue.js示例)
<!-- resources/js/components/VideoPlayer.vue -->
<template>
<div class="video-player-container">
<video
ref="videoPlayer"
:src="videoUrl"
@timeupdate="onTimeUpdate"
@ended="onVideoEnded"
@play="onPlay"
@pause="onPause"
controls
style="width: 100%; max-height: 500px;"
>
您的浏览器不支持视频标签。
</video>
<div class="progress-info">
<div class="progress-bar">
<div
class="progress-fill"
:style="{ width: progressPercentage + '%' }"
></div>
</div>
<div class="progress-text">
已观看:{{ formatTime(currentTime) }} / {{ formatTime(duration) }}
({{ progressPercentage }}%)
</div>
</div>
<div class="controls">
<button @click="togglePlay" class="btn">
{{ isPlaying ? '暂停' : '播放' }}
</button>
<button @click="saveProgress" class="btn btn-primary">
保存进度
</button>
</div>
</div>
</template>
<script>
export default {
props: {
videoUrl: String,
chapterId: Number,
initialProgress: {
type: Number,
default: 0
}
},
data() {
return {
currentTime: 0,
duration: 0,
isPlaying: false,
progressPercentage: 0,
saveInterval: null
};
},
mounted() {
// 初始化视频播放器
this.$nextTick(() => {
const video = this.$refs.videoPlayer;
// 设置初始进度
if (this.initialProgress > 0) {
video.currentTime = this.initialProgress;
}
// 监听视频事件
video.addEventListener('loadedmetadata', () => {
this.duration = video.duration;
});
// 自动保存进度(每30秒)
this.saveInterval = setInterval(() => {
if (this.isPlaying) {
this.saveProgress();
}
}, 30000);
});
},
beforeDestroy() {
// 清理定时器
if (this.saveInterval) {
clearInterval(this.saveInterval);
}
// 退出时保存进度
this.saveProgress();
},
methods: {
onTimeUpdate(event) {
this.currentTime = event.target.currentTime;
this.progressPercentage = Math.round(
(this.currentTime / this.duration) * 100
);
},
onVideoEnded() {
this.isPlaying = false;
this.progressPercentage = 100;
this.saveProgress();
// 视频结束,标记章节完成
this.markChapterCompleted();
},
onPlay() {
this.isPlaying = true;
},
onPause() {
this.isPlaying = false;
},
togglePlay() {
const video = this.$refs.videoPlayer;
if (video.paused) {
video.play();
} else {
video.pause();
}
},
async saveProgress() {
try {
const response = await axios.post('/api/learning/progress', {
chapter_id: this.chapterId,
progress_seconds: this.currentTime,
progress_percentage: this.progressPercentage
});
if (response.data.success) {
console.log('进度已保存');
}
} catch (error) {
console.error('保存进度失败:', error);
}
},
async markChapterCompleted() {
try {
const response = await axios.post('/api/learning/complete', {
chapter_id: this.chapterId
});
if (response.data.success) {
this.$emit('chapter-completed', this.chapterId);
}
} catch (error) {
console.error('标记完成失败:', error);
}
},
formatTime(seconds) {
const mins = Math.floor(seconds / 60);
const secs = Math.floor(seconds % 60);
return `${mins}:${secs.toString().padStart(2, '0')}`;
}
}
};
</script>
<style scoped>
.video-player-container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.progress-info {
margin: 15px 0;
}
.progress-bar {
height: 8px;
background: #e9ecef;
border-radius: 4px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #4CAF50, #45a049);
transition: width 0.3s ease;
}
.progress-text {
margin-top: 5px;
font-size: 14px;
color: #666;
}
.controls {
display: flex;
gap: 10px;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
background: #6c757d;
color: white;
}
.btn-primary {
background: #007bff;
}
</style>
2.3.2 后端进度跟踪API
<?php
// app/controllers/LearningController.php
namespace App\Controllers;
use App\Models\ChapterProgress;
use App\Models\Chapter;
use Illuminate\Http\Request;
class LearningController extends BaseController
{
/**
* 保存学习进度
*/
public function saveProgress(Request $request)
{
$validated = $request->validate([
'chapter_id' => 'required|exists:chapters,id',
'progress_seconds' => 'required|numeric|min:0',
'progress_percentage' => 'required|numeric|min:0|max:100'
]);
$userId = $request->auth->id;
// 查找或创建进度记录
$progress = ChapterProgress::updateOrCreate(
[
'user_id' => $userId,
'chapter_id' => $validated['chapter_id']
],
[
'progress_seconds' => $validated['progress_seconds'],
'progress_percentage' => $validated['progress_percentage'],
'last_viewed_at' => now()
]
);
// 如果进度达到100%,标记为已完成
if ($validated['progress_percentage'] >= 100) {
$progress->update(['is_completed' => true]);
}
return response()->json([
'success' => true,
'progress' => $progress
]);
}
/**
* 标记章节完成
*/
public function markCompleted(Request $request)
{
$validated = $request->validate([
'chapter_id' => 'required|exists:chapters,id'
]);
$userId = $request->auth->id;
// 更新进度记录
ChapterProgress::updateOrCreate(
[
'user_id' => $userId,
'chapter_id' => $validated['chapter_id']
],
[
'is_completed' => true,
'completed_at' => now(),
'progress_percentage' => 100,
'progress_seconds' => Chapter::find($validated['chapter_id'])->duration ?? 0
]
);
// 检查课程是否全部完成
$this->checkCourseCompletion($userId, $validated['chapter_id']);
return response()->json(['success' => true]);
}
/**
* 获取章节进度
*/
public function getChapterProgress($chapterId)
{
$userId = request()->auth->id;
$progress = ChapterProgress::where('user_id', $userId)
->where('chapter_id', $chapterId)
->first();
return response()->json([
'progress' => $progress ? [
'progress_seconds' => $progress->progress_seconds,
'progress_percentage' => $progress->progress_percentage,
'is_completed' => $progress->is_completed,
'last_viewed_at' => $progress->last_viewed_at
] : null
]);
}
/**
* 检查课程是否全部完成
*/
private function checkCourseCompletion($userId, $chapterId)
{
$chapter = Chapter::with('course')->find($chapterId);
$course = $chapter->course;
// 获取课程所有章节
$allChapters = $course->chapters;
// 获取用户已完成的章节
$completedChapters = ChapterProgress::where('user_id', $userId)
->where('chapter_id', $allChapters->pluck('id'))
->where('is_completed', true)
->pluck('chapter_id');
// 如果所有章节都已完成
if ($completedChapters->count() === $allChapters->count()) {
// 标记课程完成
$this->markCourseCompleted($userId, $course->id);
}
}
/**
* 标记课程完成
*/
private function markCourseCompleted($userId, $courseId)
{
// 更新报名记录
\DB::table('enrollments')
->where('user_id', $userId)
->where('course_id', $courseId)
->update(['completed_at' => now()]);
// 发送完成通知
$this->sendCourseCompletionNotification($userId, $courseId);
}
}
3. 实战应用指南
3.1 本地开发环境搭建
3.1.1 环境要求
- PHP 7.4+ (推荐8.0+)
- MySQL 5.7+ 或 MariaDB 10.2+
- Composer
- Node.js 14+ (用于前端构建)
- Nginx/Apache
3.1.2 安装步骤
# 1. 克隆代码仓库
git clone https://github.com/microclass/microclass-1.4.9.git
cd microclass-1.4.9
# 2. 安装PHP依赖
composer install --no-dev
# 3. 复制环境配置文件
cp .env.example .env
# 4. 生成应用密钥
php artisan key:generate
# 5. 配置数据库
# 编辑 .env 文件,设置数据库连接信息
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=microclass
# DB_USERNAME=root
# DB_PASSWORD=
# 6. 运行数据库迁移
php artisan migrate
# 7. 运行数据库种子(可选,用于测试数据)
php artisan db:seed
# 8. 安装前端依赖
npm install
# 9. 构建前端资源
npm run production
# 10. 启动开发服务器
php artisan serve --host=0.0.0.0 --port=8000
3.1.3 配置文件示例
.env 文件配置:
# 应用配置
APP_NAME=微课堂
APP_ENV=local
APP_KEY=base64:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
APP_DEBUG=true
APP_URL=http://localhost:8000
# 数据库配置
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=microclass
DB_USERNAME=root
DB_PASSWORD=
# JWT配置
JWT_SECRET=your_jwt_secret_key_here
# 支付宝配置
ALIPAY_APP_ID=your_app_id
ALIPAY_MERCHANT_PRIVATE_KEY=your_private_key
ALIPAY_PUBLIC_KEY=your_public_key
# 文件存储
FILESYSTEM_DISK=local
UPLOAD_MAX_SIZE=10485760 # 10MB
# 邮件配置(用于通知)
MAIL_MAILER=smtp
MAIL_HOST=smtp.gmail.com
MAIL_PORT=587
MAIL_USERNAME=your_email@gmail.com
MAIL_PASSWORD=your_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS=notify@microclass.com
MAIL_FROM_NAME="${APP_NAME}"
3.2 二次开发指南
3.2.1 添加新功能模块
假设需要添加一个“直播课程”功能:
- 创建模型:
// app/models/LiveCourse.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class LiveCourse extends Model
{
protected $fillable = [
'course_id', 'live_url', 'start_time', 'end_time',
'max_participants', 'current_participants'
];
public function course()
{
return $this->belongsTo(Course::class);
}
public function participants()
{
return $this->belongsToMany(User::class, 'live_course_participants')
->withPivot('joined_at');
}
}
- 创建控制器:
// app/controllers/LiveCourseController.php
namespace App\Controllers;
use App\Models\LiveCourse;
use Illuminate\Http\Request;
class LiveCourseController extends BaseController
{
public function create(Request $request)
{
// 验证权限
if (!auth()->user()->can('create_live_course')) {
return response()->json(['error' => '无权限'], 403);
}
// 验证数据
$validated = $request->validate([
'course_id' => 'required|exists:courses,id',
'live_url' => 'required|url',
'start_time' => 'required|date',
'end_time' => 'required|date|after:start_time',
'max_participants' => 'required|integer|min:1'
]);
// 创建直播课程
$liveCourse = LiveCourse::create($validated);
return response()->json([
'success' => true,
'live_course' => $liveCourse
]);
}
public function join($liveCourseId)
{
$userId = auth()->id();
$liveCourse = LiveCourse::findOrFail($liveCourseId);
// 检查是否已报名
if ($liveCourse->participants->contains($userId)) {
return response()->json(['error' => '已报名'], 400);
}
// 检查人数限制
if ($liveCourse->current_participants >= $liveCourse->max_participants) {
return response()->json(['error' => '人数已满'], 400);
}
// 检查时间
if (now() < $liveCourse->start_time || now() > $liveCourse->end_time) {
return response()->json(['error' => '不在直播时间'], 400);
}
// 加入直播
DB::transaction(function () use ($liveCourse, $userId) {
$liveCourse->participants()->attach($userId, [
'joined_at' => now()
]);
$liveCourse->increment('current_participants');
});
return response()->json(['success' => true]);
}
}
- 添加路由:
// routes/api.php
Route::group(['middleware' => ['auth:api']], function () {
Route::post('/live-courses', [LiveCourseController::class, 'create']);
Route::post('/live-courses/{id}/join', [LiveCourseController::class, 'join']);
Route::get('/live-courses/{id}', [LiveCourseController::class, 'show']);
});
3.2.2 扩展用户角色系统
微课堂默认有学生和讲师角色,可以扩展更多角色:
- 修改用户模型:
// app/models/User.php
namespace App\Models;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
// 添加角色常量
const ROLE_STUDENT = 'student';
const ROLE_INSTRUCTOR = 'instructor';
const ROLE_ADMIN = 'admin';
const ROLE_MODERATOR = 'moderator';
protected $fillable = [
'name', 'email', 'password', 'role'
];
// 角色验证方法
public function hasRole($role)
{
return $this->role === $role;
}
public function hasAnyRole($roles)
{
return in_array($this->role, (array)$roles);
}
// 权限检查(简化版,实际可使用权限包)
public function can($permission)
{
// 根据角色定义权限
$permissions = [
self::ROLE_STUDENT => ['enroll_course', 'view_content'],
self::ROLE_INSTRUCTOR => ['create_course', 'edit_course', 'view_analytics'],
self::ROLE_ADMIN => ['manage_users', 'manage_courses', 'view_reports'],
self::ROLE_MODERATOR => ['moderate_comments', 'manage_content']
];
return in_array($permission, $permissions[$this->role] ?? []);
}
}
- 创建权限中间件:
// app/Http/Middleware/CheckPermission.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckPermission
{
public function handle($request, Closure $next, $permission)
{
$user = $request->auth;
if (!$user || !$user->can($permission)) {
return response()->json(['error' => '无权限访问'], 403);
}
return $next($request);
}
}
- 使用权限中间件:
// routes/api.php
Route::group(['middleware' => ['auth:api']], function () {
// 只有讲师可以创建课程
Route::post('/courses', [CourseController::class, 'create'])
->middleware('permission:create_course');
// 只有管理员可以管理用户
Route::get('/users', [UserController::class, 'index'])
->middleware('permission:manage_users');
});
3.3 性能优化建议
3.3.1 数据库优化
- 添加索引:
-- 为常用查询字段添加索引
ALTER TABLE `courses` ADD INDEX `idx_user_status` (`user_id`, `status`);
ALTER TABLE `enrollments` ADD INDEX `idx_user_course` (`user_id`, `course_id`);
ALTER TABLE `chapters` ADD INDEX `idx_course_order` (`course_id`, `order`);
- 使用Eloquent优化查询:
// 避免N+1查询问题
$course = Course::with(['chapters', 'user', 'enrolledUsers'])->find($id);
// 使用select只获取需要的字段
$users = User::select('id', 'name', 'email')->get();
// 使用whereHas进行条件查询
$courses = Course::whereHas('enrolledUsers', function ($query) {
$query->where('user_id', auth()->id());
})->get();
3.3.2 缓存策略
// 使用Redis缓存课程数据
use Illuminate\Support\Facades\Cache;
class CourseService
{
public function getCourseWithCache($courseId)
{
$cacheKey = "course:{$courseId}";
return Cache::remember($cacheKey, 3600, function () use ($courseId) {
return Course::with(['chapters', 'user', 'enrolledUsers'])
->find($courseId);
});
}
public function clearCourseCache($courseId)
{
Cache::forget("course:{$courseId}");
}
}
3.3.3 队列处理
对于耗时操作(如发送邮件、生成报表),使用队列:
// 创建队列任务
php artisan make:job SendEnrollmentNotification
// app/Jobs/SendEnrollmentNotification.php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Queue\SerializesModels;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use App\Models\Enrollment;
class SendEnrollmentNotification implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
public $enrollment;
public function __construct(Enrollment $enrollment)
{
$this->enrollment = $enrollment;
}
public function handle()
{
// 发送邮件通知
Mail::to($this->enrollment->user->email)
->send(new EnrollmentConfirmation($this->enrollment));
// 发送短信通知(如果配置了短信服务)
// $this->sendSmsNotification();
}
}
4. 常见问题与解决方案
4.1 部署问题
4.1.1 权限问题
问题:上传文件失败,提示权限不足。
解决方案:
# 设置存储目录权限
chmod -R 775 storage/
chmod -R 775 bootstrap/cache/
chown -R www-data:www-data storage/ bootstrap/cache/
4.1.2 内存限制
问题:处理大文件或大量数据时内存不足。
解决方案:
// 在php.ini中增加内存限制
memory_limit = 256M
// 或者在代码中临时增加
ini_set('memory_limit', '256M');
// 使用生成器处理大数据集
public function processLargeDataset()
{
$query = User::where('status', 'active')->cursor();
foreach ($query as $user) {
// 处理每个用户
$this->processUser($user);
}
}
4.2 安全问题
4.2.1 SQL注入防护
问题:如何防止SQL注入攻击?
解决方案:
// 错误的做法(直接拼接SQL)
$users = DB::select("SELECT * FROM users WHERE email = '{$email}'");
// 正确的做法(使用参数绑定)
$users = DB::select("SELECT * FROM users WHERE email = ?", [$email]);
// 或者使用Eloquent
$users = User::where('email', $email)->get();
4.2.2 XSS攻击防护
问题:如何防止跨站脚本攻击?
解决方案:
// 输出到HTML时进行转义
echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
// 在Blade模板中自动转义
{{ $userInput }}
// 如果需要输出原始HTML(谨慎使用)
{!! $safeHtml !!}
4.3 性能问题
4.3.1 页面加载慢
问题:课程列表页面加载缓慢。
解决方案:
// 1. 使用分页
$courses = Course::with('user')->paginate(20);
// 2. 延迟加载关联数据
$courses = Course::with(['user' => function ($query) {
$query->select('id', 'name');
}])->paginate(20);
// 3. 使用缓存
$courseList = Cache::remember('course_list_page_' . $page, 300, function () use ($page) {
return Course::with('user')->paginate(20, ['*'], 'page', $page);
});
5. 总结
微课堂1.4.9版本是一个功能完善、架构清晰的在线教育平台。通过本文的源码解析和实战指南,开发者可以:
- 理解核心架构:掌握MVC分层结构、服务层设计、数据库关系
- 实现核心功能:用户认证、课程管理、支付系统、视频播放
- 进行二次开发:添加新功能模块、扩展角色权限系统
- 优化性能:数据库优化、缓存策略、队列处理
- 解决常见问题:部署、安全、性能问题
在实际开发中,建议:
- 遵循PSR编码规范
- 编写单元测试和集成测试
- 使用版本控制(Git)管理代码
- 定期更新依赖库以修复安全漏洞
- 监控系统性能并及时优化
通过深入理解微课堂的源码结构和设计思想,开发者可以构建出更加稳定、高效、可扩展的在线教育平台。
