1
0
mirror of https://gitee.com/koogua/course-tencent-cloud.git synced 2025-07-16 21:26:13 +08:00

阶段性提交积分机制-0204

This commit is contained in:
winzer 2021-02-04 15:39:53 +08:00
parent e902c0c80f
commit f47091991d
76 changed files with 4423 additions and 163 deletions

View File

@ -0,0 +1,29 @@
<?php
namespace App\Caches;
use App\Models\PointGift as PointGiftModel;
class MaxPointGiftId extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'max_point_gift_id';
}
public function getContent($id = null)
{
$gift = PointGiftModel::findFirst(['order' => 'id DESC']);
return $gift->id ?? 0;
}
}

31
app/Caches/PointGift.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace App\Caches;
use App\Repos\PointGift as PointGiftRepo;
class PointGift extends Cache
{
protected $lifetime = 365 * 86400;
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return "point_gift:{$id}";
}
public function getContent($id = null)
{
$giftRepo = new PointGiftRepo();
$gift = $giftRepo->findById($id);
return $gift ?: null;
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Caches;
use App\Models\PointGift as PointGiftModel;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class PointHotGiftList extends Cache
{
/**
* 过期时间
*
* @var int
*/
protected $lifetime = 1 * 86400;
/**
* 显示个数
*
* @var int
*/
protected $limit = 5;
public function setLimit($limit)
{
$this->limit = $limit;
}
public function getLifetime()
{
return $this->lifetime;
}
public function getKey($id = null)
{
return 'point_hot_gift_list';
}
public function getContent($id = null)
{
$gifts = $this->findGifts($this->limit);
if (count($gifts) == 0) {
return [];
}
$result = [];
foreach ($gifts as $gift) {
$result[] = [
'id' => $gift->id,
'name' => $gift->name,
'cover' => $gift->cover,
'details' => $gift->details,
'type' => $gift->type,
'point' => $gift->point,
'redeem_count' => $gift->redeem_count,
];
}
return $result;
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|PointGiftModel[]
*/
protected function findGifts($limit = 5)
{
return PointGiftModel::query()
->where('published = 1')
->orderBy('redeem_count DESC')
->limit($limit)
->execute();
}
}

View File

@ -25,6 +25,7 @@ class CleanLogTask extends Task
$this->cleanWxpayLog(); $this->cleanWxpayLog();
$this->cleanOrderLog(); $this->cleanOrderLog();
$this->cleanRefundLog(); $this->cleanRefundLog();
$this->cleanPointLog();
$this->cleanNoticeLog(); $this->cleanNoticeLog();
$this->cleanOtherLog(); $this->cleanOtherLog();
} }
@ -221,6 +222,18 @@ class CleanLogTask extends Task
$this->whitelist[] = $type; $this->whitelist[] = $type;
} }
/**
* 清理积分日志
*/
protected function cleanPointLog()
{
$type = 'point';
$this->cleanLog($type, 7);
$this->whitelist[] = $type;
}
/** /**
* 清理通知日志 * 清理通知日志
*/ */

View File

@ -0,0 +1,213 @@
<?php
namespace App\Console\Tasks;
use App\Models\CourseUser as CourseUserModel;
use App\Models\ImGroupUser as ImGroupUserModel;
use App\Models\PointGift as PointGiftModel;
use App\Models\PointHistory as PointHistoryModel;
use App\Models\PointRedeem as PointRedeemModel;
use App\Models\Task as TaskModel;
use App\Repos\Course as CourseRepo;
use App\Repos\ImGroupUser as ImGroupUserRepo;
use App\Repos\PointGift as PointGiftRepo;
use App\Repos\PointRedeem as PointRedeemRepo;
use App\Repos\User as UserRepo;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class PointGiftAwardTask extends Task
{
const TRY_COUNT = 3;
public function mainAction()
{
$logger = $this->getLogger('point');
$tasks = $this->findTasks();
if ($tasks->count() == 0) {
return;
}
$redeemRepo = new PointRedeemRepo();
foreach ($tasks as $task) {
$redeem = $redeemRepo->findById($task->item_id);
if (!$redeem) continue;
try {
switch ($redeem->gift_type) {
case PointGiftModel::TYPE_COURSE:
$this->handleCourseAward($redeem);
break;
case PointGiftModel::TYPE_GOODS:
$this->handleCommodityAward($redeem);
break;
}
$this->finishRedeem($redeem);
$task->status = TaskModel::STATUS_FINISHED;
$task->update();
} catch (\Exception $e) {
$task->try_count += 1;
$task->priority += 1;
if ($task->try_count > self::TRY_COUNT) {
$task->status = TaskModel::STATUS_FAILED;
}
$task->update();
$logger->info('Point Gift Award Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
]));
}
if ($task->status == TaskModel::STATUS_FINISHED) {
$this->handleFinishNotice();
} elseif ($task->status == TaskModel::STATUS_FAILED) {
$this->handlePointRefund($redeem);
}
}
}
protected function finishRedeem(PointRedeemModel $redeem)
{
$redeem->status = PointRedeemModel::STATUS_FINISHED;
if ($redeem->update() === false) {
throw new \RuntimeException('Finish Point Redeem Failed');
}
}
protected function handleCourseAward(PointRedeemModel $redeem)
{
$giftRepo = new PointGiftRepo();
$gift = $giftRepo->findById($redeem->gift_id);
$courseUser = new CourseUserModel();
$courseUser->user_id = $redeem->user_id;
$courseUser->course_id = $gift->attrs['id'];
$courseUser->expiry_time = $gift->attrs['study_expiry_time'];
$courseUser->role_type = CourseUserModel::ROLE_STUDENT;
$courseUser->source_type = CourseUserModel::SOURCE_POINT_REDEEM;
if ($courseUser->create() === false) {
throw new \RuntimeException('Create Course User Failed');
}
$courseRepo = new CourseRepo();
$group = $courseRepo->findImGroup($gift->attrs['id']);
$groupUserRepo = new ImGroupUserRepo();
$groupUser = $groupUserRepo->findGroupUser($group->id, $redeem->user_id);
if ($groupUser) return;
$groupUser = new ImGroupUserModel();
$groupUser->group_id = $group->id;
$groupUser->user_id = $redeem->user_id;
if ($groupUser->create() === false) {
throw new \RuntimeException('Create Im Group User Failed');
}
}
protected function handleCommodityAward(PointRedeemModel $redeem)
{
}
protected function handleFinishNotice()
{
}
protected function handlePointRefund(PointRedeemModel $redeem)
{
$logger = $this->getLogger('point');
$userRepo = new UserRepo();
$balance = $userRepo->findUserBalance($redeem->user_id);
try {
$this->db->begin();
$history = new PointHistoryModel();
$eventInfo = [
'gift' => [
'id' => $redeem->gift_id,
'name' => $redeem->gift_name,
]
];
$history->user_id = $redeem->user_id;
$history->event_id = $redeem->id;
$history->event_type = PointHistoryModel::EVENT_POINT_REFUND;
$history->event_info = $eventInfo;
$result = $history->create();
if ($result === false) {
throw new \RuntimeException('Create Point History Failed');
}
$balance->point += $redeem->gift_point;
$result = $balance->update();
if ($result === false) {
throw new \RuntimeException('Update User Balance Failed');
}
$this->db->commit();
} catch (\Exception $e) {
$this->db->rollback();
$logger->error('Point Refund Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
]));
}
}
/**
* @param int $limit
* @return ResultsetInterface|Resultset|TaskModel[]
*/
protected function findTasks($limit = 30)
{
$itemType = TaskModel::TYPE_POINT_GIFT_AWARD;
$status = TaskModel::STATUS_PENDING;
$createTime = strtotime('-3 days');
return TaskModel::query()
->where('item_type = :item_type:', ['item_type' => $itemType])
->andWhere('status = :status:', ['status' => $status])
->andWhere('create_time > :create_time:', ['create_time' => $createTime])
->orderBy('priority ASC')
->limit($limit)
->execute();
}
}

View File

@ -2,6 +2,7 @@
namespace App\Console\Tasks; namespace App\Console\Tasks;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\Course as CourseModel; use App\Models\Course as CourseModel;
use App\Models\Learning as LearningModel; use App\Models\Learning as LearningModel;
use App\Repos\Chapter as ChapterRepo; use App\Repos\Chapter as ChapterRepo;
@ -9,7 +10,8 @@ use App\Repos\ChapterUser as ChapterUserRepo;
use App\Repos\Course as CourseRepo; use App\Repos\Course as CourseRepo;
use App\Repos\CourseUser as CourseUserRepo; use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\Learning as LearningRepo; use App\Repos\Learning as LearningRepo;
use App\Services\Sync\Learning as LearningSync; use App\Services\Logic\Point\PointHistory as PointHistoryService;
use App\Services\Sync\Learning as LearningSyncService;
class SyncLearningTask extends Task class SyncLearningTask extends Task
{ {
@ -18,7 +20,7 @@ class SyncLearningTask extends Task
{ {
$redis = $this->getRedis(); $redis = $this->getRedis();
$sync = new LearningSync(); $sync = new LearningSyncService();
$syncKey = $sync->getSyncKey(); $syncKey = $sync->getSyncKey();
@ -120,7 +122,10 @@ class SyncLearningTask extends Task
$chapterUser->update(); $chapterUser->update();
if ($chapterUser->consumed == 1) { if ($chapterUser->consumed == 1) {
$this->updateCourseUser($learning); $this->updateCourseUser($learning);
$this->handleLearningPoint($chapterUser);
} }
} }
@ -174,4 +179,14 @@ class SyncLearningTask extends Task
$courseUser->update(); $courseUser->update();
} }
/**
* @param ChapterUserModel $chapterUser
*/
protected function handleLearningPoint(ChapterUserModel $chapterUser)
{
$service = new PointHistoryService();
$service->handleChapterLearning($chapterUser);
}
} }

View File

@ -26,8 +26,6 @@ class Controller extends \Phalcon\Mvc\Controller
$this->checkCsrfToken(); $this->checkCsrfToken();
} }
$this->checkRateLimit();
$this->authInfo = $this->getAuthInfo(); $this->authInfo = $this->getAuthInfo();
if (!$this->authInfo) { if (!$this->authInfo) {

View File

@ -0,0 +1,128 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\PointGift as PointGiftService;
/**
* @RoutePrefix("/admin/point/gift")
*/
class PointGiftController extends Controller
{
/**
* @Get("/list", name="admin.point_gift.list")
*/
public function listAction()
{
$giftService = new PointGiftService();
$pager = $giftService->getGifts();
$this->view->pick('point/gift/list');
$this->view->setVar('pager', $pager);
}
/**
* @Get("/add", name="admin.point_gift.add")
*/
public function addAction()
{
$this->view->pick('point/gift/add');
}
/**
* @Get("/{id:[0-9]+}/edit", name="admin.point_gift.edit")
*/
public function editAction($id)
{
$giftService = new PointGiftService();
$gift = $giftService->getGift($id);
$this->view->pick('point/gift/edit');
$this->view->setVar('gift', $gift);
}
/**
* @Post("/create", name="admin.point_gift.create")
*/
public function createAction()
{
$giftService = new PointGiftService();
$gift = $giftService->createGift();
$location = $this->url->get([
'for' => 'admin.point_gift.edit',
'id' => $gift->id,
]);
$content = [
'location' => $location,
'msg' => '添加礼品成功',
];
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/update", name="admin.point_gift.update")
*/
public function updateAction($id)
{
$giftService = new PointGiftService();
$giftService->updateGift($id);
$location = $this->url->get(['for' => 'admin.point_gift.list']);
$content = [
'location' => $location,
'msg' => '更新礼品成功',
];
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/delete", name="admin.point_gift.delete")
*/
public function deleteAction($id)
{
$giftService = new PointGiftService();
$giftService->deleteGift($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '删除礼品成功',
];
return $this->jsonSuccess($content);
}
/**
* @Post("/{id:[0-9]+}/restore", name="admin.point_gift.restore")
*/
public function restoreAction($id)
{
$giftService = new PointGiftService();
$giftService->restoreGift($id);
$location = $this->request->getHTTPReferer();
$content = [
'location' => $location,
'msg' => '还原礼品成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -0,0 +1,60 @@
<?php
namespace App\Http\Admin\Controllers;
use App\Http\Admin\Services\PointGift as PointGiftService;
/**
* @RoutePrefix("/admin/point/redeem")
*/
class PointRedeemController extends Controller
{
/**
* @Get("/list", name="admin.point_redeem.list")
*/
public function listAction()
{
$groupService = new PointGiftService();
$pager = $groupService->getGroups();
$this->view->pick('point/redeem/list');
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id:[0-9]+}/edit", name="admin.point_redeem.edit")
*/
public function editAction($id)
{
$groupService = new PointGiftService();
$group = $groupService->getGroup($id);
$this->view->pick('point/redeem/edit');
$this->view->setVar('group', $group);
}
/**
* @Post("/{id:[0-9]+}/update", name="admin.point_redeem.update")
*/
public function updateAction($id)
{
$groupService = new PointGiftService();
$groupService->updateGroup($id);
$location = $this->url->get(['for' => 'admin.point_redeem.list']);
$content = [
'location' => $location,
'msg' => '更新群组成功',
];
return $this->jsonSuccess($content);
}
}

View File

@ -50,7 +50,7 @@ class SettingController extends Controller
$data = $this->request->getPost(); $data = $this->request->getPost();
$settingService->updateStorageSettings($section, $data); $settingService->updateSettings($section, $data);
return $this->jsonSuccess(['msg' => '更新配置成功']); return $this->jsonSuccess(['msg' => '更新配置成功']);
@ -248,6 +248,31 @@ class SettingController extends Controller
} }
} }
/**
* @Route("/point", name="admin.setting.point")
*/
public function pointAction()
{
$section = 'point';
$settingService = new SettingService();
if ($this->request->isPost()) {
$data = $this->request->getPost();
$settingService->updatePointSettings($section, $data);
return $this->jsonSuccess(['msg' => '更新配置成功']);
} else {
$point = $settingService->getSettings($section);
$this->view->setVar('point', $point);
}
}
/** /**
* @Route("/vip", name="admin.setting.vip") * @Route("/vip", name="admin.setting.vip")
*/ */

View File

@ -476,6 +476,43 @@ class AuthNode extends Service
], ],
], ],
], ],
[
'id' => '2-8',
'title' => '积分商城',
'type' => 'menu',
'children' => [
[
'id' => '2-8-1',
'title' => '兑换记录',
'type' => 'menu',
'route' => 'admin.point_redeem.list',
],
[
'id' => '2-8-2',
'title' => '礼品列表',
'type' => 'menu',
'route' => 'admin.point_gift.list',
],
[
'id' => '2-8-3',
'title' => '添加礼品',
'type' => 'menu',
'route' => 'admin.point_gift.add',
],
[
'id' => '2-8-4',
'title' => '编辑礼品',
'type' => 'button',
'route' => 'admin.point_gift.edit',
],
[
'id' => '2-8-5',
'title' => '删除礼品',
'type' => 'button',
'route' => 'admin.point_gift.delete',
],
],
],
], ],
]; ];
} }
@ -763,6 +800,12 @@ class AuthNode extends Service
'type' => 'menu', 'type' => 'menu',
'route' => 'admin.setting.wechat_oa', 'route' => 'admin.setting.wechat_oa',
], ],
[
'id' => '5-1-14',
'title' => '积分设置',
'type' => 'menu',
'route' => 'admin.setting.point',
],
], ],
], ],
], ],

