// +---------------------------------------------------------------------- namespace App\Services\Common; use App\Models\ActionLogModel; use App\Models\MemberModel; use App\Services\BaseService; use App\Services\RedisService; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\DB; use InvalidArgumentException; /** * 用户(会员)管理-服务类 * @author laravel开发员 * @since 2020/11/11 * Class MemberService * @package App\Services\Common */ class MemberService extends BaseService { public static $instance = null; /** * 构造函数 * @author laravel开发员 * @since 2020/11/11 * MemberService constructor. */ public function __construct() { $this->model = new MemberModel(); } /** * 静态入口 */ public static function make() { if (!self::$instance) { self::$instance = new static(); } return self::$instance; } /** * 列表 * @param $params * @param int $pageSize * @return array */ public function getDataList($params, $pageSize = 15) { $where = ['a.mark' => 1]; $status = isset($params['status']) ? $params['status'] : 0; if ($status > 0) { $where['a.status'] = $status; } $query = $this->model->from('member as a') ->where($where) ->where(function ($query) use ($params) { $keyword = isset($params['keyword']) ? $params['keyword'] : ''; if ($keyword) { $query->where('a.nickname', 'like', "%{$keyword}%")->orWhere('a.realname', 'like', "%{$keyword}%"); } }) ->where(function ($query) use ($params) { $mobile = isset($params['mobile']) ? trim($params['mobile']) : ''; if ($mobile) { $query->where('a.mobile', 'like', "%{$mobile}%"); } }) ->where(function ($query) use ($params) { $confirmStatus = isset($params['confirm_status']) ? $params['confirm_status'] : -1; if ($confirmStatus == 0) { $query->whereIn('a.confirm_status', [2, 3]); } else if ($confirmStatus > 0) { $query->where('a.confirm_status', $confirmStatus); } }) ->select(['a.*']); $confirmStatus = isset($params['confirm_status']) ? $params['confirm_status'] : -1; if ($confirmStatus == 0) { $query->orderBy('a.confirm_status', 'asc')->orderBy('a.create_time', 'desc'); } else { $query->orderBy('a.create_time', 'desc')->orderBy('a.id', 'desc'); } $list = $query->paginate($pageSize > 0 ? $pageSize : 9999999); $list = $list ? $list->toArray() : []; return [ 'pageSize' => $pageSize, 'total' => isset($list['total']) ? $list['total'] : 0, 'list' => isset($list['data']) ? $list['data'] : [] ]; } /** * 按日期统计注册用户数 * @param $beginAt * @param $endAt * @param int $status * @return mixed */ public function getRegisterCount($beginAt = 0, $endAt = 0, $status = 1) { $cacheKey = "caches:members:count_{$beginAt}_{$endAt}_{$status}"; $data = RedisService::get($cacheKey); if ($data) { return $data; } $where = ['mark' => 1, 'status' => $status]; if ($status == 2) { $where['status'] = 1; } $data = $this->model->where($where)->where(function ($query) use ($beginAt, $endAt) { if ($beginAt && $endAt) { $query->whereBetween('create_time', [strtotime($beginAt), strtotime($endAt)]); } else if ($beginAt) { $query->where('create_time', '>=', strtotime($beginAt)); } })->count('id'); if ($data) { RedisService::set($cacheKey, $data, rand(300, 600)); } } public function countUsers($type = 'today', $start_time = null, $end_time = null) { // 生成缓存的唯一键,依据 type 和时间范围 $cacheKey = "user_count_{$type}"; if ($start_time && $end_time) { $cacheKey .= "_{$start_time}_{$end_time}"; } // 检查缓存中是否已经存在该统计数据 $count = Cache::get($cacheKey); if ($count === null) { // 如果缓存中没有数据,则执行数据库查询 $query = DB::table('member'); // 如果 type 是 'all',则不进行时间筛选,查询所有记录 if ($type === 'all') { // 不添加时间过滤条件,查询所有用户数量 $count = $query->count(); } else { // 根据传入的时间范围进行过滤 if ($start_time && $end_time) { $query->whereBetween('create_time', [$start_time, $end_time]); } // 根据不同的统计类型进行筛选 switch ($type) { case 'today': $query->whereDate('create_time', '=', now()->toDateString()); // 今天 break; case 'yesterday': $query->whereDate('create_time', '=', now()->subDay()->toDateString()); // 昨天 break; case 'month': $query->whereMonth('create_time', '=', now()->month) // 当前月 ->whereYear('create_time', '=', now()->year); break; case 'last_month': $query->whereMonth('create_time', '=', now()->subMonth()->month) // 上个月 ->whereYear('create_time', '=', now()->subMonth()->year); break; case 'year': $query->whereYear('create_time', '=', now()->year); // 当前年 break; default: throw new InvalidArgumentException("无效的类型"); } // 执行查询并获取统计数据 $count = $query->count(); } // 将查询结果缓存,缓存时间为 10 分钟 // Cache::put($cacheKey, $count, now()->addMinutes(10)); } return $count; } /** * 用户选项 * @return array */ public function options() { // 获取参数 $param = request()->all(); // 用户ID $keyword = getter($param, "keyword"); $parentId = getter($param, "parent_id"); $userId = getter($param, "user_id"); $datas = $this->model->where(function ($query) use ($parentId) { if ($parentId) { $query->where(['id' => $parentId, 'mark' => 1]); } else { $query->where(['status' => 1, 'mark' => 1]); } }) ->where(function ($query) use ($userId) { if ($userId) { $query->whereNotIn('id', [$userId]); } }) ->where(function ($query) use ($keyword) { if ($keyword) { $query->where('nickname', 'like', "%{$keyword}%") ->orWhere('mobile', 'like', "%{$keyword}%"); } }) ->select(['id', 'realname', 'mobile', 'code', 'nickname', 'status']) ->get(); return $datas ? $datas->toArray() : []; } /** * 添加或编辑会员 * @return array */ public function edit() { $data = request()->all(); // 允许更新的字段(对应前端 dialog) $allowedFields = [ 'id', 'avatar', 'mobile', 'nickname', 'is_zg_vip', 'zg_vip_expired', 'is_zsb_vip', 'zsb_vip_expired', 'is_video_vip', 'video_vip_expired', 'entry_type', 'need_paper' ]; // 只保留允许字段 $data = array_intersect_key($data, array_flip($allowedFields)); // 头像与证件图片处理 foreach (['avatar', 'driving_license', 'drivers_license'] as $field) { if (!empty($data[$field])) { $data[$field] = get_image_path($data[$field]); } } $id = $data['id'] ?? 0; $mobile = trim($data['mobile'] ?? ''); if ($mobile) { $checkId = $this->model->where(['mobile' => $mobile, 'mark' => 1])->value('id'); if ($checkId && $checkId != $id) { return message('手机号已存在', false); } } // 密码加密 if (!empty($data['password'])) { $data['password'] = get_password(trim($data['password'])); } // VIP字段处理(可选) $vipFields = ['is_zg_vip', 'is_zsb_vip', 'is_video_vip']; $vipMapping = [ 'is_zg_vip' => 'zg_vip_expired', 'is_zsb_vip' => 'zsb_vip_expired', 'is_video_vip' => 'video_vip_expired' ]; foreach ($vipFields as $vipField) { if (isset($data[$vipField])) { $data[$vipField] = intval($data[$vipField]); $expireField = $vipMapping[$vipField]; // 获取当前数据库状态 $oldStatus = $this->model->where('id', $id)->value($vipField); $oldExpire = $this->model->where('id', $id)->value($expireField); // 调用公共方法计算新过期时间 if($data[$expireField]<=date('Y-m-d H:i:s')){ $data[$expireField] = $this->calcVipExpire($oldStatus, $data[$vipField], $oldExpire); } } } // 日志记录 ActionLogModel::setRecord( session('userId'), [ 'type' => 1, 'title' => $id ? '修改用户信息' : '新增用户', 'content' => json_encode($data, 256), 'module' => 'admin' ] ); ActionLogModel::record(); // 清理缓存 RedisService::keyDel("caches:members:count*"); try { if ($id) { DB::table('member')->where('id', $id)->update($data); } else { DB::table('member')->insert($data); } return message("操作成功", true, ); } catch (\Exception $e) { return message("操作失败:" . $e->getMessage(), false); } } /** * 审核 * @return array * @since 2020/11/11 * @author laravel开发员 */ public function confirm() { // 请求参数 $data = request()->all(); $drivingLicense = isset($data['driving_license']) ? $data['driving_license'] : ''; if ($drivingLicense) { $data['driving_license'] = get_image_path($drivingLicense); } $driversLicense = isset($data['drivers_license']) ? $data['drivers_license'] : ''; if ($driversLicense) { $data['drivers_license'] = get_image_path($driversLicense); } // 设置日志 $mobile = isset($data['mobile']) ? $data['mobile'] : ''; ActionLogModel::setRecord(session('userId'), ['type' => 1, 'title' => "审核用户[{$mobile}]信息", 'content' => json_encode($data, 256), 'module' => 'admin']); ActionLogModel::record(); RedisService::keyDel("caches:members:count*"); return parent::edit($data); // TODO: Change the autogenerated stub } /** * 删除 * @return array */ public function delete() { // 设置日志标题 ActionLogModel::setRecord(session('userId'), ['type' => 1, 'title' => "删除用户信息", 'content' => json_encode(request()->post(), 256), 'module' => 'admin']); ActionLogModel::record(); $this->model->where('mark', 0)->where('update_time', '<=', time() - 7 * 86400)->delete(); RedisService::keyDel("caches:members:count*"); return parent::delete(); // TODO: Change the autogenerated stub } /** * 批量设置VIP * 前端传参示例: * { * ids: [1,2,3], * is_zg_vip: 1 // 可选字段: is_zg_vip / is_zsb_vip / is_video_vip * } * @return array */ public function batchVip() { $params = request()->post(); // 校验会员ID if (empty($params['ids']) || !is_array($params['ids'])) { return message("请选择要操作的会员", false); } $ids = $params['ids']; $vipMapping = [ 'is_zg_vip' => 'zg_vip_expired', 'is_zsb_vip' => 'zsb_vip_expired', 'is_video_vip' => 'video_vip_expired' ]; $updateData = ['update_time' => time()]; $hasVipField = false; foreach ($vipMapping as $vipField => $expireField) { if (isset($params[$vipField])) { $status = intval($params[$vipField]); $updateData[$vipField] = $status; // 查出这些会员当前的VIP状态和过期时间 $members = DB::table('member') ->whereIn('id', $ids) ->select('id', $vipField, $expireField) ->get() ->keyBy('id'); foreach ($members as $memberId => $member) { $newExpire = $this->calcVipExpire(intval($member->$vipField), $status, $member->$expireField); DB::table('member') ->where('id', $memberId) ->update([ $vipField => $status, $expireField => $newExpire, 'update_time' => time(), ]); } $hasVipField = true; } } if (!$hasVipField) { return message("请指定要操作的VIP类型", false); } return message("批量设置VIP成功", true); } public function getMemberStats(array $params) { $dateType = $params['dateType'] ?? 'day'; $page = max(1, (int) ($params['page'] ?? 1)); $limit = max(1, (int) ($params['limit'] ?? 10)); // 分组日期格式 switch ($dateType) { case 'month': $format = '%Y-%m'; $groupRaw = "FROM_UNIXTIME(create_time, '{$format}')"; break; case 'year': $format = '%Y'; $groupRaw = "FROM_UNIXTIME(create_time, '{$format}')"; break; case 'week': // 按周统计,格式 YYYY-WW $groupRaw = "CONCAT(YEAR(FROM_UNIXTIME(create_time)), '-', LPAD(WEEK(FROM_UNIXTIME(create_time), 1), 2, '0'))"; break; case 'day': default: $format = '%Y-%m-%d'; $groupRaw = "FROM_UNIXTIME(create_time, '{$format}')"; break; } // 统一转为时间戳 $startTimestamp = !empty($params['start_time']) ? (is_numeric($params['start_time']) ? (int) $params['start_time'] : strtotime($params['start_time'])) : strtotime(date('2000-01-01 00:00:00')); // 默认起始时间 $endTimestamp = !empty($params['end_time']) ? (is_numeric($params['end_time']) ? (int) $params['end_time'] : strtotime($params['end_time'])) : time(); // 默认当前时间 if ($endTimestamp < $startTimestamp) { $endTimestamp = $startTimestamp; } // 基础查询 $baseQuery = DB::table('member') ->selectRaw("{$groupRaw} as stat_date, COUNT(*) as reg_count") ->whereBetween('create_time', [$startTimestamp, $endTimestamp]) ->groupBy('stat_date') ->orderBy('stat_date', 'desc'); // 总数 $total = DB::table(DB::raw("({$baseQuery->toSql()}) as t")) ->mergeBindings($baseQuery) ->count(); // 分页数据 $list = $baseQuery->forPage($page, $limit)->get(); return [ 'list' => $list, 'total' => $total, 'page' => $page, 'limit' => $limit, ]; } /** * 根据旧状态和新状态计算VIP过期时间 * * @param int|null $oldStatus 当前VIP状态 (0/1/2) * @param int $newStatus 新状态 (0/1/2) * @param string|null $oldExpire 当前过期时间 * @return string|null 返回新的过期时间 */ public function calcVipExpire(?int $oldStatus, int $newStatus, ?string $oldExpire): ?string { if ($newStatus === 1) { // 从非VIP → VIP,才设置一年过期时间 if ($oldStatus !== 1) { return date('Y-m-d H:i:s', time() + 365 * 24 * 3600); } // 已是VIP,保持原过期时间 return $oldExpire; } else { // 0 或 2 → 清空过期时间 return null; } } }