Files
amb_wechatapp/application/common/model/user/Subscription.php
T

548 lines
18 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\common\model\user;
use think\Model;
use app\common\model\order\Item as OrderItem;
use app\api\model\course\ColumnBind;
use app\admin\model\course\Course as CourseModel;
use think\Cache;
/**
* 用户已购课程
*/
class Subscription extends Model
{
protected $uniacid = false;
// 表名
protected $name = 'subscription';
// 自动写入时间戳字段
protected $autoWriteTimestamp = 'integer';
// 定义时间戳字段名
protected $createTime = 'createtime';
protected $updateTime = false;
protected $deleteTime = false;
/**
* 获取订阅权限
* 说明:
* - 历史逻辑会对免费课程或价格为0的课程直接返回 true,导致用户无需任何动作即可观看。
* - 现调整为:免费课程也必须由用户主动点击【加入学习】生成订阅记录(get_type=free)后才视为已订阅。
* - 付费会员卡免费权益命中(include 全部课程作用域 / 指定课程作用域)同样要求用户点击【加入学习】,
* 生成 get_type=vip 的订阅记录后才视为已订阅;当 VIP 权益失效时,该记录会被 getUserCourse 自动忽略。
* - 当课程从免费变更为收费/其他模式时,get_type=free 的订阅记录会被 getUserCourse 自动忽略,从而失效。
* @return bool
*/
public static function getSubscriptionAuth($userId, $courseId)
{
//判断课程是否存在
$buyData = \app\common\model\course\Course::getPrice($courseId);
if (!$buyData) {
return false;
}
$userGotCourse = self::getUserCourse($userId, $courseId);
return $userGotCourse;
}
/**
* 用户在当前是否拥有「通过付费会员卡免费权益免费观看该课程」的资格
* 说明:仅作为「加入学习」资格判断,不直接代表已订阅
* @param int $userId
* @param int $courseId
* @return bool
*/
public static function getVipFreeAccess($userId, $courseId)
{
$vipConfig = \app\common\model\app\Config::getConfig('vip');
if (!$vipConfig || $vipConfig['status'] != 1) {
return false;
}
$userVips = \app\common\model\app\vip\CardUser::getUserAllVipInfo($userId);
if (!$userVips) {
return false;
}
foreach ($userVips as $userVip) {
//会员卡未开启免费权益时跳过
if (empty($userVip['card']) || $userVip['card']['privilege_free'] != 1) {
continue;
}
$freeScope = isset($userVip['card']['privilege_free_scope'])
? $userVip['card']['privilege_free_scope']
: 'specify';
if ($freeScope == 'all') {
//全部课程作用域:仅 article/video/audio/live/column 类型的课程享受
if (\app\common\model\app\vip\CardPrivilege::isCourseInScopeAll($courseId)) {
return true;
}
continue;
}
//指定课程作用域:保持原逻辑
$privilegeGoodsIds = \app\common\model\app\vip\CardPrivilege::getPrivilegeGoodsIds($userVip['card_id'], 'free');
if ($privilegeGoodsIds && in_array($courseId, $privilegeGoodsIds)) {
return true;
}
}
return false;
}
/**
* 判断课程当前是否为免费
* pay_type=free 或 pay_type=pay 且价格为 0 视为免费
* @param $courseId
* @return bool
*/
public static function isFreeCourse($courseId)
{
$buyData = \app\common\model\course\Course::getPrice($courseId);
if (!$buyData) {
return false;
}
if ($buyData['pay_type'] == 'free') {
return true;
}
if ($buyData['pay_type'] == 'pay' && floatval($buyData['price']) == 0) {
return true;
}
return false;
}
/**
* 获取用户购买的课程
* 未购买 或 超过有效期 返回false
* @param $userId
* @param $courseId
* @return bool
*/
public static function getUserCourse($userId, $courseId)
{
$userInfo = \app\common\model\User::getUserInfo($userId);
if (!$userInfo) {
return false;
}
//当前课程是否仍为免费(pay_type=free 或 pay_type=pay 且价格为 0
//仅当课程仍为免费时,get_type=free 的订阅记录才有效
$isFreeCourse = self::isFreeCourse($courseId);
$mustCondition = [
'course_id' => $courseId,
'uniacid' => UNIACID,
'validity_time' => ['>', time()]
];
if (!$isFreeCourse) {
//课程当前非免费时,过滤掉通过【加入学习】获得的 get_type=free 的记录
$mustCondition['get_type'] = ['<>', 'free'];
}
//判断是否订阅(同时返回所有可能的命中记录,以便对 get_type=vip 做权益失效判断)
$list = self::whereOr(function ($query) use ($mustCondition, $userInfo) {
if( $userInfo['mobile']){
$query->where($mustCondition)->where('mobile', $userInfo['mobile']);
}else{
$query->where($mustCondition)->where('mobile', '<>', null);
}
})->whereOr(function ($query) use ($mustCondition, $userInfo) {
$query->where($mustCondition)->where(['user_id' => $userInfo['id']]);
})->select();
if ($list) {
//缓存 VIP 免费权益命中结果,避免多条记录重复计算
$vipFreeAccessChecked = false;
$vipFreeAccess = false;
foreach ($list as $item) {
if ($item['get_type'] == 'vip') {
//get_type=vip 的记录需当前用户 VIP 免费权益仍命中该课程才视为有效
if (!$vipFreeAccessChecked) {
$vipFreeAccess = self::getVipFreeAccess($userId, $courseId);
$vipFreeAccessChecked = true;
}
if (!$vipFreeAccess) {
continue;
}
}
return true;
}
}
//判断是否已订阅该课程所属的专栏
$columnBindIds = ColumnBind::where([
'uniacid' => UNIACID,
'course_id' => $courseId,
'status'=>1,
'type'=>1 //类型是课程才可用,目录不算
])->field("column_id")->select();
$data = self::where([
'user_id' => $userId,
'uniacid' => UNIACID,
'course_id' => ['in', array_column($columnBindIds, 'column_id')],
'validity_time' => ['>', time()]
])->find();
if ($data) {
return true;
}
foreach ($columnBindIds as $column) {
if (self::getSubscriptionAuth($userId, $column['column_id'])) {
return true;
}
//判断是否在专栏内开启了试看
$isTry = ColumnBind::where([
'uniacid' => UNIACID,
'course_id' => $courseId,
'column_id' => $column['column_id'],
'try' => 1
])->field("try")->find();
if ($isTry) {
return true;
}
}
return false;
}
public function course()
{
return $this->belongsTo('\app\common\model\course\Course', 'course_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
public function user()
{
return $this->belongsTo('\app\admin\model\User', 'user_id', 'id', [], 'LEFT')->setEagerlyType(0);
}
public function mobileuser()
{
return $this->belongsTo('\app\admin\model\User', 'mobile', 'mobile', [], 'LEFT')->setEagerlyType(0);
}
/**
* 构建手机号搜索过滤参数
* 将 user.mobile 过滤条件从 filter/op 中提取并移除,
* 以便在控制器中扩展为同时匹配 subscription.mobile 或 user.mobile
* @param array $filter 过滤条件数组
* @param array $op 操作符数组
* @return array [手机号关键词, 修改后的filter, 修改后的op]
*/
public static function buildMobileFilterParams(array $filter, array $op)
{
$mobile = '';
if (isset($filter['user.mobile']) && $filter['user.mobile'] !== '') {
$mobile = trim($filter['user.mobile']);
unset($filter['user.mobile'], $op['user.mobile']);
}
return [$mobile, $filter, $op];
}
/**
* 获取课程列表
* @param $userId 分组ID
* @param $params 分页信息 page页码 limit获取数量
* @return array
*/
public function getMyCourse($userId = 0, $params = [])
{
$userInfo = \app\common\model\User::getUserInfo($userId);
if (!$userInfo) {
return [];
}
$rows = self::with(['course']);
$mustCondition = [
'subscription.uniacid' => UNIACID,
'course.id'=>['<>', 'null']
];
if ($params['type'] != 'all') {
$mustCondition['course.type'] = $params['type'];
}
//判断是否订阅
//要同时查UserId与Mobile
$list = $rows->whereOr(function ($query) use ($mustCondition, $userInfo) {
if( $userInfo['mobile']){
$query->where($mustCondition)->where('mobile', $userInfo['mobile']);
}else{
$query->where($mustCondition)->where('mobile', '<>', null);
}
// $query->where($mustCondition)->where('mobile', $userInfo['mobile'])->where('mobile', '<>', null);
})->whereOr(function ($query) use ($mustCondition, $userInfo) {
$query->where($mustCondition)->where(['user_id' => $userInfo['id']]);
})
->limit($params['limit'])->page($params['page'])
->order('createtime', 'desc')
->select();
$data = [];
if (!empty($list)) {
foreach ($list as $item) {
//课程从免费变为收费/其他时,通过【加入学习】获得的订阅记录视为失效,不展示
if ($item['get_type'] == 'free' && !self::isFreeCourse($item['course_id'])) {
continue;
}
//VIP 免费权益失效(卡过期/取消权益/范围调整)时,对应订阅记录视为失效,不展示
if ($item['get_type'] == 'vip' && !self::getVipFreeAccess($item['user_id'], $item['course_id'])) {
continue;
}
$item->getRelation('course')->visible(['name', 'cover', 'type', 'id']);
$data[] = $item;
}
}
return $data;
}
/**
* 获取有效时间
* @param array $courseInfo 课程信息
* @param int $historyTime 历史订阅的时间,如果是指定天数,那么在原有时长的基础上累加
* @param array|null $validityOverride 有效期覆盖配置(来自组合商品规格项),格式:['validity_type'=>int, 'validity_value'=>string]
* @return int
*/
public function getValidityTime($courseInfo, $historyTime = 0, $validityOverride = null)
{
// 如果有覆盖配置(组合商品自定义有效期),优先使用
if ($validityOverride && !empty($validityOverride['validity_type'])) {
$overrideType = intval($validityOverride['validity_type']);
$overrideValue = $validityOverride['validity_value'] ?? '';
switch ($overrideType) {
case 1:
// 与商品原有效期一致,走课程自身逻辑
break;
case 2:
// 长期有效
return \app\common\constant\course\Subscription::VALIDITY_ETERNITY;
case 3:
// 固定天数
$days = intval($overrideValue);
if ($historyTime && $historyTime > time()) {
return $historyTime + ($days * 86400);
}
return time() + ($days * 86400);
case 4:
// 固定截止日期
return is_numeric($overrideValue) ? intval($overrideValue) : strtotime($overrideValue);
}
}
// 默认使用课程自身的有效期配置
if ($courseInfo['validity_type'] == 'long') {
$validityTime = \app\common\constant\course\Subscription::VALIDITY_ETERNITY;
} elseif ($courseInfo['validity_type'] == 'fix') {
$validityTime = $courseInfo['validity_fix_time'];
} else {
if ($historyTime && $historyTime > time()) {
$validityTime = $historyTime + ($courseInfo['validity_diy_time'] * 86400);
} else {
$validityTime = time() + ($courseInfo['validity_diy_time'] * 86400);
}
}
return $validityTime;
}
/**
* 增加用户购买课程
* @param int $userId 用户ID
* @param int $courseId 课程ID
* @param string $getType 获取方式
* @param string $mobile 手机号
* @param array|null $validityOverride 有效期覆盖配置(来自组合商品规格项),格式:['validity_type'=>int, 'validity_value'=>string]
* @return void
*/
public function setUserCourse($userId, $courseId, $getType = '',$mobile='', $validityOverride = null)
{
$courseInfo = CourseModel::where([
'id' => $courseId,
'status' => 1,
'uniacid' => UNIACID
])->find();
if (!$courseInfo) {
return false;
}
if (!$getType) {
$getType = $courseInfo['pay_type'];
}
$row = [
'user_id' => $userId,
'mobile'=>$mobile,
'course_id' => $courseId,
'uniacid' => UNIACID,
'get_type' => $getType,
'createtime' => time()
];
$subInfo = $this->getSubscriptionInfo($userId,$mobile,$courseId);
if ($subInfo) {
$row['validity_time'] = $this->getValidityTime($courseInfo, $subInfo['validity_time'], $validityOverride);
$where = [
'course_id' => $courseId
];
if($userId){
$where['user_id'] = $userId;
}else{
$where['mobile'] = $mobile;
}
self::where($where)->update($row);
} else {
$row['validity_time'] = $this->getValidityTime($courseInfo, 0, $validityOverride);
self::insert($row);
}
if ($row['validity_time'] < time()) {
return false;
}
return true;
}
/**
* 获取订阅信息
* @param $userId
* @param $mobile
* @param $courseId
* @return mixed
*/
protected function getSubscriptionInfo($userId,$mobile,$courseId){
$subInfo = false;
if($mobile){
//手机号订阅
//判断有没有订阅过
$subInfo = self::where([
'mobile' => $mobile,
'course_id' => $courseId
])->find();
if(!$subInfo){
$userInfo = \app\common\model\User::where([
'mobile'=>$mobile
])->find();
if($userInfo){
$userId = $userInfo['id'];
//判断有没有订阅过
$subInfo = self::where([
'user_id' => $userId,
'course_id' => $courseId
])->find();
}
}
}else{
//判断有没有订阅过
$subInfo = self::where([
'user_id' => $userId,
'course_id' => $courseId
])->find();
}
return $subInfo;
}
/**
* 取消用户订阅项目
* @param $userId
* @param $goodsId
* @param $goodsType
* @return void
*/
public static function cancelUserSubscription($userId, $goodsId, $goodsType,$extendData='')
{
switch ($goodsType) {
case 'activity':
\app\api\model\app\activity\UserTicket::where([
'uniacid'=>UNIACID,
'user_id'=>$userId,
'activity_id'=>$goodsId,
'ticket_no'=>$extendData
])->update([
'status'=>2
]);
break;
case 'course':
case 'column':
self::where([
'user_id'=>$userId,
'course_id'=>$goodsId,
'uniacid'=>UNIACID
])->update([
'validity_time'=>time()
]);
break;
case 'exercises':
\app\common\model\app\exam\ExercisesSubscribe::where([
'user_id'=>$userId,
'exercises_id'=>$goodsId,
'uniacid'=>UNIACID
])->delete();
break;
case 'vipcard':
$extendData = explode("_",$extendData);
if(!$extendData){
return false;
}
$cardUserData = \app\common\model\app\vip\CardUser::where([
'user_id'=>$userId,
'card_id'=>$goodsId,
'uniacid'=>UNIACID
])->find();
if(!$cardUserData){
return false;
}
$vipTime = $extendData[1] * 86400;
$endtime = ($cardUserData['end_time'] - $vipTime) > time() ? time() + ($cardUserData['end_time'] - $vipTime) : time();
\app\common\model\app\vip\CardUser::where([
'id'=>$cardUserData['id'],
'uniacid'=>UNIACID
])->update([
'updatetime'=>time(),
'end_time'=>$endtime
]);
break;
}
return true;
}
}