View File

@ -0,0 +1,155 @@
<?php
namespace App\Http\Admin\Services;
use App\Library\Paginator\Query as PagerQuery;
use App\Models\PointGift as PointGiftModel;
use App\Repos\PointGift as PointGiftRepo;
use App\Validators\PointGift as PointGiftValidator;
class PointGift extends Service
{
public function getGifts()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['deleted'] = $params['deleted'] ?? 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$giftRepo = new PointGiftRepo();
return $giftRepo->paginate($params, $sort, $page, $limit);
}
public function getGift($id)
{
return $this->findOrFail($id);
}
public function createGift()
{
$post = $this->request->getPost();
$validator = new PointGiftValidator();
$post['type'] = $validator->checkType($post['type']);
$gift = new PointGiftModel();
switch ($post['type']) {
case PointGiftModel::TYPE_COURSE:
$gift = $this->createCourseGift($post);
break;
case PointGiftModel::TYPE_GOODS:
$gift = $this->createCommodityGift($post);
break;
}
return $gift;
}
public function updateGift($id)
{
$gift = $this->findOrFail($id);
$post = $this->request->getPost();
$validator = new PointGiftValidator();
$data = [];
if (isset($post['cover'])) {
$data['cover'] = $validator->checkCover($post['cover']);
}
if (isset($post['name'])) {
$data['name'] = $validator->checkName($post['name']);
}
if (isset($post['details'])) {
$data['details'] = $validator->checkDetails($post['details']);
}
if (isset($post['point'])) {
$data['point'] = $validator->checkPoint($post['point']);
}
if (isset($post['stock'])) {
$data['stock'] = $validator->checkStock($post['stock']);
}
if (isset($post['published'])) {
$data['published'] = $validator->checkPublishStatus($post['published']);
}
$gift->update($data);
return $gift;
}
public function deleteGift($id)
{
$gift = $this->findOrFail($id);
$gift->deleted = 1;
$gift->update();
return $gift;
}
public function restoreGift($id)
{
$gift = $this->findOrFail($id);
$gift->deleted = 0;
$gift->update();
return $gift;
}
protected function createCourseGift($post)
{
$validator = new PointGiftValidator();
$course = $validator->checkCourse($post['course_id']);
$gift = new PointGiftModel();
$gift->type = PointGiftModel::TYPE_COURSE;
$gift->name = $course->title;
$gift->create();
return $gift;
}
protected function createCommodityGift($post)
{
$validator = new PointGiftValidator();
$gift = new PointGiftModel();
$gift->type = PointGiftModel::TYPE_GOODS;
$gift->name = $validator->checkName($post['name']);
$gift->create();
return $gift;
}
protected function findOrFail($id)
{
$validator = new PointGiftValidator();
return $validator->checkGift($id);
}
}

View File

@ -4,9 +4,10 @@ namespace App\Http\Admin\Services;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Services\Auth\Admin as AdminAuth; use App\Services\Auth\Admin as AdminAuth;
use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService;
use App\Validators\Account as AccountValidator; use App\Validators\Account as AccountValidator;
use App\Validators\Captcha as CaptchaValidator; use App\Validators\Captcha as CaptchaValidator;
use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
class Session extends Service class Session extends Service
{ {
@ -47,21 +48,38 @@ class Session extends Service
$captchaValidator->checkCode($post['ticket'], $post['rand']); $captchaValidator->checkCode($post['ticket'], $post['rand']);
} }
$this->handleLoginNotice($user);
$this->auth->saveAuthInfo($user); $this->auth->saveAuthInfo($user);
$this->fireAfterLoginEvent($user);
} }
public function logout() public function logout()
{ {
$user = $this->getLoginUser();
$this->auth->clearAuthInfo(); $this->auth->clearAuthInfo();
$this->fireAfterLogoutEvent($user);
} }
protected function handleLoginNotice(UserModel $user) protected function fireAfterLoginEvent(UserModel $user)
{ {
$service = new AccountLoginNoticeService(); /**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$service->createTask($user); $eventsManager->fire('account:afterLogin', $this, $user);
}
protected function fireAfterLogoutEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('account:afterLogout', $this, $user);
} }
} }

View File

@ -181,6 +181,19 @@ class Setting extends Service
$this->updateSettings($section, $settings); $this->updateSettings($section, $settings);
} }
public function updatePointSettings($section, $settings)
{
if (isset($settings['event_rule'])) {
$settings['event_rule'] = kg_json_encode($settings['event_rule']);
}
if (isset($settings['consume_rule'])) {
$settings['consume_rule'] = kg_json_encode($settings['consume_rule']);
}
$this->updateSettings($section, $settings);
}
public function updateVipSettings($items) public function updateVipSettings($items)
{ {
$vipRepo = new VipRepo(); $vipRepo = new VipRepo();

View File

@ -0,0 +1,62 @@
{% extends 'templates/main.volt' %}
{% block content %}
<form id="form-1" class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.point_gift.create'}) }}">
<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 type="radio" name="type" value="1" title="课程" lay-filter="type">
<input type="radio" name="type" value="2" title="商品" lay-filter="type">
</div>
</div>
<div id="block-1" class="block" style="display:none;">
<div class="layui-form-item">
<label class="layui-form-label">课程编号</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="item_id" value="">
</div>
</div>
</div>
<div id="block-2" class="block" style="display:none;">
<div class="layui-form-item">
<label class="layui-form-label">商品名称</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" value="">
</div>
</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" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{% endblock %}
{% block inline_js %}
<script>
layui.use(['jquery', 'form'], function () {
var $ = layui.jquery;
var form = layui.form;
form.on('radio(type)', function (data) {
var block = $('#block-' + data.value);
$('.block').hide();
block.show();
});
});
</script>
{% endblock %}

View File

@ -0,0 +1,108 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set update_url = url({'for':'admin.point_gift.update','id':gift.id}) %}
{% if gift.type == 1 %}
<form class="layui-form kg-form" method="POST" action="{{ update_url }}">
<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-inline">
<img id="img-cover" class="kg-cover" src="{{ gift.cover }}">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">课程名称</label>
<div class="layui-form-mid layui-word-aux">{{ gift.name }}</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="point" value="{{ gift.point }}" lay-verify="number">
</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="stock" value="{{ gift.stock }}" lay-verify="number">
</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" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{% endif %}
{% if gift.type == 2 %}
<form class="layui-form kg-form" method="POST" action="{{ update_url }}">
<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-inline">
<img id="img-cover" class="kg-cover" src="{{ gift.cover }}">
<input type="hidden" name="cover" value="{{ gift.cover }}">
</div>
<div class="layui-input-inline" style="padding-top:35px;">
<button id="change-cover" class="layui-btn layui-btn-sm" type="button">更换</button>
</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="name" value="{{ gift.name }}" lay-verify="required">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">商品详情</label>
<div class="layui-input-block">
<div id="vditor"></div>
<textarea name="details" class="layui-hide" id="vditor-textarea">{{ gift.details }}</textarea>
</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="point" value="{{ gift.point }}" lay-verify="number">
</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="stock" value="{{ gift.stock }}" lay-verify="number">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="kg-submit layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{% endif %}
{% endblock %}
{% block link_css %}
{{ css_link('https://cdn.jsdelivr.net/npm/vditor/dist/index.css', false) }}
{% endblock %}
{% block include_js %}
{{ js_include('https://cdn.jsdelivr.net/npm/vditor/dist/index.min.js', false) }}
{{ js_include('admin/js/cover.upload.js') }}
{{ js_include('admin/js/vditor.js') }}
{% endblock %}

View File

@ -0,0 +1,83 @@
{% extends 'templates/main.volt' %}
{% block content %}
{%- macro type_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green">课程</span>
{% elseif value == 2 %}
<span class="layui-badge layui-bg-blue">商品</span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-cyan">现金</span>
{% else %}
<span class="layui-badge layui-bg-gray">未知</span>
{% endif %}
{%- endmacro %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>礼品管理</cite></a>
</span>
</div>
</div>
<table class="kg-table layui-table layui-form">
<group>
<col>
<col>
<col>
<col>
<col>
<col>
<col width="12%">
</group>
<thead>
<tr>
<th>编号</th>
<th>礼品名称</th>
<th>所需积分</th>
<th>库存数量</th>
<th>兑换人次</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set redeem_url = url({'for':'admin.point_redeem.list'},{'gift_id':item.id}) %}
{% set preview_url = url({'for':'home.point_gift.show','id':item.id}) %}
{% set edit_url = url({'for':'admin.point_gift.edit','id':item.id}) %}
{% set update_url = url({'for':'admin.point_gift.update','id':item.id}) %}
{% set delete_url = url({'for':'admin.point_gift.delete','id':item.id}) %}
{% set restore_url = url({'for':'admin.point_gift.restore','id':item.id}) %}
<tr>
<td>{{ item.id }}</td>
<td><a href="{{ edit_url }}">{{ item.name }}</a> {{ type_info(item.type) }}</td>
<td>{{ item.point }}</td>
<td>{{ item.stock }}</td>
<td><a class="layui-badge layui-bg-green" href="{{ redeem_url }}">{{ item.redeem_count }}</a></td>
<td><input type="checkbox" name="published" value="1" lay-filter="published" lay-skin="switch" lay-text="是|否" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
<li><a href="{{ preview_url }}" target="_blank">前台预览</a></li>
<li><a href="{{ redeem_url }}">兑换记录</a></li>
<li><a href="{{ edit_url }}">编辑</a></li>
{% if item.deleted == 0 %}
<li><a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除</a></li>
{% else %}
<li><a href="javascript:" class="kg-restore" data-url="{{ restore_url }}">还原</a></li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}

View File

@ -0,0 +1,64 @@
{% extends 'templates/main.volt' %}
{% block content %}
<form class="layui-form kg-form" method="GET" action="{{ url({'for':'admin.group.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="name" 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="user_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="course_id" placeholder="课程编号精确匹配">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">类型</label>
<div class="layui-input-block">
<input type="radio" name="type" value="1" title="课程">
<input type="radio" name="type" value="2" title="聊天">
<input type="radio" name="type" value="3" title="职工">
</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>
{% endblock %}

View File

@ -0,0 +1,86 @@
{% extends 'templates/main.volt' %}
{% block content %}
{%- macro type_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green">课</span>
{% elseif value == 2 %}
<span class="layui-badge layui-bg-blue">聊</span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-cyan">职</span>
{% else %}
<span class="layui-badge layui-bg-gray">未知</span>
{% endif %}
{%- endmacro %}
{%- macro owner_info(owner) %}
{% if owner.id is defined %}
{{ owner.name }}{{ owner.id }}
{% else %}
未设置
{% endif %}
{%- endmacro %}
<div class="kg-nav">
<div class="kg-nav-left">
<span class="layui-breadcrumb">
<a><cite>群组管理</cite></a>
</span>
</div>
</div>
<table class="kg-table layui-table layui-form">
<colgroup>
<col>
<col>
<col>
<col>
<col>
<col width="12%">
</colgroup>
<thead>
<tr>
<th>编号</th>
<th>名称</th>
<th>群主</th>
<th>成员</th>
<th>发布</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for item in pager.items %}
{% set preview_url = url({'for':'home.group.show','id':item.id}) %}
{% set edit_url = url({'for':'admin.group.edit','id':item.id}) %}
{% set update_url = url({'for':'admin.group.update','id':item.id}) %}
{% set delete_url = url({'for':'admin.group.delete','id':item.id}) %}
{% set restore_url = url({'for':'admin.group.restore','id':item.id}) %}
<tr>
<td>{{ item.id }}</td>
<td><a href="{{ edit_url }}">{{ item.name }}</a> {{ type_info(item.type) }}</td>
<td>{{ owner_info(item.owner) }}</td>
<td><span class="layui-badge layui-bg-gray">{{ item.user_count }}</span></td>
<td><input type="checkbox" name="published" value="1" lay-filter="published" lay-skin="switch" lay-text="是|否" data-url="{{ update_url }}" {% if item.published == 1 %}checked="checked"{% endif %}></td>
<td class="center">
<div class="layui-dropdown">
<button class="layui-btn layui-btn-sm">操作 <i class="layui-icon layui-icon-triangle-d"></i></button>
<ul>
<li><a href="{{ preview_url }}" target="_blank">预览</a></li>
<li><a href="{{ edit_url }}">编辑</a></li>
{% if item.deleted == 0 %}
<li><a href="javascript:" class="kg-delete" data-url="{{ delete_url }}">删除</a></li>
{% else %}
<li><a href="javascript:" class="kg-restore" data-url="{{ restore_url }}">还原</a></li>
{% endif %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{{ partial('partials/pager') }}
{% endblock %}

View File

@ -0,0 +1,112 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set consume_rule = point.consume_rule|json_decode %}
{% set event_rule = point.event_rule|json_decode %}
<form class="layui-form kg-form" method="POST" action="{{ url({'for':'admin.setting.point'}) }}">
<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 type="radio" name="enabled" value="1" title="是" {% if point.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="enabled" value="0" title="否" {% if point.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<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 type="radio" name="consume_rule[enabled]" value="1" title="是" {% if consume_rule.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="consume_rule[enabled]" value="0" title="否" {% if consume_rule.enabled == "0" %}checked="checked"{% endif %}>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">奖励倍率</label>
<div class="layui-input-inline">
<input class="layui-input" type="text" name="consume_rule[rate]" value="{{ consume_rule.rate }}" lay-verify="number">
</div>
<div class="layui-form-mid layui-word-aux">奖励积分 消费金额 X 奖励倍率</div>
</div>
<fieldset class="layui-elem-field layui-field-title">
<legend>行为奖励规则</legend>
</fieldset>
<table class="layui-table kg-table layui-form" style="width:60%;">
<colgroup>
<col width="15%">
<col>
<col>
<col>
</colgroup>
<thead>
<tr>
<th>行为类型</th>
<th>奖励积分</th>
<th>每日上限</th>
<th>启用规则</th>
</tr>
</thead>
<tbody>
<tr>
<td>帐号注册</td>
<td><input class="layui-input" type="text" name="event_rule[account_register][point]" value="{{ event_rule.account_register.point }}" lay-verify="required"></td>
<td>N/A</td>
<td>
<input type="radio" name="event_rule[account_register][enabled]" value="1" title="是" {% if event_rule.account_register.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="event_rule[account_register][enabled]" value="0" title="否" {% if event_rule.account_register.enabled == "0" %}checked="checked"{% endif %}>
</td>
</tr>
<tr>
<td>站点访问</td>
<td><input class="layui-input" type="text" name="event_rule[site_visit][point]" value="{{ event_rule.site_visit.point }}" lay-verify="required"></td>
<td>N/A</td>
<td>
<input type="radio" name="event_rule[site_visit][enabled]" value="1" title="是" {% if event_rule.site_visit.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="event_rule[site_visit][enabled]" value="0" title="否" {% if event_rule.site_visit.enabled == "0" %}checked="checked"{% endif %}>
</td>
</tr>
<tr>
<td>课程评价</td>
<td><input class="layui-input" type="text" name="event_rule[course_review][point]" value="{{ event_rule.course_review.point }}" lay-verify="required"></td>
<td>N/A</td>
<td>
<input type="radio" name="event_rule[course_review][enabled]" value="1" title="是" {% if event_rule.course_review.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="event_rule[course_review][enabled]" value="0" title="否" {% if event_rule.course_review.enabled == "0" %}checked="checked"{% endif %}>
</td>
</tr>
<tr>
<td>课时学习</td>
<td><input class="layui-input" type="text" name="event_rule[lesson_learning][point]" value="{{ event_rule.lesson_learning.point }}" lay-verify="required"></td>
<td>N/A</td>
<td>
<input type="radio" name="event_rule[lesson_learning][enabled]" value="1" title="是" {% if event_rule.lesson_learning.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="event_rule[lesson_learning][enabled]" value="0" title="否" {% if event_rule.lesson_learning.enabled == "0" %}checked="checked"{% endif %}>
</td>
</tr>
<tr>
<td>群组讨论</td>
<td><input class="layui-input" type="text" name="event_rule[group_discuss][point]" value="{{ event_rule.group_discuss.point }}" lay-verify="required"></td>
<td>N/A</td>
<td>
<input type="radio" name="event_rule[group_discuss][enabled]" value="1" title="是" {% if event_rule.group_discuss.enabled == "1" %}checked="checked"{% endif %}>
<input type="radio" name="event_rule[group_discuss][enabled]" value="0" title="否" {% if event_rule.group_discuss.enabled == "0" %}checked="checked"{% endif %}>
</td>
</tr>
</tbody>
</table>
<br>
<div class="layui-form-item">
<label class="layui-form-label"></label>
<div class="layui-input-block">
<button class="layui-btn" lay-submit="true" lay-filter="go">提交</button>
<button type="button" class="kg-back layui-btn layui-btn-primary">返回</button>
</div>
</div>
</form>
{% endblock %}

View File

@ -2,14 +2,22 @@
namespace App\Http\Api\Controllers; namespace App\Http\Api\Controllers;
use App\Services\Auth\Api as AppAuth; use App\Models\User as UserModel;
use App\Services\Auth\Api as ApiAuth;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait; use App\Traits\Security as SecurityTrait;
use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\Dispatcher; use Phalcon\Mvc\Dispatcher;
class Controller extends \Phalcon\Mvc\Controller class Controller extends \Phalcon\Mvc\Controller
{ {
/**
* @var UserModel
*/
protected $authUser;
use ResponseTrait; use ResponseTrait;
use SecurityTrait; use SecurityTrait;
@ -19,21 +27,36 @@ class Controller extends \Phalcon\Mvc\Controller
$this->setCors(); $this->setCors();
} }
if (!$this->request->isOptions()) { $this->checkRateLimit();
$this->checkRateLimit();
}
return true; return true;
} }
public function initialize()
{
$this->authUser = $this->getAuthUser();
$this->fireSiteViewEvent($this->authUser);
}
protected function getAuthUser() protected function getAuthUser()
{ {
/** /**
* @var AppAuth $auth * @var ApiAuth $auth
*/ */
$auth = $this->getDI()->get('auth'); $auth = $this->getDI()->get('auth');
return $auth->getCurrentUser(); return $auth->getCurrentUser();
} }
protected function fireSiteViewEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('site:view', $this, $user);
}
} }

