1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-06-28 05:11:39 +08:00

完成同一课程多次购买学习逻辑

This commit is contained in:
xiaochong0302 2020-06-02 18:54:42 +08:00
parent cf5cd3ea51
commit 533b0eaad5
30 changed files with 298 additions and 128 deletions

View File

@ -32,10 +32,7 @@ abstract class Cache extends Component
$content = $this->getContent($id);
/**
* 原始内容为空,设置较短的生存时间,简单防止穿透
*/
$lifetime = $content ? $this->getLifetime() : 5 * 60;
$lifetime = $this->getLifetime();
$this->cache->save($key, $content, $lifetime);

View File

@ -40,11 +40,9 @@ abstract class Counter extends Component
if (!$this->cache->exists($key)) {
$content = $this->getContent($id);
$lifetime = $this->getLifetime();
$this->redis->hMSet($key, $content);
$this->redis->expire($key, $lifetime);
}

33
app/Caches/CourseUser.php Normal file
View File

@ -0,0 +1,33 @@
<?php
namespace App\Caches;
use App\Repos\CourseUser as CourseUserRepo;
class CourseUser extends Cache
{
protected $lifetime = 1 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "course_user:{$id}";
}
public function getContent($id = null)
{
list($courseId, $userId) = explode('_', $id);
$repo = new CourseUserRepo();
$courseUser = $repo->findCourseUser($courseId, $userId);
return $courseUser ?: null;
}
}

View File

