Guest User

Untitled

a guest
Apr 1st, 2025
63
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 6.43 KB | None | 0 0
  1. # Laravel 课时消耗系统设计
  2.  
  3. 针对学生课时消耗的需求,我建议采用以下设计方案:
  4.  
  5. ## 核心设计思路
  6.  
  7. 1. **订单与课时消耗解耦**:通过中间表记录课时消耗情况
  8. 2. **消耗顺序**:先消耗购买课时,再消耗赠送课时
  9. 3. **撤销功能**:记录每次消耗的日志,便于撤销
  10. 4. **多订单处理**:按订单创建时间顺序处理
  11.  
  12. ## 数据库设计
  13.  
  14. ```php
  15. // 订单表
  16. Schema::create('orders', function (Blueprint $table) {
  17. $table->id();
  18. $table->unsignedBigInteger('student_id');
  19. $table->integer('paid_lessons'); // 购买课时
  20. $table->integer('free_lessons'); // 赠送课时
  21. $table->integer('remaining_paid_lessons'); // 剩余购买课时
  22. $table->integer('remaining_free_lessons'); // 剩余赠送课时
  23. $table->timestamps();
  24.  
  25. $table->foreign('student_id')->references('id')->on('students');
  26. });
  27.  
  28. // 课时消耗记录表
  29. Schema::create('lesson_consumptions', function (Blueprint $table) {
  30. $table->id();
  31. $table->unsignedBigInteger('student_id');
  32. $table->unsignedBigInteger('order_id')->nullable();
  33. $table->integer('amount'); // 消耗数量
  34. $table->enum('type', ['paid', 'free']); // 消耗类型
  35. $table->string('description'); // 消耗描述
  36. $table->boolean('is_reversed')->default(false); // 是否已撤销
  37. $table->timestamps();
  38.  
  39. $table->foreign('student_id')->references('id')->on('students');
  40. $table->foreign('order_id')->references('id')->on('orders');
  41. });
  42. ```
  43.  
  44. ## 服务类实现
  45.  
  46. ```php
  47. namespace App\Services;
  48.  
  49. use App\Models\Order;
  50. use App\Models\LessonConsumption;
  51. use Illuminate\Support\Facades\DB;
  52.  
  53. class LessonConsumptionService
  54. {
  55. /**
  56. * 消耗课时
  57. */
  58. public function consumeLessons($studentId, $amount, $description)
  59. {
  60. return DB::transaction(function () use ($studentId, $amount, $description) {
  61. $remainingAmount = $amount;
  62. $orders = Order::where('student_id', $studentId)
  63. ->where(function ($query) {
  64. $query->where('remaining_paid_lessons', '>', 0)
  65. ->orWhere('remaining_free_lessons', '>', 0);
  66. })
  67. ->orderBy('created_at')
  68. ->lockForUpdate()
  69. ->get();
  70.  
  71. $consumptions = [];
  72.  
  73. foreach ($orders as $order) {
  74. if ($remainingAmount <= 0) break;
  75.  
  76. // 先消耗购买课时
  77. if ($order->remaining_paid_lessons > 0) {
  78. $consumed = min($remainingAmount, $order->remaining_paid_lessons);
  79.  
  80. $consumptions[] = $this->createConsumption(
  81. $studentId,
  82. $order->id,
  83. $consumed,
  84. 'paid',
  85. $description
  86. );
  87.  
  88. $order->decrement('remaining_paid_lessons', $consumed);
  89. $remainingAmount -= $consumed;
  90. }
  91.  
  92. // 再消耗赠送课时
  93. if ($remainingAmount > 0 && $order->remaining_free_lessons > 0) {
  94. $consumed = min($remainingAmount, $order->remaining_free_lessons);
  95.  
  96. $consumptions[] = $this->createConsumption(
  97. $studentId,
  98. $order->id,
  99. $consumed,
  100. 'free',
  101. $description
  102. );
  103.  
  104. $order->decrement('remaining_free_lessons', $consumed);
  105. $remainingAmount -= $consumed;
  106. }
  107. }
  108.  
  109. if ($remainingAmount > 0) {
  110. throw new \Exception("课时不足,还缺少 {$remainingAmount} 课时");
  111. }
  112.  
  113. return $consumptions;
  114. });
  115. }
  116.  
  117. /**
  118. * 创建消耗记录
  119. */
  120. protected function createConsumption($studentId, $orderId, $amount, $type, $description)
  121. {
  122. return LessonConsumption::create([
  123. 'student_id' => $studentId,
  124. 'order_id' => $orderId,
  125. 'amount' => $amount,
  126. 'type' => $type,
  127. 'description' => $description,
  128. ]);
  129. }
  130.  
  131. /**
  132. * 撤销课时消耗
  133. */
  134. public function reverseConsumption($consumptionId)
  135. {
  136. return DB::transaction(function () use ($consumptionId) {
  137. $consumption = LessonConsumption::lockForUpdate()->findOrFail($consumptionId);
  138.  
  139. if ($consumption->is_reversed) {
  140. throw new \Exception('该消耗记录已撤销');
  141. }
  142.  
  143. // 恢复订单中的课时
  144. if ($consumption->order_id) {
  145. $order = Order::lockForUpdate()->find($consumption->order_id);
  146.  
  147. if ($consumption->type === 'paid') {
  148. $order->increment('remaining_paid_lessons', $consumption->amount);
  149. } else {
  150. $order->increment('remaining_free_lessons', $consumption->amount);
  151. }
  152. }
  153.  
  154. // 标记为已撤销
  155. $consumption->update(['is_reversed' => true]);
  156.  
  157. return $consumption;
  158. });
  159. }
  160. }
  161. ```
  162.  
  163. ## 使用示例
  164.  
  165. ```php
  166. // 消耗课时
  167. $service = new LessonConsumptionService();
  168. try {
  169. $consumptions = $service->consumeLessons($studentId, 3, '常规课程消耗');
  170. // 处理成功
  171. } catch (\Exception $e) {
  172. // 处理失败
  173. }
  174.  
  175. // 撤销消耗
  176. try {
  177. $reversed = $service->reverseConsumption($consumptionId);
  178. // 撤销成功
  179. } catch (\Exception $e) {
  180. // 撤销失败
  181. }
  182. ```
  183.  
  184. ## 设计优势
  185.  
  186. 1. **解耦设计**:课时消耗记录与订单分离,便于管理和查询
  187. 2. **事务处理**:使用数据库事务确保数据一致性
  188. 3. **撤销功能**:通过记录消耗日志实现撤销
  189. 4. **消耗顺序**:严格按照先购买后赠送的顺序消耗
  190. 5. **并发安全**:使用锁机制防止并发问题
  191.  
  192. ## 扩展考虑
  193.  
  194. 1. 可以添加课时消耗事件,便于后续业务扩展
  195. 2. 可以添加课时余额快照功能,便于历史查询
  196. 3. 可以添加课时有效期管理,自动过期处理
  197.  
  198. 这个设计提供了灵活且可靠的课时管理系统,满足您的需求同时保持了良好的扩展性。
Add Comment
Please, Sign In to add comment