View File

@ -6,8 +6,9 @@ use App\Models\User as UserModel;
use App\Repos\User as UserRepo; use App\Repos\User as UserRepo;
use App\Services\Auth\Api as AuthService; use App\Services\Auth\Api as AuthService;
use App\Services\Logic\Account\Register as RegisterService; use App\Services\Logic\Account\Register as RegisterService;
use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService;
use App\Validators\Account as AccountValidator; use App\Validators\Account as AccountValidator;
use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
class Account extends Service class Account extends Service
{ {
@ -32,7 +33,11 @@ class Account extends Service
$user = $userRepo->findById($account->id); $user = $userRepo->findById($account->id);
return $this->auth->saveAuthInfo($user); $token = $this->auth->saveAuthInfo($user);
$this->fireAfterRegisterEvent($user);
return $token;
} }
public function loginByPassword() public function loginByPassword()
@ -52,9 +57,11 @@ class Account extends Service
$user = $validator->checkUserLogin($post['account'], $post['password']); $user = $validator->checkUserLogin($post['account'], $post['password']);
$this->handleLoginNotice($user); $token = $this->auth->saveAuthInfo($user);
return $this->auth->saveAuthInfo($user); $this->fireAfterLoginEvent($user);
return $token;
} }
public function loginByVerify() public function loginByVerify()
@ -74,21 +81,50 @@ class Account extends Service
$user = $validator->checkVerifyLogin($post['account'], $post['verify_code']); $user = $validator->checkVerifyLogin($post['account'], $post['verify_code']);
$this->handleLoginNotice($user); $token = $this->auth->saveAuthInfo($user);
return $this->auth->saveAuthInfo($user); $this->fireAfterLoginEvent($user);
return $token;
} }
public function logout() public function logout()
{ {
$user = $this->getLoginUser();
$this->auth->clearAuthInfo(); $this->auth->clearAuthInfo();
$this->fireAfterLogoutEvent($user);
} }
protected function handleLoginNotice(UserModel $user) protected function fireAfterRegisterEvent(UserModel $user)
{ {
$service = new AccountLoginNoticeService(); /**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$service->createTask($user); $eventsManager->fire('account:afterRegister', $this, $user);
}
protected function fireAfterLoginEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('account:afterLogin', $this, $user);
}
protected function fireAfterLogoutEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('account:afterLogout', $this, $user);
} }
} }

View File

@ -10,6 +10,8 @@ use App\Services\Auth\Home as HomeAuth;
use App\Services\Service as AppService; use App\Services\Service as AppService;
use App\Traits\Response as ResponseTrait; use App\Traits\Response as ResponseTrait;
use App\Traits\Security as SecurityTrait; use App\Traits\Security as SecurityTrait;
use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\Dispatcher; use Phalcon\Mvc\Dispatcher;
class Controller extends \Phalcon\Mvc\Controller class Controller extends \Phalcon\Mvc\Controller
@ -66,11 +68,7 @@ class Controller extends \Phalcon\Mvc\Controller
$this->checkCsrfToken(); $this->checkCsrfToken();
} }
$config = $this->getConfig(); $this->checkRateLimit();
if ($config->path('throttle.enabled')) {
$this->checkRateLimit();
}
return true; return true;
} }
@ -82,6 +80,11 @@ class Controller extends \Phalcon\Mvc\Controller
$this->appInfo = $this->getAppInfo(); $this->appInfo = $this->getAppInfo();
$this->imInfo = $this->getImInfo(); $this->imInfo = $this->getImInfo();
/**
* @todo 内部操作会改变afterFetch()
*/
$this->fireSiteViewEvent($this->authUser);
$this->seo->setTitle($this->siteInfo['title']); $this->seo->setTitle($this->siteInfo['title']);
$this->view->setVar('seo', $this->seo); $this->view->setVar('seo', $this->seo);
@ -158,4 +161,14 @@ class Controller extends \Phalcon\Mvc\Controller
return $appService->getSettings($section); return $appService->getSettings($section);
} }
protected function fireSiteViewEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('site:view', $this, $user);
}
} }

View File

@ -0,0 +1,80 @@
<?php
namespace App\Http\Home\Controllers;
use App\Services\Logic\Point\GiftInfo as GiftInfoService;
use App\Services\Logic\Point\GiftList as GiftListService;
use App\Services\Logic\Point\HotGiftList as HotGiftListService;
use App\Services\Logic\Point\PointRedeem as GiftRedeemService;
use Phalcon\Mvc\View;
/**
* @RoutePrefix("/point/gift")
*/
class PointGiftController extends Controller
{
/**
* @Get("/list", name="home.point_gift.list")
*/
public function listAction()
{
$this->seo->prependTitle('积分兑换');
$this->view->pick('point/gift/list');
}
/**
* @Get("/pager", name="home.point_gift.pager")
*/
public function pagerAction()
{
$service = new GiftListService();
$pager = $service->handle();
$pager->target = 'gift-list';
$this->view->setRenderLevel(View::LEVEL_ACTION_VIEW);
$this->view->pick('point/gift/pager');
$this->view->setVar('pager', $pager);
}
/**
* @Get("/{id:[0-9]+}", name="home.point_gift.show")
*/
public function showAction($id)
{
$service = new GiftInfoService();
$gift = $service->handle($id);
$hotGifts = $this->getHotGifts();
$this->seo->prependTitle(['积分兑换', $gift['name']]);
$this->view->pick('point/gift/show');
$this->view->setVar('gift', $gift);
$this->view->setVar('hot_gifts', $hotGifts);
}
/**
* @Post("/redeem", name="home.point_gift.redeem")
*/
public function redeemAction()
{
$service = new GiftRedeemService();
$service->handle();
return $this->jsonSuccess(['msg' => '兑换成功']);
}
protected function getHotGifts()
{
$service = new HotGiftListService();
return $service->handle();
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\Home\Controllers;
use App\Services\Logic\Point\PointRedeem as GiftRedeemService;
/**
* @RoutePrefix("/point/redeem")
*/
class PointRedeemController extends Controller
{
/**
* @Post("/create", name="home.point_redeem.create")
*/
public function createAction()
{
$service = new GiftRedeemService();
$service->handle();
return $this->jsonSuccess(['msg' => '兑换成功']);
}
/**
* @Get("/list", name="home.point_gift.list")
*/
public function listAction()
{
$this->seo->prependTitle('积分兑换');
$this->view->pick('point/gift/list');
}
}

View File

@ -8,11 +8,14 @@ use App\Services\Logic\User\Console\AccountInfo as AccountInfoService;
use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService; use App\Services\Logic\User\Console\ConnectDelete as ConnectDeleteService;
use App\Services\Logic\User\Console\ConnectList as ConnectListService; use App\Services\Logic\User\Console\ConnectList as ConnectListService;
use App\Services\Logic\User\Console\ConsultList as ConsultListService; use App\Services\Logic\User\Console\ConsultList as ConsultListService;
use App\Services\Logic\User\Console\ContactInfo as ContactInfoService;
use App\Services\Logic\User\Console\ContactUpdate as ContactUpdateService;
use App\Services\Logic\User\Console\CourseList as CourseListService; use App\Services\Logic\User\Console\CourseList as CourseListService;
use App\Services\Logic\User\Console\FavoriteList as FavoriteListService; use App\Services\Logic\User\Console\FavoriteList as FavoriteListService;
use App\Services\Logic\User\Console\FriendList as FriendListService; use App\Services\Logic\User\Console\FriendList as FriendListService;
use App\Services\Logic\User\Console\GroupList as GroupListService; use App\Services\Logic\User\Console\GroupList as GroupListService;
use App\Services\Logic\User\Console\OrderList as OrderListService; use App\Services\Logic\User\Console\OrderList as OrderListService;
use App\Services\Logic\User\Console\PointRedeemList as PointRedeemListService;
use App\Services\Logic\User\Console\ProfileInfo as ProfileInfoService; use App\Services\Logic\User\Console\ProfileInfo as ProfileInfoService;
use App\Services\Logic\User\Console\ProfileUpdate as ProfileUpdateService; use App\Services\Logic\User\Console\ProfileUpdate as ProfileUpdateService;
use App\Services\Logic\User\Console\RefundList as RefundListService; use App\Services\Logic\User\Console\RefundList as RefundListService;
@ -67,6 +70,19 @@ class UserConsoleController extends Controller
$this->view->setVar('user', $user); $this->view->setVar('user', $user);
} }
/**
* @Get("/contact", name="home.uc.contact")
*/
public function contactAction()
{
$service = new ContactInfoService();
$contact = $service->handle();
$this->view->pick('user/console/contact');
$this->view->setVar('contact', $contact);
}
/** /**
* @Get("/account", name="home.uc.account") * @Get("/account", name="home.uc.account")
*/ */
@ -182,6 +198,19 @@ class UserConsoleController extends Controller
$this->view->setVar('pager', $pager); $this->view->setVar('pager', $pager);
} }
/**
* @Get("/point_redeems", name="home.uc.point_redeems")
*/
public function pointRedeemsAction()
{
$service = new PointRedeemListService();
$pager = $service->handle();
$this->view->pick('user/console/point_redeems');
$this->view->setVar('pager', $pager);
}
/** /**
* @Get("/friends", name="home.uc.friends") * @Get("/friends", name="home.uc.friends")
*/ */
@ -249,6 +278,20 @@ class UserConsoleController extends Controller
return $this->jsonSuccess($content); return $this->jsonSuccess($content);
} }
/**
* @Post("/contact/update", name="home.uc.update_contact")
*/
public function updateContactAction()
{
$service = new ContactUpdateService();
$service->handle();
$content = ['msg' => '更新收货信息成功'];
return $this->jsonSuccess($content);
}
/** /**
* @Post("/connect/{id:[0-9]+}/delete", name="home.uc.unconnect") * @Post("/connect/{id:[0-9]+}/delete", name="home.uc.unconnect")
*/ */