@ -2,14 +2,11 @@
namespace App\Console\Tasks;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\CourseUser as CourseUserModel;
use App\Models\Learning as LearningModel;
use App\Models\Order as OrderModel;
use App\Models\Refund as RefundModel;
use App\Models\Task as TaskModel;
use App\Models\Trade as TradeModel;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Order as OrderRepo;
use App\Repos\User as UserRepo;
use App\Services\Smser\Order as OrderSmser;
@ -116,8 +113,6 @@ class OrderTask extends Task
if ($courseUser->create($data) === false) {
throw new \RuntimeException('Create CourseQuery User Failed');
}
$this->handleCourseHistory($data['course_id'], $data['user_id']);
}
protected function handlePackageOrder(OrderModel $order)
@ -140,10 +135,8 @@ class OrderTask extends Task
$courseUser = new CourseUserModel();
if ($courseUser->create($data) === false) {
throw new \RuntimeException('Create CourseQuery User Failed');
throw new \RuntimeException('Create Course User Failed');
}
$this->handleCourseHistory($data['course_id'], $data['user_id']);
}
}
@ -196,57 +189,6 @@ class OrderTask extends Task
$refund->create();
}
protected function handleCourseHistory($courseId, $userId)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseStudent($courseId, $userId);
if ($courseUser) {
$courseUser->update(['deleted' => 1]);
}
$chapterUsers = $this->findPlanChapterUsers($courseId, $userId);
if ($chapterUsers->count() > 0) {
$chapterUsers->update(['deleted' => 1]);
}
$learnings = $this->findPlanLearnings($courseId, $userId);
if ($learnings->count() > 0) {
$learnings->update(['deleted' => 1]);
}
}
/**
* @param int $courseId
* @param int $userId
* @return ResultsetInterface|Resultset|CourseUserModel[]
*/
protected function findPlanChapterUsers($courseId, $userId)
{
return ChapterUserModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->andWhere('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
/**
* @param int $courseId
* @param int $userId
* @return ResultsetInterface|Resultset|CourseUserModel[]
*/
protected function findPlanLearnings($courseId, $userId)
{
return LearningModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->andWhere('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
/**
* @param int $orderId
* @return Model|TradeModel

View File

@ -153,21 +153,34 @@ class SyncLearningTask extends Task
return;
}
$userLearnings = $courseRepo->findConsumedUserLearnings($courseId, $userId);
$userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $courseUser->plan_id);
if ($userLearnings->count() == 0) {
return;
}
/**
* @var array $consumedUserLearnings
*/
$consumedUserLearnings = $userLearnings->filter(function ($item) {
if ($item->consumed == 1) {
return $item;
}
});
if (count($consumedUserLearnings) == 0) {
return;
}
$duration = 0;
foreach ($userLearnings as $learning) {
$duration += $learning->duration;
foreach ($consumedUserLearnings as $learning) {
$duration += $learning['duration'];
}
$courseLessonIds = kg_array_column($courseLessons->toArray(), 'id');
$userLessonIds = kg_array_column($userLearnings->toArray(), 'chapter_id');
$consumedLessonIds = array_intersect($courseLessonIds, $userLessonIds);
$consumedUserLessonIds = kg_array_column($consumedUserLearnings, 'chapter_id');
$consumedLessonIds = array_intersect($courseLessonIds, $consumedUserLessonIds);
$totalCount = count($courseLessonIds);
$consumedCount = count($consumedLessonIds);

View File

@ -10,6 +10,14 @@ use App\Http\Admin\Services\Package as PackageService;
class PackageController extends Controller
{
/**
* @Get("/search", name="admin.package.search")
*/
public function searchAction()
{
}
/**
* @Get("/guiding", name="admin.package.guiding")
*/

View File

@ -105,18 +105,24 @@ class AuthNode extends Service
],
[
'id' => '1-3-2',
'title' => '搜索套餐',
'type' => 'menu',
'route' => 'admin.package.search',
],
[
'id' => '1-3-3',
'title' => '添加套餐',
'type' => 'menu',
'route' => 'admin.package.add',
],
[
'id' => '1-3-3',
'id' => '1-3-4',
'title' => '编辑套餐',
'type' => 'button',
'route' => 'admin.package.edit',
],
[
'id' => '1-3-4',
'id' => '1-3-5',
'title' => '删除套餐',
'type' => 'button',
'route' => 'admin.package.delete',

View File

@ -358,7 +358,7 @@ class Course extends Service
}
}
$newTeacherIds = explode(',', $teacherIds);
$newTeacherIds = $teacherIds ? explode(',', $teacherIds) : [];
$addedTeacherIds = array_diff($newTeacherIds, $originTeacherIds);
if ($addedTeacherIds) {
@ -412,7 +412,7 @@ class Course extends Service
}
}
$newCategoryIds = explode(',', $categoryIds);
$newCategoryIds = $categoryIds ? explode(',', $categoryIds) : [];
$addedCategoryIds = array_diff($newCategoryIds, $originCategoryIds);
if ($addedCategoryIds) {
@ -463,7 +463,7 @@ class Course extends Service
}
}
$newRelatedIds = explode(',', $courseIds);
$newRelatedIds = $courseIds ? explode(',', $courseIds) : [];
$addedRelatedIds = array_diff($newRelatedIds, $originRelatedIds);
$courseRelatedRepo = new CourseRelatedRepo();

View File

@ -2,6 +2,7 @@
namespace App\Http\Admin\Services;
use App\Caches\CoursePackageList as CoursePackageListCache;
use App\Caches\Package as PackageCache;
use App\Caches\PackageCourseList as PackageCourseListCache;
use App\Library\Paginator\Query as PagerQuery;
@ -93,7 +94,7 @@ class Package extends Service
$package->update($data);
$this->updateCourseCount($package);
$this->updatePackageCourseCount($package);
$this->rebuildPackageCache($package);
@ -193,7 +194,7 @@ class Package extends Service
}
}
$newCourseIds = explode(',', $courseIds);
$newCourseIds = $courseIds ? explode(',', $courseIds) : [];
$addedCourseIds = array_diff($newCourseIds, $originCourseIds);
if ($addedCourseIds) {
@ -203,6 +204,8 @@ class Package extends Service
'course_id' => $courseId,
'package_id' => $package->id,
]);
$this->updateCoursePackageCount($courseId);
$this->rebuildCoursePackageCache($courseId);
}
}
@ -212,14 +215,14 @@ class Package extends Service
$coursePackageRepo = new CoursePackageRepo();
foreach ($deletedCourseIds as $courseId) {
$coursePackage = $coursePackageRepo->findCoursePackage($courseId, $package->id);
if ($coursePackage) {
$coursePackage->delete();
}
$coursePackage->delete();
$this->updateCoursePackageCount($courseId);
$this->rebuildCoursePackageCache($courseId);
}
}
}
protected function updateCourseCount(PackageModel $package)
protected function updatePackageCourseCount(PackageModel $package)
{
$packageRepo = new PackageRepo();
@ -230,6 +233,19 @@ class Package extends Service
$package->update();
}
protected function updateCoursePackageCount($courseId)
{
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($courseId);
$packageCount = $courseRepo->countPackages($courseId);
$course->package_count = $packageCount;
$course->update();
}
protected function rebuildPackageCache(PackageModel $package)
{
$cache = new PackageCache();
@ -241,6 +257,13 @@ class Package extends Service
$cache->rebuild($package->id);
}
protected function rebuildCoursePackageCache($courseId)
{
$cache = new CoursePackageListCache();
$cache->rebuild($courseId);
}
protected function findOrFail($id)
{
$validator = new PackageValidator();

View File

@ -5,9 +5,9 @@
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">课程编号</label>
<label class="layui-form-label">编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="id" placeholder="课程编号精确匹配">
<input class="layui-input" type="text" name="id" placeholder="编号精确匹配">
</div>
</div>

View File

@ -0,0 +1,45 @@
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.package.list'}) }}">
<fieldset class="layui-elem-field layui-field-title">
<legend>搜索套餐</legend>
</fieldset>
<div class="layui-form-item">
<label class="layui-form-label">编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="id" placeholder="编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">标题</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="title" placeholder="标题模糊匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">发布</label>
<div class="layui-input-block">
<input type="radio" name="published" value="1" title="是">
<input type="radio" name="published" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">删除</label>
<div class="layui-input-block">
<input type="radio" name="deleted" value="1" title="是">
<input type="radio" name="deleted" value="0" title="否">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>

View File

@ -23,6 +23,9 @@
<div class="cover"><img src="{{ course.cover }}!cover_270" alt="{{ course.title }}"></div>
<div class="title"><a href="{{ course_url }}">{{ course.title }}</a></div>
</div>
{% if loop.first %}
<div class="separator"><i class="layui-icon layui-icon-add-1"></i></div>
{% endif %}
{% endfor %}
</div>
</div>

View File

@ -21,7 +21,7 @@
<div class="layout-main clearfix">
{% set show_tab_packages = 1 %}
{% set show_tab_packages = course.package_count > 0 ? 1 : 0 %}
{% set show_tab_consults = course.consult_count > 0 ? 1 : 0 %}
{% set show_tab_reviews = course.review_count > 0 ? 1 : 0 %}

View File

@ -15,7 +15,7 @@ class ChapterUser extends Model
public $id;
/**
* 计划编号(course_user主键)
* 计划编号
*
* @var int
*/

View File

@ -178,6 +178,13 @@ class Course extends Model
*/
public $lesson_count;
/**
* 套餐数
*
* @var int
*/
public $package_count;
/**
* 评论数
*
@ -349,6 +356,7 @@ class Course extends Model
'rating' => '评价',
'latest' => '最新',
'popular' => '最热',
'free' => '免费',
];
}

View File

@ -27,6 +27,13 @@ class CourseUser extends Model
*/
public $id;
/**
* 计划编号
*
* @var int
*/
public $plan_id;
/**
* 课程编号
*
@ -123,6 +130,8 @@ class CourseUser extends Model
public function beforeCreate()
{
$this->plan_id = (int)date('Y-m-d');
$this->create_time = time();
}

View File

@ -28,7 +28,7 @@ class Learning extends Model
public $request_id;
/**
* 计划编号(course_user主键)
* 计划编号
*
* @var int
*/

View File

@ -45,6 +45,7 @@ class ChapterUser extends Repository
return ChapterUserModel::findFirst([
'conditions' => 'chapter_id = ?1 AND user_id = ?2 AND deleted = 0',
'bind' => [1 => $chapterId, 2 => $userId],
'order' => 'id DESC',
]);
}

