Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Laravel 课时消耗系统设计
- 针对学生课时消耗的需求,我建议采用以下设计方案:
- ## 核心设计思路
- 1. **订单与课时消耗解耦**:通过中间表记录课时消耗情况
- 2. **消耗顺序**:先消耗购买课时,再消耗赠送课时
- 3. **撤销功能**:记录每次消耗的日志,便于撤销
- 4. **多订单处理**:按订单创建时间顺序处理
- ## 数据库设计
- ```php
- // 订单表
- Schema::create('orders', function (Blueprint $table) {
- $table->id();
- $table->unsignedBigInteger('student_id');
- $table->integer('paid_lessons'); // 购买课时
- $table->integer('free_lessons'); // 赠送课时
- $table->integer('remaining_paid_lessons'); // 剩余购买课时
- $table->integer('remaining_free_lessons'); // 剩余赠送课时
- $table->timestamps();
- $table->foreign('student_id')->references('id')->on('students');
- });
- // 课时消耗记录表
- Schema::create('lesson_consumptions', function (Blueprint $table) {
- $table->id();
- $table->unsignedBigInteger('student_id');
- $table->unsignedBigInteger('order_id')->nullable();
- $table->integer('amount'); // 消耗数量
- $table->enum('type', ['paid', 'free']); // 消耗类型
- $table->string('description'); // 消耗描述
- $table->boolean('is_reversed')->default(false); // 是否已撤销
- $table->timestamps();
- $table->foreign('student_id')->references('id')->on('students');
- $table->foreign('order_id')->references('id')->on('orders');
- });
- ```
- ## 服务类实现
- ```php
- namespace App\Services;
- use App\Models\Order;
- use App\Models\LessonConsumption;
- use Illuminate\Support\Facades\DB;
- class LessonConsumptionService
- {
- /**
- * 消耗课时
- */
- public function consumeLessons($studentId, $amount, $description)
- {
- return DB::transaction(function () use ($studentId, $amount, $description) {
- $remainingAmount = $amount;
- $orders = Order::where('student_id', $studentId)
- ->where(function ($query) {
- $query->where('remaining_paid_lessons', '>', 0)
- ->orWhere('remaining_free_lessons', '>', 0);
- })
- ->orderBy('created_at')
- ->lockForUpdate()
- ->get();
- $consumptions = [];
- foreach ($orders as $order) {
- if ($remainingAmount <= 0) break;
- // 先消耗购买课时
- if ($order->remaining_paid_lessons > 0) {
- $consumed = min($remainingAmount, $order->remaining_paid_lessons);
- $consumptions[] = $this->createConsumption(
- $studentId,
- $order->id,
- $consumed,
- 'paid',
- $description
- );
- $order->decrement('remaining_paid_lessons', $consumed);
- $remainingAmount -= $consumed;
- }
- // 再消耗赠送课时
- if ($remainingAmount > 0 && $order->remaining_free_lessons > 0) {
- $consumed = min($remainingAmount, $order->remaining_free_lessons);
- $consumptions[] = $this->createConsumption(
- $studentId,
- $order->id,
- $consumed,
- 'free',
- $description
- );
- $order->decrement('remaining_free_lessons', $consumed);
- $remainingAmount -= $consumed;
- }
- }
- if ($remainingAmount > 0) {
- throw new \Exception("课时不足,还缺少 {$remainingAmount} 课时");
- }
- return $consumptions;
- });
- }
- /**
- * 创建消耗记录
- */
- protected function createConsumption($studentId, $orderId, $amount, $type, $description)
- {
- return LessonConsumption::create([
- 'student_id' => $studentId,
- 'order_id' => $orderId,
- 'amount' => $amount,
- 'type' => $type,
- 'description' => $description,
- ]);
- }
- /**
- * 撤销课时消耗
- */
- public function reverseConsumption($consumptionId)
- {
- return DB::transaction(function () use ($consumptionId) {
- $consumption = LessonConsumption::lockForUpdate()->findOrFail($consumptionId);
- if ($consumption->is_reversed) {
- throw new \Exception('该消耗记录已撤销');
- }
- // 恢复订单中的课时
- if ($consumption->order_id) {
- $order = Order::lockForUpdate()->find($consumption->order_id);
- if ($consumption->type === 'paid') {
- $order->increment('remaining_paid_lessons', $consumption->amount);
- } else {
- $order->increment('remaining_free_lessons', $consumption->amount);
- }
- }
- // 标记为已撤销
- $consumption->update(['is_reversed' => true]);
- return $consumption;
- });
- }
- }
- ```
- ## 使用示例
- ```php
- // 消耗课时
- $service = new LessonConsumptionService();
- try {
- $consumptions = $service->consumeLessons($studentId, 3, '常规课程消耗');
- // 处理成功
- } catch (\Exception $e) {
- // 处理失败
- }
- // 撤销消耗
- try {
- $reversed = $service->reverseConsumption($consumptionId);
- // 撤销成功
- } catch (\Exception $e) {
- // 撤销失败
- }
- ```
- ## 设计优势
- 1. **解耦设计**:课时消耗记录与订单分离,便于管理和查询
- 2. **事务处理**:使用数据库事务确保数据一致性
- 3. **撤销功能**:通过记录消耗日志实现撤销
- 4. **消耗顺序**:严格按照先购买后赠送的顺序消耗
- 5. **并发安全**:使用锁机制防止并发问题
- ## 扩展考虑
- 1. 可以添加课时消耗事件,便于后续业务扩展
- 2. 可以添加课时余额快照功能,便于历史查询
- 3. 可以添加课时有效期管理,自动过期处理
- 这个设计提供了灵活且可靠的课时管理系统,满足您的需求同时保持了良好的扩展性。
Add Comment
Please, Sign In to add comment