View File

@ -6,9 +6,10 @@ use App\Models\User as UserModel;
use App\Repos\User as UserRepo; use App\Repos\User as UserRepo;
use App\Services\Auth\Home as AuthService; use App\Services\Auth\Home as AuthService;
use App\Services\Logic\Account\Register as RegisterService; use App\Services\Logic\Account\Register as RegisterService;
use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService;
use App\Validators\Account as AccountValidator; use App\Validators\Account as AccountValidator;
use App\Validators\Captcha as CaptchaValidator; use App\Validators\Captcha as CaptchaValidator;
use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
class Account extends Service class Account extends Service
{ {
@ -35,6 +36,8 @@ class Account extends Service
$this->auth->saveAuthInfo($user); $this->auth->saveAuthInfo($user);
$this->fireAfterRegisterEvent($user);
return $user; return $user;
} }
@ -50,9 +53,9 @@ class Account extends Service
$validator->checkCode($post['ticket'], $post['rand']); $validator->checkCode($post['ticket'], $post['rand']);
$this->handleLoginNotice($user);
$this->auth->saveAuthInfo($user); $this->auth->saveAuthInfo($user);
$this->fireAfterLoginEvent($user);
} }
public function loginByVerify() public function loginByVerify()
@ -63,21 +66,48 @@ class Account extends Service
$user = $validator->checkVerifyLogin($post['account'], $post['verify_code']); $user = $validator->checkVerifyLogin($post['account'], $post['verify_code']);
$this->handleLoginNotice($user);
$this->auth->saveAuthInfo($user); $this->auth->saveAuthInfo($user);
$this->fireAfterLoginEvent($user);
} }
public function logout() public function logout()
{ {
$user = $this->getLoginUser();
$this->auth->clearAuthInfo(); $this->auth->clearAuthInfo();
$this->fireAfterLogoutEvent($user);
} }
protected function handleLoginNotice(UserModel $user) protected function fireAfterRegisterEvent(UserModel $user)
{ {
$service = new AccountLoginNoticeService(); /**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$service->createTask($user); $eventsManager->fire('account:afterRegister', $this, $user);
}
protected function fireAfterLoginEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('account:afterLogin', $this, $user);
}
protected function fireAfterLogoutEvent(UserModel $user)
{
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('account:afterLogout', $this, $user);
} }
} }

View File

@ -0,0 +1,9 @@
{%- macro gift_type_info(value) %}
{% if value == 1 %}
<span class="layui-badge layui-bg-green type">课程</span>
{% elseif value == 2 %}
<span class="layui-badge layui-bg-blue type">商品</span>
{% elseif value == 3 %}
<span class="layui-badge layui-bg-cyan type">现金</span>
{% endif %}
{%- endmacro %}

View File

@ -32,6 +32,9 @@
<li class="layui-nav-item"> <li class="layui-nav-item">
<a href="javascript:" class="nav-search" data-type="{{ s_type }}" data-query="{{ s_query }}" data-url="{{ s_url }}"><i class="layui-icon layui-icon-search"></i> 搜索</a> <a href="javascript:" class="nav-search" data-type="{{ s_type }}" data-query="{{ s_query }}" data-url="{{ s_url }}"><i class="layui-icon layui-icon-search"></i> 搜索</a>
</li> </li>
<li class="layui-nav-item">
<a href="{{ url({'for':'home.point_gift.list'}) }}" class="nav-point-gift"><i class="layui-icon layui-icon-gift"></i> 积分</a>
</li>
<li class="layui-nav-item"> <li class="layui-nav-item">
<a href="{{ url({'for':'home.vip.index'}) }}" class="nav-vip"><i class="layui-icon layui-icon-diamond"></i> 会员</a> <a href="{{ url({'for':'home.vip.index'}) }}" class="nav-vip"><i class="layui-icon layui-icon-diamond"></i> 会员</a>
</li> </li>

View File

@ -0,0 +1,20 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set pager_url = url({'for':'home.point_gift.pager'}) %}
<div class="layui-breadcrumb breadcrumb">
<a href="/">首页</a>
<a><cite>积分兑换</cite></a>
</div>
<div id="gift-list" data-url="{{ pager_url }}"></div>
{% endblock %}
{% block include_js %}
{{ js_include('home/js/point.gift.list.js') }}
{% endblock %}

View File

@ -0,0 +1,31 @@
{{ partial('macros/point') }}
{% if pager.total_pages > 0 %}
<div class="course-list clearfix">
<div class="layui-row layui-col-space20">
{% for item in pager.items %}
{% set gift_url = url({'for':'home.point_gift.show','id':item.id}) %}
<div class="layui-col-md3">
<div class="course-card">
<div class="cover">
<a href="{{ gift_url }}">
<img src="{{ item.cover }}" alt="item.name">
</a>
</div>
<div class="info">
<div class="title layui-elip">
<a href="{{ gift_url }}">{{ item.name }}</a>
</div>
<div class="meta">
{{ gift_type_info(item.type) }}
<span class="price">{{ item.point }} 积分</span>
<span>{{ item.redeem_count }} 人兑换</span>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{{ partial('partials/pager_ajax') }}
{% endif %}

View File

@ -0,0 +1,89 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set gift_redeem_url = url({'for':'home.point_redeem.create'}) %}
{% set gift_list_url = url({'for':'home.point_gift.list'}) %}
<div class="breadcrumb">
<span class="layui-breadcrumb">
<a href="/">首页</a>
<a href="{{ gift_list_url }}">积分兑换</a>
<a><cite>{{ gift.name }}</cite></a>
</span>
</div>
<div class="layout-main clearfix">
<div class="layout-content">
<div class="layui-card">
<div class="layui-card-header">商品信息</div>
<div class="layui-card-body">
<div class="gift-meta clearfix">
<div class="cover">
<img src="{{ gift.cover }}!cover_270" alt="{{ gift.name }}">
</div>
<div class="info">
<p class="item">{{ gift.name }}</p>
<p class="item stats">
<span class="key">兑换价格</span>
<span class="price">{{ gift.point }} 积分</span>
<span class="key">兑换人次</span>
<span class="value">{{ gift.redeem_count }}</span>
</p>
<p class="item">
<button class="layui-btn layui-bg-red btn-redeem" data-id="{{ gift.id }}" data-url="{{ gift_redeem_url }}">立即兑换</button>
</p>
</div>
</div>
</div>
</div>
<div class="layui-card">
<div class="layui-card-header">商品详情</div>
<div class="layui-card-body">
<div class="gift-details markdown-body">{{ gift.details }}</div>
</div>
</div>
</div>
<div class="layout-sidebar">
<div class="sidebar">
<div class="layui-card">
<div class="layui-card-header">热门商品</div>
<div class="layui-card-body">
{% for gift in hot_gifts %}
{% set gift_url = url({'for':'home.point_gift.show','id':gift.id}) %}
<div class="sidebar-course-card clearfix">
<div class="cover">
<a href="{{ gift_url }}" title="{{ gift.name }}">
<img src="{{ gift.cover }}!cover_270" alt="{{ gift.name }}">
</a>
</div>
<div class="info">
<div class="title layui-elip">
<a href="{{ gift_url }}" title="{{ gift.name }}">{{ gift.name }}</a>
</div>
<div class="meta">
<span class="price">{{ gift.point }} 积分</span>
<span class="count">{{ gift.redeem_count }} 人兑换</span>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block link_css %}
{{ css_link('home/css/markdown.css') }}
{% endblock %}
{% block include_js %}
{{ js_include('home/js/point.gift.show.js') }}
{% endblock %}

View File

@ -0,0 +1,69 @@
{% extends 'templates/main.volt' %}
{% block content %}
{% set update_url = url({'for':'home.uc.update_contact'}) %}
<div class="layout-main clearfix">
<div class="my-sidebar">{{ partial('user/console/menu') }}</div>
<div class="my-content">
<div class="wrap">
<div class="my-nav">
<span class="title">收货信息</span>
</div>
<form class="layui-form profile-form" method="post" action="{{ update_url }}">
<div class="layui-form-item">
<label class="layui-form-label">真实姓名</label>
<div class="layui-input-block">
<input class="layui-input" type="text" name="name" value="{{ contact.name }}" lay-verify="required">
</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="phone" value="{{ contact.phone }}" lay-verify="phone">
</div>
</div>
<div class="layui-form-item" id="area-picker" style="margin-bottom: 25px;">
<div class="layui-form-label">所在地区</div>
<div class="layui-input-inline" style="width: 200px;">
<select name="address[province]" class="province-selector" data-value="{{ contact.add_province }}" lay-verify="required">
<option value="">请选择省</option>
</select>
</div>
<div class="layui-input-inline" style="width: 200px;">
<select name="address[city]" class="city-selector" data-value="{{ contact.add_city }}" lay-verify="required">
<option value="">请选择市</option>
</select>
</div>
<div class="layui-input-inline" style="width: 200px;">
<select name="address[county]" class="county-selector" data-value="{{ contact.add_county }}">
<option value="">请选择区</option>
</select>
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">详细地址</label>
<div class="layui-input-block">
<textarea class="layui-textarea" name="address[other]" lay-verify="required">{{ contact.add_other }}</textarea>
</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" lay-filter="go">提交</button>
<button class="layui-btn layui-btn-primary" type="reset">重置</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block include_js %}
{{ js_include('home/js/user.console.contact.js') }}
{% endblock %}

View File

@ -51,6 +51,7 @@
<div class="layui-card-body"> <div class="layui-card-body">
<ul class="my-menu"> <ul class="my-menu">
<li><a href="{{ url({'for':'home.uc.profile'}) }}">个人信息</a></li> <li><a href="{{ url({'for':'home.uc.profile'}) }}">个人信息</a></li>
<li><a href="{{ url({'for':'home.uc.contact'}) }}">收货地址</a></li>
<li><a href="{{ url({'for':'home.uc.account'}) }}">帐号安全</a></li> <li><a href="{{ url({'for':'home.uc.account'}) }}">帐号安全</a></li>
{% if wechat_oa.enabled == 1 %} {% if wechat_oa.enabled == 1 %}
<li><a href="{{ url({'for':'home.uc.subscribe'}) }}">关注订阅</a></li> <li><a href="{{ url({'for':'home.uc.subscribe'}) }}">关注订阅</a></li>

42
app/Listeners/Account.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace App\Listeners;
use App\Models\User as UserModel;
use App\Services\Logic\Notice\AccountLogin as AccountLoginNoticeService;
use App\Services\Logic\Point\PointHistory as PointHistoryService;
use Phalcon\Events\Event;
class Account extends Listener
{
public function afterRegister(Event $event, $source, UserModel $user)
{
$this->handleRegisterPoint($user);
}
public function afterLogin(Event $event, $source, UserModel $user)
{
$this->handleLoginNotice($user);
}
public function afterLogout(Event $event, $source, UserModel $user)
{
}
protected function handleRegisterPoint(UserModel $user)
{
$service = new PointHistoryService();
$service->handleAccountRegister($user);
}
protected function handleLoginNotice(UserModel $user)
{
$service = new AccountLoginNoticeService();
$service->createTask($user);
}
}

View File

@ -8,6 +8,28 @@ use Phalcon\Mvc\User\Plugin as UserPlugin;
class Listener extends UserPlugin class Listener extends UserPlugin
{ {
public function getConfig()
{
$appService = new AppService();
return $appService->getConfig();
}
public function getCache()
{
$appService = new AppService();
return $appService->getCache();
}
public function getRedis()
{
$appService = new AppService();
return $appService->getRedis();
}
public function getLogger($channel = null) public function getLogger($channel = null)
{ {
$appService = new AppService(); $appService = new AppService();

116
app/Listeners/Site.php Normal file
View File

@ -0,0 +1,116 @@
<?php
namespace App\Listeners;
use App\Library\Utils\Lock as LockUtil;
use App\Models\Online as OnlineModel;
use App\Models\User as UserModel;
use App\Repos\Online as OnlineRepo;
use App\Services\Logic\Point\PointHistory as PointHistoryService;
use App\Traits\Client as ClientTrait;
use Phalcon\Events\Event;
class Site extends Listener
{
use ClientTrait;
/**
* 访问站点
*
* @param Event $event
* @param $source
* @param UserModel $user
*/
public function view(Event $event, $source, UserModel $user)
{
if ($user->id > 0) {
$this->handleOnline($user);
$this->handleVisitPoint($user);
}
}
protected function handleOnline(UserModel $user)
{
$now = time();
if ($now - $user->active_time < 900) {
return;
}
$itemId = "user_online:{$user->id}";
$clientType = $this->getClientType();
$clientIp = $this->getClientIp();
$lockId = LockUtil::addLock($itemId);
$user->active_time = $now;
$user->update();
$onlineRepo = new OnlineRepo();
$records = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
if ($records->count() > 0) {
$online = null;
foreach ($records as $record) {
$case1 = $record->client_type == $clientType;
$case2 = $record->client_ip == $clientIp;
if ($case1 && $case2) {
$online = $record;
break;
}
}
if ($online) {
$online->active_time = $now;
$online->update();
} else {
$this->createOnline($user->id, $clientType, $clientIp);
}
} else {
$this->createOnline($user->id, $clientType, $clientIp);
}
LockUtil::releaseLock($itemId, $lockId);
}
protected function createOnline($userId, $clientType, $clientIp)
{
$online = new OnlineModel();
$online->user_id = $userId;
$online->client_type = $clientType;
$online->client_ip = $clientIp;
$online->active_time = time();
$online->create();
return $online;
}
protected function handleVisitPoint(UserModel $user)
{
$todayDate = date('Ymd');
$keyName = sprintf('site_visit:%s:%s', $user->id, $todayDate);
$cache = $this->getCache();
$content = $cache->get($keyName);
if ($content) return;
$service = new PointHistoryService();
$service->handleSiteVisit($user);
$tomorrow = strtotime($todayDate) + 86400;
$lifetime = $tomorrow - time();
$cache->save($keyName, 1, $lifetime);
}
}

View File

@ -2,10 +2,7 @@
namespace App\Listeners; namespace App\Listeners;
use App\Library\Utils\Lock as LockUtil;
use App\Models\Online as OnlineModel;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Repos\Online as OnlineRepo;
use App\Traits\Client as ClientTrait; use App\Traits\Client as ClientTrait;
use Phalcon\Events\Event; use Phalcon\Events\Event;
@ -14,64 +11,9 @@ class User extends Listener
use ClientTrait; use ClientTrait;
public function online(Event $event, $source, UserModel $user) public function view(Event $event, $source, UserModel $user)
{ {
$itemId = "user:{$user->id}";
$lockId = LockUtil::addLock($itemId);
$now = time();
$clientType = $this->getClientType();
$clientIp = $this->getClientIp();
if ($now - $user->active_time > 600) {
$user->active_time = $now;
$user->update();
$onlineRepo = new OnlineRepo();
$records = $onlineRepo->findByUserDate($user->id, date('Y-m-d'));
if ($records->count() > 0) {
$online = null;
foreach ($records as $record) {
if ($record->client_type == $clientType && $record->client_ip == $clientIp) {
$online = $record;
break;
}
}
if ($online) {
$online->active_time = $now;
$online->update();
} else {
$this->createOnline($user->id, $clientType, $clientIp);
}
} else {
$this->createOnline($user->id, $clientType, $clientIp);
}
}
LockUtil::releaseLock($itemId, $lockId);
}
protected function createOnline($userId, $clientType, $clientIp)
{
$online = new OnlineModel();
$online->user_id = $userId;
$online->client_type = $clientType;
$online->client_ip = $clientIp;
$online->active_time = time();
$online->create();
return $online;
} }
} }