View File

@ -60,6 +60,10 @@ class Course extends Repository
$builder->andWhere('level = :level:', ['level' => $where['level']]);
}
if ($sort == 'free') {
$where['free'] = 1;
}
if (isset($where['free'])) {
if ($where['free'] == 1) {
$builder->andWhere('market_price = 0');
@ -215,28 +219,15 @@ class Course extends Repository
/**
* @param int $courseId
* @param int $userId
* @param int $planId
* @return ResultsetInterface|Resultset|ChapterUserModel[]
*/
public function findUserLearnings($courseId, $userId)
public function findUserLearnings($courseId, $userId, $planId)
{
return ChapterUserModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->andWhere('user_id = :user_id:', ['user_id' => $userId])
->andWhere('deleted = 0')
->execute();
}
/**
* @param int $courseId
* @param int $userId
* @return ResultsetInterface|Resultset|ChapterUserModel[]
*/
public function findConsumedUserLearnings($courseId, $userId)
{
return ChapterUserModel::query()
->where('course_id = :course_id:', ['course_id' => $courseId])
->andWhere('user_id = :user_id:', ['user_id' => $userId])
->andWhere('consumed = 1 AND deleted = 0')
->andWhere('plan_id = :plan_id:', ['plan_id' => $planId])
->execute();
}
@ -280,6 +271,14 @@ class Course extends Repository
]);
}
public function countPackages($courseId)
{
return CoursePackageModel::count([
'conditions' => 'course_id = :course_id:',
'bind' => ['course_id' => $courseId],
]);
}
public function countUsers($courseId)
{
return CourseUserModel::count([

View File

@ -21,8 +21,12 @@ class Package extends Repository
$builder->where('1 = 1');
if (!empty($where['user_id'])) {
$builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
if (!empty($where['id'])) {
$builder->andWhere('id = :id:', ['id' => $where['id']]);
}
if (!empty($where['title'])) {
$builder->andWhere('title LIKE :title:', ['title' => "%{$where['title']}%"]);
}
if (isset($where['published'])) {

View File

@ -170,7 +170,7 @@ class ChapterInfo extends FrontendService
protected function handleCourseUser(CourseModel $course, UserModel $user)
{
if (empty($user->id)) return;
if ($user->id == 0) return;
if ($this->joinedCourse) return;
@ -182,7 +182,6 @@ class ChapterInfo extends FrontendService
$courseUser->user_id = $user->id;
$courseUser->source_type = CourseUserModel::SOURCE_FREE;
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->expiry_time = strtotime('+3 years');
$courseUser->create();
@ -191,14 +190,22 @@ class ChapterInfo extends FrontendService
protected function handleChapterUser(ChapterModel $chapter, UserModel $user)
{
if (empty($user->id)) return;
if ($user->id == 0) return;
if ($this->joinedChapter) return;
/**
* 一个课程可能购买学习多次
*/
if ($this->chapterUser && $this->courseUser) {
if ($this->chapterUser->plan_id == $this->courseUser->plan_id) {
return;
}
}
if (!$this->ownedChapter) return;
$chapterUser = new ChapterUserModel();
$chapterUser->plan_id = $this->courseUser->plan_id;
$chapterUser->course_id = $chapter->course_id;
$chapterUser->chapter_id = $chapter->id;
$chapterUser->user_id = $user->id;

View File

@ -3,6 +3,7 @@
namespace App\Services\Frontend;
use App\Models\Chapter as ChapterModel;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\User as UserModel;
use App\Repos\ChapterUser as ChapterUserRepo;
use App\Validators\Chapter as ChapterValidator;
@ -20,6 +21,11 @@ trait ChapterTrait
*/
protected $joinedChapter = false;
/**
* @var ChapterUserModel
*/
protected $chapterUser;
public function checkChapter($id)
{
$validator = new ChapterValidator();
@ -41,6 +47,7 @@ trait ChapterTrait
$chapterUser = $chapterUserRepo->findChapterUser($chapter->id, $user->id);
if ($chapterUser) {
$this->chapterUser = $chapterUser;
$this->joinedChapter = true;
}

View File

@ -31,7 +31,7 @@ class ChapterList extends FrontendService
$chapters = $cache->get($course->id);
if (empty($chapters)) {
if (count($chapters) == 0) {
return [];
}
@ -46,7 +46,7 @@ class ChapterList extends FrontendService
}
}
} else {
$mappings = $this->getLearningMappings($course, $user);
$mappings = $this->getLearningMappings($course->id, $user->id, $this->courseUser->plan_id);
foreach ($chapters as &$chapter) {
foreach ($chapter['children'] as &$lesson) {
$lesson['me'] = [
@ -61,11 +61,11 @@ class ChapterList extends FrontendService
return $chapters;
}
protected function getLearningMappings(CourseModel $course, UserModel $user)
protected function getLearningMappings($courseId, $userId, $planId)
{
$courseRepo = new CourseRepo();
$userLearnings = $courseRepo->findUserLearnings($course->id, $user->id);
$userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $planId);
if ($userLearnings->count() == 0) {
return [];

View File

@ -45,6 +45,7 @@ class CourseInfo extends FrontendService
'attrs' => $course->attrs,
'user_count' => $course->user_count,
'lesson_count' => $course->lesson_count,
'package_count' => $course->package_count,
'review_count' => $course->review_count,
'comment_count' => $course->comment_count,
'consult_count' => $course->consult_count,

View File

@ -28,18 +28,21 @@ class PackageList extends FrontendService
$result = [];
$firstCourseId = $course->id;
foreach ($packages as $package) {
$courses = $cache->get($package['id']);
$package['origin_price'] = 0.00;
$package['courses'] = [];
if ($courses) {
foreach ($courses as $course) {
$package['origin_price'] += $course['market_price'];
}
$package['courses'] = $courses;
$package['courses'] = $this->sortCourses($courses, $firstCourseId);
}
$result[] = $package;
@ -48,4 +51,36 @@ class PackageList extends FrontendService
return $result;
}
/**
* 把特定课程排在第一位
*
* @param array $courses
* @param int $firstCourseId
* @return array
*/
protected function sortCourses(array $courses, $firstCourseId)
{
$firstCourse = [];
foreach ($courses as $course) {
if ($course['id'] == $firstCourseId) {
$firstCourse = $course;
}
}
$result = [];
if ($firstCourse) {
$result[] = $firstCourse;
}
foreach ($courses as $course) {
if ($course['id'] != $firstCourseId) {
$result[] = $course;
}
}
return $result;
}
}

View File

@ -22,7 +22,7 @@ trait CourseTrait
protected $joinedCourse = false;
/**
* @var CourseUserModel
* @var CourseUserModel|null
*/
protected $courseUser;
@ -42,12 +42,16 @@ trait CourseTrait
public function setCourseUser(CourseModel $course, UserModel $user)
{
$courseUserRepo = new CourseUserRepo();
$courseUser = null;
$courseUser = $courseUserRepo->findCourseUser($course->id, $user->id);
if ($user->id > 0) {
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($course->id, $user->id);
}
$this->courseUser = $courseUser;
if ($courseUser) {
$this->courseUser = $courseUser;
$this->joinedCourse = true;
}

View File

@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\Order as OrderModel;
use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo;
class Refund extends Service
{
@ -104,15 +105,36 @@ class Refund extends Service
return 1.00;
}
$userLearnings = $courseRepo->findConsumedUserLearnings($courseId, $userId);
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($courseId, $userId);
if (!$courseUser) {
return 1.00;
}
$userLearnings = $courseRepo->findUserLearnings($courseId, $userId, $courseUser->plan_id);
if ($userLearnings->count() == 0) {
return 1.00;
}
/**
* @var array $consumedUserLearnings
*/
$consumedUserLearnings = $userLearnings->filter(function ($item) {
if ($item->consumed == 1) {
return $item;
}
});
if (count($consumedUserLearnings) == 0) {
return 1.00;
}
$courseLessonIds = kg_array_column($courseLessons->toArray(), 'id');
$userLessonIds = kg_array_column($userLearnings->toArray(), 'chapter_id');
$consumedLessonIds = array_intersect($courseLessonIds, $userLessonIds);
$consumedUserLessonIds = kg_array_column($consumedUserLearnings, 'chapter_id');
$consumedLessonIds = array_intersect($courseLessonIds, $consumedUserLessonIds);
$totalCount = count($courseLessonIds);
$consumedCount = count($consumedLessonIds);

View File

@ -4,7 +4,6 @@ namespace App\Traits;
use App\Caches\User as UserCache;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Auth as AuthService;
use App\Validators\Validator as AppValidator;
use Phalcon\Di;
@ -33,10 +32,6 @@ trait Auth
*/
public function getLoginUser()
{
$userRepo = new UserRepo();
return $userRepo->findById(100015);
$authUser = $this->getAuthUser();
$validator = new AppValidator();

View File

@ -419,7 +419,7 @@ body {
}
.package-item {
margin-bottom: 15px;
margin-bottom: 25px;
border-bottom: 1px dashed #ccc;
}
@ -468,16 +468,24 @@ body {
}
.package-course-list {
width: 560px;
width: 580px;
height: 170px;
overflow-x: auto;
white-space: nowrap;
}
.separator {
color: #666;
display: inline-block;
padding-top: 35px;
margin-left: -5px;
margin-right: 4px;
}
.package-course-card {
width: 175px;
display: inline-block;
margin-right: 5px;
margin-right: 8px;
vertical-align: top;
}

2
storage/cache/annotations/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*
!.gitignore