View File

@ -205,22 +205,18 @@ class Chapter extends Model
$this->model = $course->model; $this->model = $course->model;
if ($this->parent_id > 0) { if ($this->parent_id > 0) {
if (empty($this->attrs)) {
$attrs = []; if ($this->model == Course::MODEL_VOD) {
$this->attrs = $this->_vod_attrs;
switch ($course->model) { } elseif ($this->model == Course::MODEL_LIVE) {
case Course::MODEL_VOD: $this->attrs = $this->_live_attrs;
$attrs = $this->_vod_attrs; } elseif ($this->model == Course::MODEL_READ) {
break; $this->attrs = $this->_read_attrs;
case Course::MODEL_LIVE: }
$attrs = $this->_live_attrs; }
break; if (is_array($this->attrs) && !empty($this->attrs)) {
case Course::MODEL_READ: $this->attrs = kg_json_encode($this->attrs);
$attrs = $this->_read_attrs;
break;
} }
$this->attrs = kg_json_encode($attrs);
} }
$this->create_time = time(); $this->create_time = time();

View File

@ -271,18 +271,18 @@ class Course extends Model
public function beforeCreate() public function beforeCreate()
{ {
$attrs = []; if (empty($this->attrs)) {
if ($this->model == self::MODEL_VOD) {
$this->attrs = $this->_vod_attrs;
} elseif ($this->model == self::MODEL_LIVE) {
$this->attrs = $this->_live_attrs;
} elseif ($this->model == self::MODEL_READ) {
$this->attrs = $this->_read_attrs;
}
}
switch ($this->model) { if (is_array($this->attrs) && !empty($this->attrs)) {
case Course::MODEL_VOD: $this->attrs = kg_json_encode($this->attrs);
$attrs = $this->_vod_attrs;
break;
case Course::MODEL_LIVE:
$attrs = $this->_live_attrs;
break;
case Course::MODEL_READ:
$attrs = $this->_read_attrs;
break;
} }
if (empty($this->cover)) { if (empty($this->cover)) {
@ -298,10 +298,6 @@ class Course extends Model
$this->details = ''; $this->details = '';
} }
if (!empty($attrs)) {
$this->attrs = kg_json_encode($attrs);
}
$this->create_time = time(); $this->create_time = time();
} }

View File

@ -20,6 +20,8 @@ class CourseUser extends Model
const SOURCE_CHARGE = 2; // 付费 const SOURCE_CHARGE = 2; // 付费
const SOURCE_VIP = 3; // 会员 const SOURCE_VIP = 3; // 会员
const SOURCE_IMPORT = 4; // 导入 const SOURCE_IMPORT = 4; // 导入
const SOURCE_POINT_REDEEM = 5; // 积分兑换
const SOURCE_LUCKY_REDEEM = 6; // 抽奖兑换
/** /**
* 主键编号 * 主键编号

235
app/Models/PointGift.php Normal file
View File

@ -0,0 +1,235 @@
<?php
namespace App\Models;
use App\Caches\MaxPointGiftId as MaxPointGiftIdCache;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
use Phalcon\Text;
class PointGift extends Model
{
/**
* 礼物类型
*/
const TYPE_COURSE = 1; // 课程
const TYPE_GOODS = 2; // 商品
const TYPE_CASH = 3; // 现金
/**
* 课程扩展属性
*
* @var array
*/
protected $_course_attrs = [
'id' => 0,
'title' => '',
'market_price' => 0,
'study_expiry_time' => 0,
];
/**
* 商品扩展属性
*
* @var array
*/
protected $_goods_attrs = [
'source' => '',
'price' => 0,
'url' => '',
];
/**
* 现金扩展属性
*
* @var array
*/
protected $_cash_attrs = ['amount' => 0];
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 名称
*
* @var string
*/
public $name = '';
/**
* 封面
*
* @var string
*/
public $cover = '';
/**
* 详情
*
* @var string
*/
public $details = '';
/**
* 属性
*
* @var string|array
*/
public $attrs = [];
/**
* 类型
*
* @var int
*/
public $type = 0;
/**
* 库存
*
* @var int
*/
public $stock = 0;
/**
* 所需积分
*
* @var int
*/
public $point = 0;
/**
* 发布标识
*
* @var int
*/
public $published = 0;
/**
* 删除标识
*
* @var int
*/
public $deleted = 0;
/**
* 兑换数
*
* @var int
*/
public $redeem_count = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_point_gift';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
if (empty($this->attrs)) {
if ($this->type == self::TYPE_COURSE) {
$this->attrs = $this->_course_attrs;
} elseif ($this->type == self::TYPE_GOODS) {
$this->attrs = $this->_goods_attrs;
} elseif ($this->type == self::TYPE_CASH) {
$this->attrs = $this->_cash_attrs;
}
}
if (is_array($this->attrs) && !empty($this->attrs)) {
$this->attrs = kg_json_encode($this->attrs);
}
if (empty($this->cover)) {
$this->cover = kg_default_cover_path();
} elseif (Text::startsWith($this->cover, 'http')) {
$this->cover = self::getCoverPath($this->cover);
}
$this->create_time = time();
}
public function beforeUpdate()
{
if (Text::startsWith($this->cover, 'http')) {
$this->cover = self::getCoverPath($this->cover);
}
if (is_array($this->attrs) && !empty($this->attrs)) {
$this->attrs = kg_json_encode($this->attrs);
}
if ($this->deleted == 1) {
$this->published = 0;
}
$this->update_time = time();
}
public function afterCreate()
{
$cache = new MaxPointGiftIdCache();
$cache->rebuild();
}
public function afterFetch()
{
if (!Text::startsWith($this->cover, 'http')) {
$this->cover = kg_cos_cover_url($this->cover);
}
if (is_string($this->attrs) && !empty($this->attrs)) {
$this->attrs = json_decode($this->attrs, true);
}
}
public static function getCoverPath($url)
{
if (Text::startsWith($url, 'http')) {
return parse_url($url, PHP_URL_PATH);
}
return $url;
}
public static function types()
{
return [
self::TYPE_COURSE => '课程',
self::TYPE_GOODS => '商品',
self::TYPE_CASH => '现金',
];
}
}

133
app/Models/PointHistory.php Normal file
View File

@ -0,0 +1,133 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class PointHistory extends Model
{
/**
* 事件类型
*/
const EVENT_ORDER_CONSUME = 1; // 订单消费
const EVENT_POINT_REDEEM = 2; // 积分兑换
const EVENT_POINT_REFUND = 3; // 积分退款
const EVENT_ACCOUNT_REGISTER = 4; // 帐号注册
const EVENT_SITE_VISIT = 5; // 站点访问
const EVENT_LESSON_LEARNING = 6; // 课时学习
const EVENT_COURSE_REVIEW = 7; // 课程评价
const EVENT_GROUP_DISCUSS = 8; // 群组讨论
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 用户编号
*
* @var int
*/
public $user_id = 0;
/**
* 用户名称
*
* @var int
*/
public $user_name = '';
/**
* 事件编号
*
* @var int
*/
public $event_id = 0;
/**
* 事件类型
*
* @var int
*/
public $event_type = '';
/**
* 事件内容
*
* @var string|array
*/
public $event_info = [];
/**
* 事件积分
*
* @var int
*/
public $event_point = 0;
/**
* 删除标识
*
* @var int
*/
public $deleted = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_point_history';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeCreate()
{
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
public static function eventTypes()
{
return [
self::EVENT_ORDER_CONSUME => '订单消费',
self::EVENT_POINT_REDEEM => '积分兑换',
self::EVENT_POINT_REFUND => '积分退款',
self::EVENT_ACCOUNT_REGISTER => '用户注册',
self::EVENT_SITE_VISIT => '用户登录',
self::EVENT_LESSON_LEARNING => '课时学习',
self::EVENT_COURSE_REVIEW => '课程评价',
self::EVENT_GROUP_DISCUSS => '群组讨论',
];
}
}

130
app/Models/PointRedeem.php Normal file
View File

@ -0,0 +1,130 @@
<?php
namespace App\Models;
class PointRedeem extends Model
{
/**
* 状态类型
*/
const STATUS_PENDING = 1; // 待处理
const STATUS_FINISHED = 2; // 已完成
const STATUS_FAILED = 3; // 已失败
/**
* 主键编号
*
* @var int
*/
public $id = 0;
/**
* 用户编号
*
* @var int
*/
public $user_id = 0;
/**
* 用户名称
*
* @var string
*/
public $user_name = '';
/**
* 礼品编号
*
* @var int
*/
public $gift_id = 0;
/**
* 礼品名称
*
* @var string
*/
public $gift_name = '';
/**
* 礼品类型
*
* @var int
*/
public $gift_type = 0;
/**
* 礼品积分
*
* @var int
*/
public $gift_point = 0;
/**
* 联系人
*
* @var string
*/
public $contact_name = '';
/**
* 联系电话
*
* @var string
*/
public $contact_phone = '';
/**
* 联系地址
*
* @var string
*/
public $contact_address = '';
/**
* 备注内容
*
* @var string
*/
public $remark = '';
/**
* 兑换状态
*
* @var int
*/
public $status = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_point_redeem';
}
public function beforeCreate()
{
$this->status = self::STATUS_PENDING;
$this->create_time = time();
}
public function beforeUpdate()
{
$this->update_time = time();
}
}

View File

@ -10,6 +10,8 @@ class Task extends Model
*/ */
const TYPE_DELIVER = 1; // 发货 const TYPE_DELIVER = 1; // 发货
const TYPE_REFUND = 2; // 退款 const TYPE_REFUND = 2; // 退款
const TYPE_POINT_GIFT_AWARD = 3; // 积分礼品派发
const TYPE_LUCKY_GIFT_AWARD = 4; // 抽奖礼品派发
const TYPE_NOTICE_ACCOUNT_LOGIN = 11; // 帐号登录通知 const TYPE_NOTICE_ACCOUNT_LOGIN = 11; // 帐号登录通知
const TYPE_NOTICE_LIVE_BEGIN = 12; // 直播开始通知 const TYPE_NOTICE_LIVE_BEGIN = 12; // 直播开始通知

View File

@ -0,0 +1,78 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class UserBalance extends Model
{
/**
* 用户编号(主键)
*
* @var int
*/
public $user_id = 0;
/**
* 可用现金(元)
*
* @var float
*/
public $cash = 0.00;
/**
* 可用积分
*
* @var int
*/
public $point = 0;
/**
* 删除标识
*
* @var int
*/
public $deleted = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_user_balance';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeSave()
{
if (empty($this->create_time)) {
$this->create_time = time();
}
$this->update_time = time();
}
}

107
app/Models/UserContact.php Normal file
View File

@ -0,0 +1,107 @@
<?php
namespace App\Models;
use Phalcon\Mvc\Model\Behavior\SoftDelete;
class UserContact extends Model
{
/**
* 用户编号(主键)
*
* @var int
*/
public $user_id = 0;
/**
* 姓名
*
* @var string
*/
public $name = '';
/**
* 手机
*
* @var string
*/
public $phone = '';
/**
* 地址(省)
*
* @var string
*/
public $add_province = '';
/**
* 地址(市)
*
* @var string
*/
public $add_city = '';
/**
* 地址(区)
*
* @var string
*/
public $add_county = '';
/**
* 地址(详)
*
* @var string
*/
public $add_other = '';
/**
* 删除标识
*
* @var int
*/
public $deleted = 0;
/**
* 创建时间
*
* @var int
*/
public $create_time = 0;
/**
* 更新时间
*
* @var int
*/
public $update_time = 0;
public function getSource(): string
{
return 'kg_user_contact';
}
public function initialize()
{
parent::initialize();
$this->addBehavior(
new SoftDelete([
'field' => 'deleted',
'value' => 1,
])
);
}
public function beforeSave()
{
if (empty($this->create_time)) {
$this->create_time = time();
}
$this->update_time = time();
}
}

87
app/Repos/PointGift.php Normal file
View File

@ -0,0 +1,87 @@
<?php
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\PointGift as PointGiftModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class PointGift extends Repository
{
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
$builder->from(PointGiftModel::class);
$builder->where('1 = 1');
if (!empty($where['id'])) {
$builder->andWhere('id = :id:', ['id' => $where['id']]);
}
if (!empty($where['type'])) {
$builder->andWhere('type = :type:', ['type' => $where['type']]);
}
if (!empty($where['name'])) {
$builder->andWhere('name LIKE :name:', ['name' => "%{$where['name']}%"]);
}
if (isset($where['published'])) {
$builder->andWhere('published = :published:', ['published' => $where['published']]);
}
if (isset($where['deleted'])) {
$builder->andWhere('deleted = :deleted:', ['deleted' => $where['deleted']]);
}
switch ($sort) {
case 'popular':
$orderBy = 'redeem_count DESC';
break;
default:
$orderBy = 'id DESC';
break;
}
$builder->orderBy($orderBy);
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
/**
* @param int $id
* @return PointGiftModel|Model|bool
*/
public function findById($id)
{
return PointGiftModel::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id],
]);
}
/**
* @param array $ids
* @param string|array $columns
* @return ResultsetInterface|Resultset|PointGiftModel[]
*/
public function findByIds($ids, $columns = '*')
{
return PointGiftModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
}
}

104
app/Repos/PointHistory.php Normal file
View File

@ -0,0 +1,104 @@
<?php
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\PointHistory as PointHistoryModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class PointHistory extends Repository
{
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
$builder->from(PointHistoryModel::class);
$builder->where('1 = 1');
if (!empty($where['user_id'])) {
$builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
}
if (!empty($where['event_id'])) {
$builder->andWhere('event_id = :event_id:', ['event_id' => $where['event_id']]);
}
if (!empty($where['event_type'])) {
$builder->andWhere('event_type = :event_type:', ['event_type' => $where['event_type']]);
}
switch ($sort) {
default:
$orderBy = 'id DESC';
break;
}
$builder->orderBy($orderBy);
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
/**
* @param int $eventId
* @param int $eventType
* @return PointHistoryModel|Model|bool
*/
public function findEventHistory($eventId, $eventType)
{
return PointHistoryModel::findFirst([
'conditions' => 'event_id = ?1 AND event_type = ?2',
'bind' => [1 => $eventId, 2 => $eventType],
]);
}
/**
* @param int $eventId
* @param int $eventType
* @return PointHistoryModel|Model|bool
*/
public function findDailyEventHistory($eventId, $eventType)
{
$createTime = strtotime(date('Y-m-d'));
return PointHistoryModel::findFirst([
'conditions' => 'event_id = ?1 AND event_type = ?2 AND create_time > ?3',
'bind' => [1 => $eventId, 2 => $eventType, 3 => $createTime],
]);
}
/**
* @param int $id
* @return PointHistoryModel|Model|bool
*/
public function findById($id)
{
return PointHistoryModel::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id],
]);
}
/**
* @param array $ids
* @param string|array $columns
* @return ResultsetInterface|Resultset|PointHistoryModel[]
*/
public function findByIds($ids, $columns = '*')
{
return PointHistoryModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
}
}

76
app/Repos/PointRedeem.php Normal file
View File

@ -0,0 +1,76 @@
<?php
namespace App\Repos;
use App\Library\Paginator\Adapter\QueryBuilder as PagerQueryBuilder;
use App\Models\PointRedeem as PointRedeemModel;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface;
class PointRedeem extends Repository
{
public function paginate($where = [], $sort = 'latest', $page = 1, $limit = 15)
{
$builder = $this->modelsManager->createBuilder();
$builder->from(PointRedeemModel::class);
$builder->where('1 = 1');
if (!empty($where['user_id'])) {
$builder->andWhere('user_id = :user_id:', ['user_id' => $where['user_id']]);
}
if (!empty($where['gift_id'])) {
$builder->andWhere('gift_id = :gift_id:', ['gift_id' => $where['gift_id']]);
}
if (!empty($where['gift_type'])) {
$builder->andWhere('gift_type = :gift_type:', ['gift_type' => $where['gift_type']]);
}
switch ($sort) {
default:
$orderBy = 'id DESC';
break;
}
$builder->orderBy($orderBy);
$pager = new PagerQueryBuilder([
'builder' => $builder,
'page' => $page,
'limit' => $limit,
]);
return $pager->paginate();
}
/**
* @param int $id
* @return PointRedeemModel|Model|bool
*/
public function findById($id)
{
return PointRedeemModel::findFirst([
'conditions' => 'id = :id:',
'bind' => ['id' => $id],
]);
}
/**
* @param array $ids
* @param string|array $columns
* @return ResultsetInterface|Resultset|PointRedeemModel[]
*/
public function findByIds($ids, $columns = '*')
{
return PointRedeemModel::query()
->columns($columns)
->inWhere('id', $ids)
->execute();
}
}

View File

@ -7,6 +7,8 @@ use App\Models\CourseFavorite as CourseFavoriteModel;
use App\Models\CourseUser as CourseUserModel; use App\Models\CourseUser as CourseUserModel;
use App\Models\ImUser as ImUserModel; use App\Models\ImUser as ImUserModel;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Models\UserBalance as UserBalanceModel;
use App\Models\UserContact as UserContactModel;
use Phalcon\Mvc\Model; use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Resultset; use Phalcon\Mvc\Model\Resultset;
use Phalcon\Mvc\Model\ResultsetInterface; use Phalcon\Mvc\Model\ResultsetInterface;
@ -104,6 +106,30 @@ class User extends Repository
->execute(); ->execute();
} }
/**
* @param int $userId
* @return UserBalanceModel|Model
*/
public function findUserBalance($userId)
{
return UserBalanceModel::findFirst([
'conditions' => 'user_id = :user_id:',
'bind' => ['user_id' => $userId],
]);
}
/**
* @param int $userId
* @return UserContactModel|Model
*/
public function findUserContact($userId)
{
return UserContactModel::findFirst([
'conditions' => 'user_id = :user_id:',
'bind' => ['user_id' => $userId],
]);
}
/** /**
* @param int $id * @param int $id
* @return ImUserModel|Model|bool * @return ImUserModel|Model|bool

View File

@ -0,0 +1,50 @@
<?php
namespace App\Services\Logic\Point;
use App\Models\PointGift;
use App\Services\Logic\CourseTrait;
use App\Services\Logic\PointGiftTrait;
use App\Services\Logic\Service;
class GiftInfo extends Service
{
use CourseTrait;
use PointGiftTrait;
public function handle($id)
{
$gift = $this->checkGift($id);
if ($gift->type == PointGift::TYPE_COURSE) {
$gift = $this->getCourseGift($gift);
}
$gift->details = kg_parse_markdown($gift->details);
return [
'id' => $gift->id,
'name' => $gift->name,
'cover' => $gift->cover,
'details' => $gift->details,
'type' => $gift->type,
'point' => $gift->point,
'redeem_count' => $gift->redeem_count,
];
}
protected function getCourseGift(PointGift $gift)
{
$courseId = $gift->attrs['id'] ?? 0;
$course = $this->checkCourse($courseId);
$gift->name = $course->title;
$gift->cover = $course->cover;
$gift->details = $course->details;
return $gift;
}
}

View File

@ -0,0 +1,61 @@
<?php
namespace App\Services\Logic\Point;
use App\Library\Paginator\Query as PagerQuery;
use App\Repos\PointGift as PointGiftRepo;
use App\Services\Logic\Service;
class GiftList extends Service
{
public function handle()
{
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['published'] = 1;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$giftRepo = new PointGiftRepo();
$pager = $giftRepo->paginate($params, $sort, $page, $limit);
return $this->handleGifts($pager);
}
protected function handleGifts($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$baseUrl = kg_cos_url();
$items = [];
foreach ($pager->items->toArray() as $gift) {
$gift['cover'] = $baseUrl . $gift['cover'];
$items[] = [
'id' => $gift['id'],
'name' => $gift['name'],
'cover' => $gift['cover'],
'details' => $gift['details'],
'type' => $gift['type'],
'point' => $gift['point'],
'redeem_count' => $gift['redeem_count'],
];
}
$pager->items = $items;
return $pager;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace App\Services\Logic\Point;
use App\Caches\PointHotGiftList;
use App\Services\Logic\Service;
class HotGiftList extends Service
{
public function handle()
{
$cache = new PointHotGiftList();
$cache->setLimit(5);
return $cache->get() ?: [];
}
}

View File

@ -0,0 +1,438 @@
<?php
namespace App\Services\Logic\Point;
use App\Models\ChapterUser as ChapterUserModel;
use App\Models\Order as OrderModel;
use App\Models\PointHistory as PointHistoryModel;
use App\Models\PointRedeem as PointRedeemModel;
use App\Models\Review as ReviewModel;
use App\Models\User as UserModel;
use App\Models\UserBalance as UserBalanceModel;
use App\Repos\Chapter as ChapterRepo;
use App\Repos\Course as CourseRepo;
use App\Repos\PointHistory as PointHistoryRepo;
use App\Repos\User as UserRepo;
use App\Services\Logic\Service;
class PointHistory extends Service
{
public function handleOrderConsume(OrderModel $order)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$ruleEnabled = $setting['consume_rule']['enabled'] ?? 0;
if ($ruleEnabled == 0) return;
$ruleRate = $setting['consume_rule']['rate'] ?? 0;
if ($ruleRate <= 0) return;
$eventId = $order->id;
$eventType = PointHistoryModel::EVENT_ORDER_CONSUME;
$eventPoint = $ruleRate * $order->amount;
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findEventHistory($eventId, $eventType);
if ($history) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($order->owner_id);
$eventInfo = [
'order' => [
'sn' => $order->sn,
'subject' => $order->subject,
'amount' => $order->amount,
]
];
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_point = $eventPoint;
$history->event_info = $eventInfo;
$this->handlePointHistory($history);
}
public function handlePointRedeem(PointRedeemModel $redeem)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventId = $redeem->id;
$eventType = PointHistoryModel::EVENT_POINT_REDEEM;
$eventPoint = 0 - $redeem->gift_point;
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findEventHistory($eventId, $eventType);
if ($history) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($redeem->user_id);
$eventInfo = [
'point_redeem' => [
'id' => $redeem->id,
'gift_name' => $redeem->gift_name,
'gift_type' => $redeem->gift_type,
'gift_point' => $redeem->gift_point,
]
];
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_point = $eventPoint;
$history->event_info = $eventInfo;
$this->handlePointHistory($history);
}
public function handlePointRefund(PointRedeemModel $redeem)
{
$eventId = $redeem->id;
$eventType = PointHistoryModel::EVENT_POINT_REFUND;
$eventPoint = $redeem->gift_point;
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findEventHistory($eventId, $eventType);
if ($history) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($redeem->user_id);
$eventInfo = [
'point_redeem' => [
'id' => $redeem->id,
'gift_name' => $redeem->gift_name,
'gift_type' => $redeem->gift_type,
'gift_point' => $redeem->gift_point,
]
];
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_point = $eventPoint;
$history->event_info = $eventInfo;
$this->handlePointHistory($history);
}
public function handleSiteVisit(UserModel $user)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventRule = json_decode($setting['event_rule'], true);
$eventEnabled = $eventRule['site_visit']['enabled'] ?? 0;
if ($eventEnabled == 0) return;
$eventPoint = $eventRule['site_visit']['point'] ?? 0;
if ($eventPoint <= 0) return;
$eventId = $user->id;
$eventType = PointHistoryModel::EVENT_SITE_VISIT;
$eventInfo = '每日登录';
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findDailyEventHistory($eventId, $eventType);
if ($history) return;
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_point = $eventPoint;
$history->event_info = $eventInfo;
$this->handlePointHistory($history);
}
public function handleAccountRegister(UserModel $user)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventRule = json_decode($setting['event_rule'], true);
$eventEnabled = $eventRule['account_register']['enabled'] ?? 0;
if ($eventEnabled == 0) return;
$eventPoint = $eventRule['account_register']['point'] ?? 0;
if ($eventPoint <= 0) return;
$eventId = $user->id;
$eventType = PointHistoryModel::EVENT_ACCOUNT_REGISTER;
$eventInfo = '用户注册';
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findDailyEventHistory($eventId, $eventType);
if ($history) return;
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $user->id;
$history->event_type = $eventType;
$history->event_point = $eventPoint;
$history->event_info = $eventInfo;
$this->handlePointHistory($history);
}
public function handleLessonLearning(ChapterUserModel $chapterUser)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventRule = json_decode($setting['event_rule'], true);
$eventEnabled = $eventRule['chapter_learning']['enabled'] ?? 0;
if ($eventEnabled == 0) return;
$eventPoint = $eventRule['chapter_learning']['point'] ?? 0;
if ($eventPoint <= 0) return;
$eventId = $chapterUser->id;
$eventType = PointHistoryModel::EVENT_LESSON_LEARNING;
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findEventHistory($eventId, $eventType);
if ($history) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($chapterUser->user_id);
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($chapterUser->course_id);
$chapterRepo = new ChapterRepo();
$chapter = $chapterRepo->findById($chapterUser->chapter_id);
$eventInfo = [
'course' => [
'id' => $course->id,
'title' => $course->title,
],
'chapter' => [
'id' => $chapter->id,
'title' => $chapter->title,
]
];
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_info = $eventInfo;
$history->event_point = $eventPoint;
$this->handlePointHistory($history);
}
public function handleCourseReview(ReviewModel $review)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventRule = json_decode($setting['event_rule'], true);
$eventEnabled = $eventRule['course_review']['enabled'] ?? 0;
if ($eventEnabled == 0) return;
$eventPoint = $eventRule['course_review']['point'] ?? 0;
if ($eventPoint <= 0) return;
$eventId = $review->id;
$eventType = PointHistoryModel::EVENT_COURSE_REVIEW;
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findEventHistory($eventId, $eventType);
if ($history) return;
$userRepo = new UserRepo();
$user = $userRepo->findById($review->owner_id);
$courseRepo = new CourseRepo();
$course = $courseRepo->findById($review->course_id);
$eventInfo = [
'course' => [
'id' => $course->id,
'title' => $course->title,
]
];
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_info = $eventInfo;
$history->event_point = $eventPoint;
$this->handlePointHistory($history);
}
public function handleGroupDiscuss(UserModel $user)
{
$setting = $this->getSettings('point');
$pointEnabled = $setting['enabled'] ?? 0;
if ($pointEnabled == 0) return;
$eventRule = json_decode($setting['event_rule'], true);
$eventEnabled = $eventRule['group_discuss']['enabled'] ?? 0;
if ($eventEnabled == 0) return;
$eventPoint = $eventRule['group_discuss']['point'] ?? 0;
if ($eventPoint <= 0) return;
$eventId = $user->id;
$eventType = PointHistoryModel::EVENT_GROUP_DISCUSS;
$eventInfo = '群组讨论';
$historyRepo = new PointHistoryRepo();
$history = $historyRepo->findEventHistory($eventId, $eventType);
if ($history) return;
$history = new PointHistoryModel();
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $eventId;
$history->event_type = $eventType;
$history->event_info = $eventInfo;
$history->event_point = $eventPoint;
$this->handlePointHistory($history);
}
protected function handlePointHistory(PointHistoryModel $history)
{
try {
$this->db->begin();
if ($history->create() === false) {
throw new \RuntimeException('Create Point History Failed');
}
$userRepo = new UserRepo();
$balance = $userRepo->findUserBalance($history->user_id);
if ($balance) {
$balance->user_id = $history->user_id;
$balance->point += $history->event_point;
$result = $balance->update();
} else {
$balance = new UserBalanceModel();
$balance->user_id = $history->user_id;
$balance->point = $history->event_point;
$result = $balance->create();
}
if ($result === false) {
throw new \RuntimeException('Save User Balance Failed');
}
$this->db->commit();
} catch (\Exception $e) {
$this->db->rollback();
$this->logException($e);
throw new \RuntimeException('sys.trans_rollback');
}
}
protected function logException(\Exception $e)
{
$logger = $this->getLogger('point');
$logger->error('Point History Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
]));
}
}

View File

@ -0,0 +1,125 @@
<?php
namespace App\Services\Logic\Point;
use App\Models\PointGift as PointGiftModel;
use App\Models\PointHistory as PointHistoryModel;
use App\Models\PointRedeem as PointRedeemModel;
use App\Models\Task as TaskModel;
use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Logic\PointGiftTrait;
use App\Services\Logic\Service;
use App\Validators\PointRedeem as PointRedeemValidator;
class PointRedeem extends Service
{
use PointGiftTrait;
public function handle()
{
$giftId = $this->request->getPost('gift_id', ['trim', 'int']);
$gift = $this->checkGift($giftId);
$user = $this->getLoginUser();
$validator = new PointRedeemValidator();
$validator->checkIfAllowRedeem($gift, $user);
$this->createPointRedeem($gift, $user);
}
protected function createPointRedeem(PointGiftModel $gift, UserModel $user)
{
$userRepo = new UserRepo();
$balance = $userRepo->findUserBalance($user->id);
try {
$this->db->begin();
$redeem = new PointRedeemModel();
$redeem->gift_id = $gift->id;
$redeem->gift_type = $gift->type;
$redeem->gift_name = $gift->name;
$redeem->gift_point = $gift->point;
$result = $redeem->create();
if ($result === false) {
throw new \RuntimeException('Create Point Redeem Failed');
}
$task = new TaskModel();
$itemInfo = [
'point_redeem' => [
'id' => $redeem->id,
'user_id' => $redeem->user_id,
'gift_id' => $redeem->gift_id,
]
];
$task->item_id = $redeem->id;
$task->item_type = TaskModel::TYPE_POINT_GIFT_AWARD;
$task->item_info = $itemInfo;
$result = $task->create();
if ($result === false) {
throw new \RuntimeException('Create Async Task Failed');
}
$history = new PointHistoryModel();
$eventInfo = [
'gift' => [
'id' => $gift->id,
'name' => $gift->name,
]
];
$history->user_id = $user->id;
$history->user_name = $user->name;
$history->event_id = $gift->id;
$history->event_type = PointHistoryModel::EVENT_POINT_REDEEM;
$history->event_point = $gift->point;
$history->event_info = $eventInfo;
$result = $history->create();
if ($result === false) {
throw new \RuntimeException('Create Point History Failed');
}
$balance->point -= $gift->point;
$result = $balance->update();
if ($result === false) {
throw new \RuntimeException('Update User Balance Failed');
}
$this->db->commit();
} catch (\Exception $e) {
$this->db->rollback();
$this->logger->error('Point Redeem Exception ' . kg_json_encode([
'code' => $e->getCode(),
'message' => $e->getMessage(),
'balance' => ['user_id' => $balance->id, 'point' => $balance->point],
'gift' => ['id' => $gift->id, 'point' => $gift->point],
]));
throw new \RuntimeException('sys.trans_rollback');
}
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace App\Services\Logic;
use App\Validators\PointGift as PointGiftValidator;
trait PointGiftTrait
{
public function checkGift($id)
{
$validator = new PointGiftValidator();
return $validator->checkGift($id);
}
}

View File

@ -0,0 +1,45 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Repos\User as UserRepo;
use App\Services\Logic\Service;
class ContactInfo extends Service
{
public function handle()
{
$user = $this->getLoginUser();
$userRepo = new UserRepo();
$contact = $userRepo->findUserContact($user->id);
if (!$contact) {
return $this->defaultContactInfo();
}
return [
'name' => $contact->name,
'phone' => $contact->phone,
'add_province' => $contact->add_province,
'add_city' => $contact->add_city,
'add_county' => $contact->add_county,
'add_other' => $contact->add_other,
];
}
protected function defaultContactInfo()
{
return [
'name' => '',
'phone' => '',
'add_province' => '',
'add_city' => '',
'add_county' => '',
'add_other' => '',
];
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Models\UserContact as UserContactModel;
use App\Services\Logic\Service;
use App\Validators\UserContact as UserContactValidator;
class ContactUpdate extends Service
{
public function handle()
{
$post = $this->request->getPost();
$user = $this->getLoginUser();
$validator = new UserContactValidator();
$contact = new UserContactModel();
$contact->name = $validator->checkName($post['name']);
$contact->phone = $validator->checkPhone($post['phone']);
$contact->add_province = $validator->checkAddProvince($post['address']['province']);
$contact->add_city = $validator->checkAddCity($post['address']['city']);
$contact->add_county = $validator->checkAddCounty($post['address']['county']);
$contact->add_other = $validator->checkAddOther($post['address']['other']);
$contact->user_id = $user->id;
$contact->save();
return $contact;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace App\Services\Logic\User\Console;
use App\Library\Paginator\Query as PagerQuery;
use App\Repos\Order as OrderRepo;
use App\Services\Logic\Service;
use App\Services\Logic\UserTrait;
class PointRedeemList extends Service
{
use UserTrait;
public function handle()
{
$user = $this->getLoginUser();
$pagerQuery = new PagerQuery();
$params = $pagerQuery->getParams();
$params['user_id'] = $user->id;
$params['deleted'] = 0;
$sort = $pagerQuery->getSort();
$page = $pagerQuery->getPage();
$limit = $pagerQuery->getLimit();
$orderRepo = new OrderRepo();
$pager = $orderRepo->paginate($params, $sort, $page, $limit);
return $this->handleOrders($pager);
}
public function handleOrders($pager)
{
if ($pager->total_items == 0) {
return $pager;
}
$items = [];
foreach ($pager->items as $item) {
$items[] = [
'id' => $item->id,
'status' => $item->status,
'create_time' => $item->create_time,
'user' => [
'id' => $item->user_id,
'name' => $item->user_name,
],
'gift' => [
'id' => $item->gift_id,
'type' => $item->gift_type,
'name' => $item->gift_name,
'point' => $item->gift_point,
],
];
}
$pager->items = $items;
return $pager;
}
}

View File

@ -2,12 +2,11 @@
namespace App\Traits; namespace App\Traits;
use App\Caches\User as UserCache;
use App\Models\User as UserModel; use App\Models\User as UserModel;
use App\Repos\User as UserRepo;
use App\Services\Auth as AuthService; use App\Services\Auth as AuthService;
use App\Validators\Validator as AppValidator; use App\Validators\Validator as AppValidator;
use Phalcon\Di as Di; use Phalcon\Di as Di;
use Phalcon\Events\Manager as EventsManager;
trait Auth trait Auth
{ {
@ -23,18 +22,9 @@ trait Auth
return $this->getGuestUser(); return $this->getGuestUser();
} }
$userRepo = new UserRepo(); $userCache = new UserCache();
$user = $userRepo->findById($authUser['id']); return $userCache->get($authUser['id']);
/**
* @var EventsManager $eventsManager
*/
$eventsManager = Di::getDefault()->getShared('eventsManager');
$eventsManager->fire('user:online', $this, $user);
return $user;
} }
/** /**
@ -48,9 +38,9 @@ trait Auth
$validator->checkAuthUser($authUser['id']); $validator->checkAuthUser($authUser['id']);
$userRepo = new UserRepo(); $userCache = new UserCache();
return $userRepo->findById($authUser['id']); return $userCache->get($authUser['id']);
} }
/** /**

View File

@ -30,13 +30,6 @@ trait Security
$validator->checkRateLimit(); $validator->checkRateLimit();
} }
public function checkApiSignature()
{
$validator = new SecurityValidator();
$validator->checkApiSignature();
}
public function isNotSafeRequest() public function isNotSafeRequest()
{ {
/** /**

View File

@ -91,7 +91,7 @@ class Help extends Validator
throw new BadRequestException('help.content_too_short'); throw new BadRequestException('help.content_too_short');
} }
if ($length > 3000) { if ($length > 30000) {
throw new BadRequestException('help.content_too_long'); throw new BadRequestException('help.content_too_long');
} }

View File

@ -77,9 +77,9 @@ class ImGroup extends Validator
return $value; return $value;
} }
public function checkAbout($name) public function checkAbout($about)
{ {
$value = $this->filter->sanitize($name, ['trim', 'string']); $value = $this->filter->sanitize($about, ['trim', 'string']);
$length = kg_strlen($value); $length = kg_strlen($value);

View File

@ -86,7 +86,7 @@ class Page extends Validator
throw new BadRequestException('page.content_too_short'); throw new BadRequestException('page.content_too_short');
} }
if ($length > 3000) { if ($length > 30000) {
throw new BadRequestException('page.content_too_long'); throw new BadRequestException('page.content_too_long');
} }

View File

@ -0,0 +1,153 @@
<?php
namespace App\Validators;
use App\Caches\MaxPointGiftId as MaxPointGiftIdCache;
use App\Caches\PointGift as PointGiftCache;
use App\Exceptions\BadRequest as BadRequestException;
use App\Library\Validators\Common as CommonValidator;
use App\Models\PointGift as PointGiftModel;
use App\Repos\PointGift as PointGiftRepo;
class PointGift extends Validator
{
/**
* @param int $id
* @return PointGiftModel
* @throws BadRequestException
*/
public function checkGiftCache($id)
{
$this->checkId($id);
$giftCache = new PointGiftCache();
$gift = $giftCache->get($id);
if (!$gift) {
throw new BadRequestException('point_gift.not_found');
}
return $gift;
}
public function checkGift($id)
{
$this->checkId($id);
$giftRepo = new PointGiftRepo();
$gift = $giftRepo->findById($id);
if (!$gift) {
throw new BadRequestException('point_gift.not_found');
}
return $gift;
}
public function checkId($id)
{
$id = intval($id);
$maxGiftIdCache = new MaxPointGiftIdCache();
$maxId = $maxGiftIdCache->get();
if ($id < 1 || $id > $maxId) {
throw new BadRequestException('point_gift.not_found');
}
}
public function checkName($name)
{
$value = $this->filter->sanitize($name, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 2) {
throw new BadRequestException('point_gift.name_too_short');
}
if ($length > 30) {
throw new BadRequestException('point_gift.name_too_long');
}
return $value;
}
public function checkDetails($details)
{
$value = $this->filter->sanitize($details, ['trim', 'string']);
$length = kg_strlen($value);
if ($length > 30000) {
throw new BadRequestException('point_gift.details_too_long');
}
return $value;
}
public function checkCover($cover)
{
$value = $this->filter->sanitize($cover, ['trim', 'string']);
if (!CommonValidator::url($value)) {
throw new BadRequestException('point_gift.invalid_cover');
}
return kg_cos_img_style_trim($value);
}
public function checkType($type)
{
$list = PointGiftModel::types();
if (!isset($list[$type])) {
throw new BadRequestException('point_gift.invalid_type');
}
return $type;
}
public function checkPoint($point)
{
$value = $this->filter->sanitize($point, ['trim', 'int']);
if ($value < 1 || $value > 999999) {
throw new BadRequestException('point_gift.invalid_point');
}
return $value;
}
public function checkStock($stock)
{
$value = $this->filter->sanitize($stock, ['trim', 'int']);
if ($value < 1 || $value > 999999) {
throw new BadRequestException('point_gift.invalid_stock');
}
return $value;
}
public function checkPublishStatus($status)
{
if (!in_array($status, [0, 1])) {
throw new BadRequestException('point_gift.invalid_publish_status');
}
return $status;
}
public function checkCourse($id)
{
$validator = new Course();
return $validator->checkCourse($id);
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest;
use App\Exceptions\BadRequest as BadRequestException;
use App\Models\Course as CourseModel;
use App\Models\PointGift as PointGiftModel;
use App\Models\User as UserModel;
use App\Repos\CourseUser as CourseUserRepo;
use App\Repos\User as UserRepo;
class PointRedeem extends Validator
{
public function checkGift($giftId)
{
$validator = new PointGift();
return $validator->checkGift($giftId);
}
public function checkIfAllowRedeem(PointGiftModel $gift, UserModel $user)
{
$this->checkPointBalance($gift, $user);
if ($gift->type == PointGiftModel::TYPE_COURSE) {
$validator = new Course();
$course = $validator->checkCourse($gift->attrs['id']);
$this->checkIfAllowRedeemCourse($course, $user);
} elseif ($gift->type == PointGiftModel::TYPE_GOODS) {
$this->checkIfAllowRedeemCommodity($user);
}
}
protected function checkIfAllowRedeemCourse(CourseModel $course, UserModel $user)
{
if ($course->published == 0) {
throw new BadRequestException('point_redeem.course_not_published');
}
if ($course->market_price == 0) {
throw new BadRequestException('point_redeem.course_free');
}
$courseUserRepo = new CourseUserRepo();
$courseUser = $courseUserRepo->findCourseUser($course->id, $user->id);
if ($courseUser->expiry_time > time()) {
throw new BadRequestException('point_redeem.course_owned');
}
}
protected function checkIfAllowRedeemCommodity(UserModel $user)
{
$userRepo = new UserRepo();
$contact = $userRepo->findUserContact($user->id);
if (!$contact) {
throw new BadRequestException('point_redeem.no_user_contact');
}
}
protected function checkPointBalance(PointGiftModel $gift, UserModel $user)
{
$userRepo = new UserRepo();
$balance = $userRepo->findUserBalance($user->id);
if (!$balance || $balance->point < $gift->point) {
throw new BadRequestException('point_redeem.no_enough_point');
}
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Validators;
use App\Exceptions\BadRequest as BadRequestException;
use App\Library\Validators\Common as CommonValidator;
use App\Repos\User as UserRepo;
class UserContact extends Validator
{
public function checkContact($id)
{
$userRepo = new UserRepo();
$user = $userRepo->findById($id);
if (!$user) {
throw new BadRequestException('user_contact.not_found');
}
return $user;
}
public function checkName($name)
{
$value = $this->filter->sanitize($name, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 2 || $length > 15) {
throw new BadRequestException('user_contact.invalid_name');
}
return $value;
}
public function checkPhone($phone)
{
$value = $this->filter->sanitize($phone, ['trim', 'string']);
if (!CommonValidator::phone($value)) {
throw new BadRequestException('user_contact.invalid_phone');
}
return $value;
}
public function checkAddProvince($province)
{
$value = $this->filter->sanitize($province, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 2 || $length > 15) {
throw new BadRequestException('user_contact.invalid_add_province');
}
return $value;
}
public function checkAddCity($city)
{
$value = $this->filter->sanitize($city, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 2 || $length > 15) {
throw new BadRequestException('user_contact.invalid_add_city');
}
return $value;
}
public function checkAddCounty($county)
{
$value = $this->filter->sanitize($county, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 2 || $length > 15) {
throw new BadRequestException('user_contact.invalid_add_county');
}
return $value;
}
public function checkAddOther($other)
{
$value = $this->filter->sanitize($other, ['trim', 'string']);
$length = kg_strlen($value);
if ($length < 5 || $length > 50) {
throw new BadRequestException('user_contact.invalid_add_other');
}
return $value;
}
}

View File

@ -222,7 +222,7 @@ $error['page.not_found'] = '单页不存在';
$error['page.title_too_short'] = '标题太短少于2个字符'; $error['page.title_too_short'] = '标题太短少于2个字符';
$error['page.title_too_long'] = '标题太长多于50个字符'; $error['page.title_too_long'] = '标题太长多于50个字符';
$error['page.content_too_short'] = '内容太短少于10个字符'; $error['page.content_too_short'] = '内容太短少于10个字符';
$error['page.content_too_long'] = '内容太长多于3000个字符)'; $error['page.content_too_long'] = '内容太长多于30000个字符)';
$error['page.invalid_publish_status'] = '无效的发布状态'; $error['page.invalid_publish_status'] = '无效的发布状态';
/** /**
@ -232,7 +232,7 @@ $error['help.not_found'] = '帮助不存在';
$error['help.title_too_short'] = '标题太短少于2个字符'; $error['help.title_too_short'] = '标题太短少于2个字符';
$error['help.title_too_long'] = '标题太长多于50个字符'; $error['help.title_too_long'] = '标题太长多于50个字符';
$error['help.content_too_short'] = '内容太短少于10个字符'; $error['help.content_too_short'] = '内容太短少于10个字符';
$error['help.content_too_long'] = '内容太长(多于60000个字符'; $error['help.content_too_long'] = '内容太长(多于30000个字符';
$error['help.invalid_priority'] = '无效的排序数值范围1-255'; $error['help.invalid_priority'] = '无效的排序数值范围1-255';
$error['help.invalid_publish_status'] = '无效的发布状态'; $error['help.invalid_publish_status'] = '无效的发布状态';
@ -244,7 +244,7 @@ $error['slide.invalid_platform'] = '无效的平台类型';
$error['slide.invalid_target'] = '无效的目标类型'; $error['slide.invalid_target'] = '无效的目标类型';
$error['slide.invalid_link'] = '无效的链接地址'; $error['slide.invalid_link'] = '无效的链接地址';
$error['slide.invalid_priority'] = '无效的排序数值范围1-255'; $error['slide.invalid_priority'] = '无效的排序数值范围1-255';
$error['slide.invalid_cover'] = '无效的封面图片'; $error['slide.invalid_cover'] = '无效的封面';
$error['slide.title_too_short'] = '标题太短少于2个字符'; $error['slide.title_too_short'] = '标题太短少于2个字符';
$error['slide.title_too_long'] = '标题太长多于50个字符'; $error['slide.title_too_long'] = '标题太长多于50个字符';
$error['slide.summary_too_long'] = '简介太长多于255个字符'; $error['slide.summary_too_long'] = '简介太长多于255个字符';
@ -356,4 +356,34 @@ $error['im_message.content_too_short'] = '消息内容太短少于1字符'
$error['im_message.content_too_long'] = '消息内容太长超过1000字符'; $error['im_message.content_too_long'] = '消息内容太长超过1000字符';
$error['im_message.self_chat'] = '不能给自己发送消息'; $error['im_message.self_chat'] = '不能给自己发送消息';
/**
* 联系信息相关
*/
$error['user_contact.invalid_name'] = '无效的用户姓名';
$error['user_contact.invalid_phone'] = '无效的手机号码';
$error['user_contact.invalid_add_province'] = '无效的地址(省)';
$error['user_contact.invalid_add_city'] = '无效的地址(市)';
$error['user_contact.invalid_add_county'] = '无效的地址(区)';
$error['user_contact.invalid_add_other'] = '无效的地址(详)';
/**
* 积分兑换相关
*/
$error['point_gift.not_found'] = '礼品不存在';
$error['point_gift.name_too_short'] = '礼品名称太短少于2字符';
$error['point_gift.name_too_long'] = '礼品名称太长超过30字符';
$error['point_gift.details_too_long'] = '礼品详情太长多于30000个字符';
$error['point_gift.invalid_cover'] = '无效的封面';
$error['point_gift.invalid_type'] = '无效的类型';
$error['point_gift.invalid_point'] = '无效的积分值范围1-999999';
$error['point_gift.invalid_stock'] = '无效的库存值范围1-999999';
$error['point_gift.invalid_publish_status'] = '无效的发布状态';
$error['point_redeem.course_not_published'] = '课程尚未发布';
$error['point_redeem.course_free'] = '课程当前免费,无需积分兑换';
$error['point_redeem.course_owned'] = '您已经拥有课程,无需积分兑换';
$error['point_redeem.no_user_contact'] = '您尚未设置收货地址,请前往用户中心设置';
$error['point_redeem.no_enough_point'] = '您的积分余额不足以抵扣此次兑换';
return $error; return $error;

View File

@ -1,11 +1,15 @@
<?php <?php
use App\Listeners\Account;
use App\Listeners\Pay; use App\Listeners\Pay;
use App\Listeners\Site;
use App\Listeners\User; use App\Listeners\User;
use App\Listeners\UserDailyCounter; use App\Listeners\UserDailyCounter;
return [ return [
'pay' => Pay::class, 'pay' => Pay::class,
'user' => User::class, 'user' => User::class,
'site' => Site::class,
'account' => Account::class,
'userDailyCounter' => UserDailyCounter::class, 'userDailyCounter' => UserDailyCounter::class,
]; ];

View File

@ -0,0 +1,58 @@
<?php
class Data202101261130 extends Phinx\Migration\AbstractMigration
{
public function up()
{
$consumeRule = ['enabled' => 1, 'rate' => 5];
$eventRule = [
'account_register' => ['enabled' => 1, 'point' => 100],
'site_visit' => ['enabled' => 1, 'point' => 10],
'course_review' => ['enabled' => 1, 'point' => 50],
'group_discuss' => ['enabled' => 1, 'point' => 10],
'lesson_learning' => ['enabled' => 1, 'point' => 10],
];
$rows = [
[
'section' => 'point',
'item_key' => 'enabled',
'item_value' => 1,
],
[
'section' => 'point',
'item_key' => 'consume_rule',
'item_value' => json_encode($consumeRule),
],
[
'section' => 'point',
'item_key' => 'event_rule',
'item_value' => json_encode($eventRule),
],
];
$this->table('kg_setting')->insert($rows)->save();
}
public function down()
{
$this->getQueryBuilder()
->delete('kg_setting')
->where(['section' => 'point'])
->execute();
}
protected function initUserBalanceData()
{
$dataQuery = $this->getQueryBuilder()->select(['id'])->from('kg_user');
$this->getQueryBuilder()
->insert(['user_id'])
->into('kg_user_balance')
->values($dataQuery)
->execute();
}
}

View File

@ -42,13 +42,6 @@ layui.use(['jquery'], function () {
value: $textarea.val() value: $textarea.val()
}); });
/**
* 取消提交表单行为
*/
$('body').on('click', '.vditor-preview__action > button', function () {
return false;
});
/** /**
* 同步编辑器内容到表单 * 同步编辑器内容到表单
*/ */

View File

@ -1816,4 +1816,45 @@
.layim-chat-status .offline { .layim-chat-status .offline {
color: gray; color: gray;
}
.gift-meta .cover {
float: left;
margin-right: 10px;
}
.gift-meta .cover img {
width: 210px;
height: 118px;
}
.gift-meta .info {
float: left;
width: 400px;
}
.gift-meta .info p {
line-height: 35px;
}
.gift-meta .info .stats {
margin-bottom: 10px;
}
.gift-meta .info span {
margin-right: 5px;
}
.gift-meta .info .value {
font-size: 16px;
color: #666;
}
.gift-meta .info .price {
font-size: 16px;
color: red;
}
.gift-details {
min-height: 450px;
} }

View File

@ -0,0 +1,9 @@
layui.use(['jquery', 'helper'], function () {
var $ = layui.jquery;
var helper = layui.helper;
var $giftList = $('#gift-list');
helper.ajaxLoadHtml($giftList.data('url'), $giftList.attr('id'));
});

View File

@ -0,0 +1,24 @@
layui.use(['jquery', 'layer'], function () {
var $ = layui.jquery;
var layer = layui.layer;
$('.btn-redeem').on('click', function () {
var url = $(this).data('url');
var data = {gift_id: $(this).data('id')};
layer.confirm('兑换不支持退换,确定要兑换该物品吗?', function () {
$.ajax({
type: 'POST',
url: url,
data: data,
success: function (res) {
layer.msg(res.msg, {icon: 1});
setTimeout(function () {
window.location.href = '/uc/redeems';
}, 3000);
}
});
});
});
});

View File

@ -0,0 +1,12 @@
layui.use(['layarea'], function () {
var layarea = layui.layarea;
layarea.render({
elem: '#area-picker',
change: function (res) {
console.log(res);
}
});
});