初始化本地仓库
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\enum\common\EnabledStatus;
|
||||
use app\model\member\MemberAddress;
|
||||
|
||||
|
||||
/**
|
||||
* 会员地址数据访问对象
|
||||
*/
|
||||
class MemberAddressDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberAddress::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认地址
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setDefault(MemberAddress $address): bool
|
||||
{
|
||||
// 先将该会员的所有地址设置为非默认
|
||||
$this->query()->where('member_id', $address->member_id)->update(['is_default' => 0]);
|
||||
|
||||
// 将当前地址设置为默认
|
||||
$address->is_default = 1;
|
||||
return $address->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员默认地址
|
||||
*/
|
||||
public function getDefaultAddress(int $memberId): ?MemberAddress
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('is_default', 1)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员地址列表
|
||||
*/
|
||||
public function getMemberAddresses(int $memberId): array
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->orderBy('is_default', 'desc')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新地址
|
||||
*/
|
||||
public function saveAddress(int $memberId, array $data): MemberAddress
|
||||
{
|
||||
$addressId = $data['id'] ?? 0;
|
||||
|
||||
if ($addressId) {
|
||||
// 更新地址
|
||||
$address = $this->find($addressId);
|
||||
if (!$address || $address->member_id != $memberId) {
|
||||
throw new \Exception('地址不存在或无权限');
|
||||
}
|
||||
} else {
|
||||
// 创建新地址
|
||||
$address = $this->getModel();
|
||||
$address->member_id = $memberId;
|
||||
$address->enabled = EnabledStatus::ENABLED->value;
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
$fillable = [
|
||||
'name',
|
||||
'phone',
|
||||
'province',
|
||||
'city',
|
||||
'district',
|
||||
'address',
|
||||
'zipcode',
|
||||
];
|
||||
|
||||
foreach ($fillable as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$address->{$field} = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认地址
|
||||
if (isset($data['is_default']) && $data['is_default'] == 1) {
|
||||
// 先将其他地址设置为非默认
|
||||
$this->query()
|
||||
->where('member_id', $memberId)
|
||||
->update(['is_default' => 0]);
|
||||
$address->is_default = 1;
|
||||
} elseif (!$addressId) {
|
||||
// 新地址默认为非默认
|
||||
$address->is_default = 0;
|
||||
}
|
||||
|
||||
$address->save();
|
||||
return $address;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除地址
|
||||
*/
|
||||
public function deleteAddress(MemberAddress $address): bool
|
||||
{
|
||||
$address->enabled = EnabledStatus::DISABLED->value;
|
||||
return $address->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证地址数据
|
||||
*/
|
||||
public function validateAddress(array $data): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (empty($data['name'])) {
|
||||
$errors[] = '收货人姓名不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['phone'])) {
|
||||
$errors[] = '联系电话不能为空';
|
||||
} elseif (!preg_match('/^1[3-9]\d{9}$/', $data['phone'])) {
|
||||
$errors[] = '联系电话格式不正确';
|
||||
}
|
||||
|
||||
if (empty($data['province'])) {
|
||||
$errors[] = '省份不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['city'])) {
|
||||
$errors[] = '城市不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['district'])) {
|
||||
$errors[] = '区县不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['address'])) {
|
||||
$errors[] = '详细地址不能为空';
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员地址数量
|
||||
*/
|
||||
public function getMemberAddressCount(int $memberId): int
|
||||
{
|
||||
return $this->count([
|
||||
'member_id' => $memberId,
|
||||
'enabled' => EnabledStatus::ENABLED->value
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\model\member\MemberBill;
|
||||
use app\enum\member\BillType;
|
||||
use app\enum\member\BillStatus;
|
||||
|
||||
/**
|
||||
* 会员账单DAO
|
||||
*/
|
||||
class MemberBillDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberBill::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录账单
|
||||
*/
|
||||
public function record(int $memberId, int $type, int $category, float $amount, string $description, string $orderSn = '', int $status = BillStatus::SUCCESS->value): MemberBill
|
||||
{
|
||||
$bill = $this->getModel();
|
||||
$bill->member_id = $memberId;
|
||||
$bill->type = $type;
|
||||
$bill->category = $category;
|
||||
$bill->amount = $amount;
|
||||
$bill->description = $description;
|
||||
$bill->order_sn = $orderSn;
|
||||
$bill->status = $status;
|
||||
$bill->save();
|
||||
|
||||
return $bill;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员账单列表
|
||||
*/
|
||||
public function getMemberBills(int $memberId, array $params = []): array
|
||||
{
|
||||
$query = $this->query()->where('member_id', $memberId);
|
||||
|
||||
// 类型筛选
|
||||
if (isset($params['type'])) {
|
||||
$query->where('type', $params['type']);
|
||||
}
|
||||
|
||||
// 分类筛选
|
||||
if (isset($params['category'])) {
|
||||
$query->where('category', $params['category']);
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (isset($params['start_date']) && isset($params['end_date'])) {
|
||||
$query->whereBetween('created_at', [
|
||||
$params['start_date'] . ' 00:00:00',
|
||||
$params['end_date'] . ' 23:59:59'
|
||||
]);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (isset($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
$page = $params['page'] ?? 1;
|
||||
$limit = $params['limit'] ?? 10;
|
||||
|
||||
return $query->orderBy('created_at', 'desc')
|
||||
->paginate($limit, ['*'], 'page', $page)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员账单统计
|
||||
*/
|
||||
public function getMemberBillStats(int $memberId, string $startDate, string $endDate): array
|
||||
{
|
||||
$stats = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->whereBetween('created_at', [
|
||||
$startDate . ' 00:00:00',
|
||||
$endDate . ' 23:59:59'
|
||||
])
|
||||
->selectRaw('type, category, SUM(amount) as total_amount, COUNT(*) as count')
|
||||
->groupBy('type', 'category')
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$result = [
|
||||
'total_income' => 0,
|
||||
'total_expense' => 0,
|
||||
'category_stats' => [],
|
||||
];
|
||||
|
||||
foreach ($stats as $stat) {
|
||||
if ($stat['type'] == BillType::INCOME->value) {
|
||||
$result['total_income'] += $stat['total_amount'];
|
||||
} else {
|
||||
$result['total_expense'] += $stat['total_amount'];
|
||||
}
|
||||
|
||||
$result['category_stats'][$stat['category']] = [
|
||||
'total_amount' => $stat['total_amount'],
|
||||
'count' => $stat['count'],
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最新账单
|
||||
*/
|
||||
public function getLatestBill(int $memberId): ?MemberBill
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->orderBy('created_at', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日账单统计
|
||||
*/
|
||||
public function getTodayStats(int $memberId): array
|
||||
{
|
||||
$today = date('Y-m-d');
|
||||
return $this->getMemberBillStats($memberId, $today, $today);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月账单统计
|
||||
*/
|
||||
public function getMonthStats(int $memberId): array
|
||||
{
|
||||
$firstDay = date('Y-m-01');
|
||||
$lastDay = date('Y-m-t');
|
||||
return $this->getMemberBillStats($memberId, $firstDay, $lastDay);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单号更新账单状态
|
||||
*/
|
||||
public function updateStatusByOrderSn(string $orderSn, int $status): bool
|
||||
{
|
||||
return $this->query()
|
||||
->where('order_sn', $orderSn)
|
||||
->update(['status' => $status]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账单总数
|
||||
*/
|
||||
public function getCount(int $memberId, array $params = []): int
|
||||
{
|
||||
$query = $this->query()->where('member_id', $memberId);
|
||||
|
||||
// 类型筛选
|
||||
if (isset($params['type'])) {
|
||||
$query->where('type', $params['type']);
|
||||
}
|
||||
|
||||
// 分类筛选
|
||||
if (isset($params['category'])) {
|
||||
$query->where('category', $params['category']);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (isset($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use app\scope\global\AccessPermissionScope;
|
||||
use app\service\admin\member\MemberTagRelationService;
|
||||
use app\service\admin\member\MemberTagService;
|
||||
use core\base\BaseDao;
|
||||
use app\model\member\Member;
|
||||
use madong\query\QueryBuilderHelper;
|
||||
use madong\query\QueryParamConverter;
|
||||
use madong\query\enum\ParamFormat;
|
||||
use InvalidArgumentException;
|
||||
use support\Container;
|
||||
|
||||
/**
|
||||
* 会员DAO
|
||||
*/
|
||||
class MemberDao extends BaseDao
|
||||
{
|
||||
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return Member::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取活跃用户
|
||||
*
|
||||
* @param int $limit
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Collection
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getActiveUsers(int $limit = 5): \Illuminate\Database\Eloquent\Collection
|
||||
{
|
||||
// last_time 字段存储为时间戳格式(dateFormat = 'U')
|
||||
// 计算30天前的时间戳
|
||||
$thirtyDaysAgo = time() - (30 * 24 * 60 * 60);
|
||||
|
||||
return $this->query()->withoutGlobalScope(AccessPermissionScope::class)
|
||||
->where('last_login_time', '>=', $thirtyDaysAgo)
|
||||
->orderBy('last_login_time', 'desc')
|
||||
->limit($limit)
|
||||
->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标签ID获取用户列表
|
||||
*
|
||||
* @param array $where 查询条件,支持多种格式自动转换
|
||||
* @param string $field 查询字段
|
||||
* @param int $page 页码
|
||||
* @param int $limit 每页数量
|
||||
* @param array|null $withoutScopes 移除的作用域
|
||||
*
|
||||
* @return array ['total' => int, 'items' => array]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getUsersListByTagId(array $where, string $field, int $page, int $limit, ?array $withoutScopes = null): array
|
||||
{
|
||||
try {
|
||||
//传入的是标准filters格式
|
||||
$tagId = QueryParamConverter::getValue($where, 'tag_id');
|
||||
if (!$tagId) {
|
||||
throw new InvalidArgumentException("Tag ID is required.");
|
||||
}
|
||||
// 从 where 中移除 tag_id
|
||||
$where = QueryParamConverter::removeFilter($where, 'tag_id');
|
||||
|
||||
$query = $this->query()->with(['tags' => function ($query) use ($withoutScopes) {
|
||||
$this->applyScopeRemoval($query, $withoutScopes);
|
||||
}])
|
||||
->whereHas('tags', function ($query) use ($tagId) {
|
||||
$query->where('id', $tagId);
|
||||
});
|
||||
|
||||
$this->applyScopeRemoval($query, $withoutScopes);
|
||||
|
||||
// 应用剩余的 filters 条件
|
||||
if (!empty($where['filters'])) {
|
||||
QueryBuilderHelper::applyFiltersToQuery($query, $where['filters']);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
$items = $query->when($page > 0 && $limit > 0, function ($query) use ($page, $limit) {
|
||||
return $query->skip(($page - 1) * $limit)->take($limit);
|
||||
})
|
||||
->select(explode(',', $field))
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
return compact('total', 'items');
|
||||
} catch (\Throwable $e) {
|
||||
throw new \Exception("获取用户列表失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 排除标签ID-用户列表
|
||||
*
|
||||
* @param array $where
|
||||
* @param string $field
|
||||
* @param int $page
|
||||
* @param int $limit
|
||||
* @param array|null $withoutScopes
|
||||
*
|
||||
* @return array
|
||||
* @throws \ReflectionException|\Exception
|
||||
*/
|
||||
public function getUsersExcludingTag(array $where, string $field, int $page, int $limit, ?array $withoutScopes = null): array
|
||||
{
|
||||
|
||||
// 使用 QueryParamConverter 获取 tag_id 值(自动识别格式)
|
||||
$tagId = QueryParamConverter::getValue($where, 'tag_id');
|
||||
|
||||
// 从 where 中移除 tag_id
|
||||
$where = QueryParamConverter::removeFilter($where, 'tag_id');
|
||||
// 获取排除的用户ID列表
|
||||
if ($tagId) {
|
||||
$memberTagRelationService = Container::make(MemberTagRelationService::class);
|
||||
|
||||
$excludedMemberUserIds = $memberTagRelationService->getColumn(['tag_id' => $tagId], 'member_id');
|
||||
} else {
|
||||
$excludedMemberUserIds = [];
|
||||
}
|
||||
|
||||
$query = $this->getModel()->whereNotIn('id', $excludedMemberUserIds)
|
||||
->with(['tags' => function ($query) use ($withoutScopes) {
|
||||
$this->applyScopeRemoval($query, $withoutScopes);
|
||||
}]);
|
||||
$this->applyScopeRemoval($query, $withoutScopes);
|
||||
|
||||
// 应用剩余的 filters 条件
|
||||
if (!empty($where['filters'])) {
|
||||
QueryBuilderHelper::applyFiltersToQuery($query, $where['filters']);
|
||||
}
|
||||
|
||||
$total = $query->count();
|
||||
|
||||
// 如果没有用户,则返回空结果
|
||||
if ($total === 0) {
|
||||
return ['total' => 0, 'items' => []];
|
||||
}
|
||||
|
||||
$items = $query->when($page > 0 && $limit > 0, function ($query) use ($page, $limit) {
|
||||
return $query->skip(($page - 1) * $limit)->take($limit);
|
||||
})->select($field)->get()->toArray();
|
||||
|
||||
return compact('total', 'items');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\enum\common\EnabledStatus;
|
||||
use app\model\member\MemberLevel;
|
||||
|
||||
/**
|
||||
* 会员等级数据访问对象
|
||||
*/
|
||||
class MemberLevelDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberLevel::class;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 根据积分获取等级
|
||||
// */
|
||||
// public function getLevelByPoints(int $points): ?MemberLevel
|
||||
// {
|
||||
// return $this->query()
|
||||
// ->where('enabled', EnabledStatus::ENABLED->value)
|
||||
// ->where('min_points', '<=', $points)
|
||||
// ->where(function ($query) use ($points) {
|
||||
// $query->where('max_points', '>=', $points)
|
||||
// ->orWhere('max_points', 0);
|
||||
// })
|
||||
// ->orderBy('level', 'desc')
|
||||
// ->first();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取下一个等级
|
||||
// */
|
||||
// public function getNextLevel(MemberLevel $currentLevel): ?MemberLevel
|
||||
// {
|
||||
// return $this->query()
|
||||
// ->where('enabled', EnabledStatus::ENABLED->value)
|
||||
// ->where('level', '>', $currentLevel->level)
|
||||
// ->orderBy('level', 'asc')
|
||||
// ->first();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取等级进度
|
||||
// */
|
||||
// public function getLevelProgress(MemberLevel $currentLevel, int $points): array
|
||||
// {
|
||||
// $nextLevel = $this->getNextLevel($currentLevel);
|
||||
//
|
||||
// if (!$nextLevel) {
|
||||
// return [
|
||||
// 'current_points' => $points,
|
||||
// 'next_points' => 0,
|
||||
// 'progress' => 100,
|
||||
// 'is_max_level' => true,
|
||||
// ];
|
||||
// }
|
||||
//
|
||||
// $currentRange = $currentLevel->max_points - $currentLevel->min_points;
|
||||
// $currentPoints = $points - $currentLevel->min_points;
|
||||
// $progress = $currentRange > 0 ? round(($currentPoints / $currentRange) * 100, 2) : 0;
|
||||
//
|
||||
// return [
|
||||
// 'current_points' => $points,
|
||||
// 'next_points' => $nextLevel->min_points,
|
||||
// 'progress' => min($progress, 100),
|
||||
// 'is_max_level' => false,
|
||||
// ];
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use app\model\member\MemberPoints;
|
||||
use core\base\BaseDao;
|
||||
|
||||
/**
|
||||
* 会员积分DAO类
|
||||
*/
|
||||
class MemberPointsDao extends BaseDao
|
||||
{
|
||||
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
public function setModel(): string
|
||||
{
|
||||
return MemberPoints::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员积分记录
|
||||
*/
|
||||
public function getMemberPoints(int|string $memberId, int $limit = 20): array
|
||||
{
|
||||
return $this->selectList(
|
||||
[['member_id', '=', $memberId]],
|
||||
'*',
|
||||
0, $limit,
|
||||
'created_at desc'
|
||||
)->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员积分余额
|
||||
*/
|
||||
public function getMemberBalance(int|string $memberId): float
|
||||
{
|
||||
$lastRecord = $this->query()
|
||||
->where('member_id', '=', $memberId)
|
||||
->orderBy('created_at', 'desc')
|
||||
->first();
|
||||
return $lastRecord ? $lastRecord->balance : 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use app\service\api\member\MemberService;
|
||||
use core\base\BaseDao;
|
||||
use app\model\member\MemberSign;
|
||||
use support\Container;
|
||||
|
||||
/**
|
||||
* 会员签到DAO
|
||||
*/
|
||||
class MemberSignDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberSign::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建签到记录
|
||||
*/
|
||||
public function created(int $memberId, string $signDate, int $points, int $continuousDays, array $deviceInfo = []): MemberSign
|
||||
{
|
||||
$sign = new MemberSign();
|
||||
$sign->member_id = $memberId;
|
||||
$sign->sign_date = $signDate;
|
||||
$sign->points = $points;
|
||||
$sign->continuous_days = $continuousDays;
|
||||
|
||||
$sign->save();
|
||||
|
||||
return $sign;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查今日是否已签到
|
||||
*/
|
||||
public function isSignedToday(int|string $memberId, string $today): bool
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('sign_date', $today)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取最近一次签到记录
|
||||
*/
|
||||
public function getLastSign(int|string $memberId): ?MemberSign
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->orderBy('sign_date', 'desc')
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员签到记录
|
||||
*/
|
||||
public function getMemberSignRecords(int|string $memberId, int $days = 30): array
|
||||
{
|
||||
$startDate = date('Y-m-d', strtotime("-$days days"));
|
||||
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('sign_date', '>=', $startDate)
|
||||
->orderBy('sign_date', 'asc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本月签到天数
|
||||
*/
|
||||
public function getMonthSignDays(int|string $memberId): int
|
||||
{
|
||||
$firstDay = date('Y-m-01');
|
||||
$lastDay = date('Y-m-t');
|
||||
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->whereBetween('sign_date', [$firstDay, $lastDay])
|
||||
->count();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连续签到排行榜
|
||||
*/
|
||||
public function getContinuousSignRanking(int $limit = 10): array
|
||||
{
|
||||
return $this->query()
|
||||
->selectRaw('member_id, MAX(continuous_days) as max_continuous_days')
|
||||
->groupBy('member_id')
|
||||
->orderBy('max_continuous_days', 'desc')
|
||||
->limit($limit)
|
||||
->with('member')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签到统计
|
||||
*/
|
||||
public function getSignStatistics(int|string $memberId): array
|
||||
{
|
||||
$totalSignDays = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->count();
|
||||
$monthSignDays = $this->getMonthSignDays($memberId);
|
||||
$continuousDays = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->orderBy('sign_date', 'desc')
|
||||
->value('continuous_days') ?? 0;
|
||||
|
||||
// 获取用户积分
|
||||
$points = Container::make(MemberService::class)
|
||||
->get($memberId)
|
||||
->points ?? 0;
|
||||
|
||||
return [
|
||||
'total_sign_days' => $totalSignDays,
|
||||
'month_sign_days' => $monthSignDays,
|
||||
'continuous_days' => $continuousDays,
|
||||
'today_signed' => $this->isSignedToday($memberId, date('Y-m-d')),
|
||||
'points' => $points
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取年度签到统计
|
||||
*/
|
||||
public function getYearSignStatistics(int|string $memberId, int $year = null): array
|
||||
{
|
||||
if ($year === null) {
|
||||
$year = date('Y');
|
||||
}
|
||||
|
||||
$startDate = $year . '-01-01';
|
||||
$endDate = $year . '-12-31';
|
||||
|
||||
$totalSignDays = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->whereBetween('sign_date', [$startDate, $endDate])
|
||||
->count();
|
||||
|
||||
// 统计每个月的签到天数
|
||||
$monthlyStats = [];
|
||||
for ($month = 1; $month <= 12; $month++) {
|
||||
$monthStart = $year . '-' . str_pad((string)$month, 2, '0', STR_PAD_LEFT) . '-01';
|
||||
$monthEnd = $year . '-' . str_pad((string)$month, 2, '0', STR_PAD_LEFT) . '-' . date('t', strtotime($monthStart));
|
||||
|
||||
$monthlyStats[$month] = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->whereBetween('sign_date', [$monthStart, $monthEnd])
|
||||
->count();
|
||||
}
|
||||
|
||||
return [
|
||||
'year' => $year,
|
||||
'total_sign_days' => $totalSignDays,
|
||||
'monthly_stats' => $monthlyStats
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取周签到统计
|
||||
*/
|
||||
public function getWeekSignStatistics(int|string $memberId): array
|
||||
{
|
||||
$weekStart = date('Y-m-d', strtotime('this week'));
|
||||
$weekEnd = date('Y-m-d', strtotime('this week +6 days'));
|
||||
|
||||
$signDays = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->whereBetween('sign_date', [$weekStart, $weekEnd])
|
||||
->count();
|
||||
|
||||
return [
|
||||
'week_start' => $weekStart,
|
||||
'week_end' => $weekEnd,
|
||||
'sign_days' => $signDays,
|
||||
'total_days' => 7
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连续签到奖励
|
||||
*/
|
||||
public function getContinuousSignReward(int|string $continuousDays): int
|
||||
{
|
||||
// 连续签到奖励规则
|
||||
$rewards = [
|
||||
7 => 50, // 连续7天奖励50积分
|
||||
14 => 100, // 连续14天奖励100积分
|
||||
30 => 200, // 连续30天奖励200积分
|
||||
];
|
||||
|
||||
return $rewards[$continuousDays] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取签到日历
|
||||
*/
|
||||
public function getSignCalendar(int|string $memberId, int $year, int $month): array
|
||||
{
|
||||
$firstDay = $year . '-' . str_pad((string)$month, 2, '0', STR_PAD_LEFT) . '-01';
|
||||
$lastDay = $year . '-' . str_pad((string)$month, 2, '0', STR_PAD_LEFT) . '-' . date('t', strtotime($firstDay));
|
||||
|
||||
$signs = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->whereBetween('sign_date', [$firstDay, $lastDay])
|
||||
->get()
|
||||
->toArray();
|
||||
|
||||
$calendar = [];
|
||||
foreach ($signs as $sign) {
|
||||
$calendar[] = $sign['sign_date'];
|
||||
}
|
||||
|
||||
return $calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取会员签到统计
|
||||
*/
|
||||
public function batchGetSignStatistics(array $memberIds): array
|
||||
{
|
||||
$result = [];
|
||||
foreach ($memberIds as $memberId) {
|
||||
$result[$memberId] = $this->getSignStatistics($memberId);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理过期签到记录(保留最近一年的记录)
|
||||
*/
|
||||
public function cleanExpiredRecords(): int
|
||||
{
|
||||
$oneYearAgo = date('Y-m-d', strtotime('-1 year'));
|
||||
return $this->query()
|
||||
->where('sign_date', '<', $oneYearAgo)
|
||||
->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\enum\common\EnabledStatus;
|
||||
use app\model\member\MemberTag;
|
||||
|
||||
/**
|
||||
* 会员标签数据访问对象
|
||||
*/
|
||||
class MemberTagDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberTag::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签使用统计
|
||||
*/
|
||||
public function getUsageCount(int $tagId): int
|
||||
{
|
||||
$relationDao = new MemberTagRelationDao();
|
||||
return $relationDao->getTagMemberCount($tagId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取启用的标签列表
|
||||
*/
|
||||
public function getEnabledTags(): array
|
||||
{
|
||||
return $this->query()
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->orderBy('sort', 'asc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签详情
|
||||
*/
|
||||
public function getTagById(int $tagId): ?MemberTag
|
||||
{
|
||||
return $this->find($tagId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标签列表
|
||||
*/
|
||||
public function getTagList(array $params = []): array
|
||||
{
|
||||
$query = $this->query();
|
||||
|
||||
// 状态筛选
|
||||
if (isset($params['enabled'])) {
|
||||
$query->where('enabled', $params['enabled']);
|
||||
}
|
||||
|
||||
// 关键词搜索
|
||||
if (isset($params['keyword'])) {
|
||||
$query->where('name', 'like', '%' . $params['keyword'] . '%')
|
||||
->orWhere('description', 'like', '%' . $params['keyword'] . '%');
|
||||
}
|
||||
|
||||
return $query->orderBy('sort', 'asc')
|
||||
->orderBy('id', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新标签
|
||||
*/
|
||||
public function saveTag(array $data): MemberTag
|
||||
{
|
||||
$tagId = $data['id'] ?? 0;
|
||||
|
||||
if ($tagId) {
|
||||
// 更新标签
|
||||
$tag = $this->find($tagId);
|
||||
if (!$tag) {
|
||||
throw new \Exception('标签不存在');
|
||||
}
|
||||
} else {
|
||||
// 创建新标签
|
||||
$tag = $this->getModel();
|
||||
$tag->enabled = EnabledStatus::ENABLED->value;
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
$fillable = [
|
||||
'name',
|
||||
'color',
|
||||
'description',
|
||||
'sort',
|
||||
'enabled',
|
||||
];
|
||||
|
||||
foreach ($fillable as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$tag->{$field} = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
$tag->save();
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除标签
|
||||
*/
|
||||
public function deleteTag(int $tagId): bool
|
||||
{
|
||||
// 检查标签是否被使用
|
||||
$usageCount = $this->getUsageCount($tagId);
|
||||
if ($usageCount > 0) {
|
||||
throw new \Exception('该标签已被使用,无法删除');
|
||||
}
|
||||
|
||||
$tag = $this->find($tagId);
|
||||
if (!$tag) {
|
||||
throw new \Exception('标签不存在');
|
||||
}
|
||||
|
||||
return $tag->delete();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
*+------------------
|
||||
* madong
|
||||
*+------------------
|
||||
* Copyright (c) https://gitee.com/motion-code All rights reserved.
|
||||
*+------------------
|
||||
* Author: Mr. April (405784684@qq.com)
|
||||
*+------------------
|
||||
* Official Website: http://www.madong.tech
|
||||
*/
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
|
||||
use app\model\member\MemberTagPermission;
|
||||
use core\base\BaseDao;
|
||||
|
||||
class MemberTagPermissionDao extends BaseDao
|
||||
{
|
||||
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberTagPermission::class;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\model\member\MemberTagRelation;
|
||||
|
||||
/**
|
||||
* 会员标签关系数据访问对象
|
||||
*/
|
||||
class MemberTagRelationDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberTagRelation::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存标签关系(自动过滤已存在的记录)
|
||||
*
|
||||
* @param array $data 标签关系数据 [['member_id' => xx, 'tag_id' => yy], ...]
|
||||
* @return array 返回结果 ['saved' => int, 'duplicates' => int, 'failed' => int, 'errors' => array]
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function batchSave(array $data): array
|
||||
{
|
||||
if (empty($data)) {
|
||||
return [
|
||||
'saved' => 0,
|
||||
'duplicates' => 0,
|
||||
'failed' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
}
|
||||
|
||||
// 验证数据格式
|
||||
foreach ($data as $index => $item) {
|
||||
if (!isset($item['member_id']) || !isset($item['tag_id'])) {
|
||||
throw new \Exception("数据格式错误: 第 {$index} 条记录缺少 member_id 或 tag_id");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查已存在的记录
|
||||
$existing = $this->getExistingRelations($data);
|
||||
|
||||
// 过滤掉已存在的记录
|
||||
$newData = $this->filterExistingRelations($data, $existing);
|
||||
|
||||
$duplicates = count($data) - count($newData);
|
||||
|
||||
if (empty($newData)) {
|
||||
return [
|
||||
'saved' => 0,
|
||||
'duplicates' => $duplicates,
|
||||
'failed' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
}
|
||||
|
||||
try {
|
||||
// 使用批量插入(性能更好)
|
||||
$result = $this->getModel()->insert($newData);
|
||||
|
||||
if (!$result) {
|
||||
throw new \Exception('批量插入失败');
|
||||
}
|
||||
|
||||
return [
|
||||
'saved' => count($newData),
|
||||
'duplicates' => $duplicates,
|
||||
'failed' => 0,
|
||||
'errors' => []
|
||||
];
|
||||
} catch (\Throwable $e) {
|
||||
// 检查是否是唯一键冲突
|
||||
if ($this->isDuplicateEntryError($e)) {
|
||||
return [
|
||||
'saved' => 0,
|
||||
'duplicates' => count($data),
|
||||
'failed' => 0,
|
||||
'errors' => ['部分记录可能已存在']
|
||||
];
|
||||
}
|
||||
|
||||
throw new \Exception("批量保存失败: " . $e->getMessage(), 0, $e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取已存在的关联关系
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getExistingRelations(array $data): array
|
||||
{
|
||||
if (empty($data)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$query = $this->query();
|
||||
|
||||
// 构建查询:WHERE (member_id = x AND tag_id = y) OR (member_id = a AND tag_id = b) ...
|
||||
$query->where(function($q) use ($data) {
|
||||
foreach ($data as $item) {
|
||||
$q->orWhere(function($subQ) use ($item) {
|
||||
$subQ->where('member_id', $item['member_id'])
|
||||
->where('tag_id', $item['tag_id']);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return $query->get()->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤掉已存在的关联关系
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $existing
|
||||
* @return array
|
||||
*/
|
||||
protected function filterExistingRelations(array $data, array $existing): array
|
||||
{
|
||||
if (empty($existing)) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
// 构建已存在记录的 map
|
||||
$existingMap = [];
|
||||
foreach ($existing as $record) {
|
||||
$key = $record['member_id'] . ':' . $record['tag_id'];
|
||||
$existingMap[$key] = true;
|
||||
}
|
||||
|
||||
// 过滤掉已存在的记录
|
||||
$newData = [];
|
||||
foreach ($data as $item) {
|
||||
$key = $item['member_id'] . ':' . $item['tag_id'];
|
||||
if (!isset($existingMap[$key])) {
|
||||
$newData[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
return $newData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是重复记录错误
|
||||
*
|
||||
* @param \Throwable $e
|
||||
* @return bool
|
||||
*/
|
||||
protected function isDuplicateEntryError(\Throwable $e): bool
|
||||
{
|
||||
$message = $e->getMessage();
|
||||
return str_contains($message, 'Duplicate entry') ||
|
||||
str_contains($message, '1062') ||
|
||||
str_contains($message, 'Integrity constraint violation');
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 添加标签关系
|
||||
// */
|
||||
// public function addTagRelation(int $memberId, int $tagId): bool
|
||||
// {
|
||||
// $exists = $this->query()
|
||||
// ->where('member_id', $memberId)
|
||||
// ->where('tag_id', $tagId)
|
||||
// ->exists();
|
||||
//
|
||||
// if (!$exists) {
|
||||
// $relation = $this->getModel();
|
||||
// $relation->member_id = $memberId;
|
||||
// $relation->tag_id = $tagId;
|
||||
// return $relation->save();
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 移除标签关系
|
||||
// */
|
||||
// public function removeTagRelation(int $memberId, int $tagId): bool
|
||||
// {
|
||||
// return $this->query()
|
||||
// ->where('member_id', $memberId)
|
||||
// ->where('tag_id', $tagId)
|
||||
// ->delete() > 0;
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取会员的标签ID列表
|
||||
// */
|
||||
// public function getMemberTagIds(int $memberId): array
|
||||
// {
|
||||
// return $this->query()
|
||||
// ->where('member_id', $memberId)
|
||||
// ->pluck('tag_id')
|
||||
// ->toArray();
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 获取标签的会员数量
|
||||
// */
|
||||
// public function getTagMemberCount(int $tagId): int
|
||||
// {
|
||||
// return $this->count(['tag_id' => $tagId]);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use app\enum\common\EnabledStatus;
|
||||
use core\base\BaseDao;
|
||||
use app\model\member\MemberThirdParty;
|
||||
use app\model\member\Member;
|
||||
|
||||
/**
|
||||
* 会员第三方登录DAO
|
||||
*/
|
||||
class MemberThirdPartyDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberThirdParty::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定第三方账号
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function bind(int|string $memberId, int $platform, string $openid, array $data = []): MemberThirdParty
|
||||
{
|
||||
// 检查是否已绑定
|
||||
$thirdParty = $this->findByMemberAndPlatform($memberId, $platform);
|
||||
|
||||
if ($thirdParty) {
|
||||
// 更新绑定信息
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, $thirdParty->fillable)) {
|
||||
$thirdParty->$key = $value;
|
||||
}
|
||||
}
|
||||
$thirdParty->openid = $openid;
|
||||
$thirdParty->enabled = EnabledStatus::ENABLED->value;
|
||||
$thirdParty->save();
|
||||
return $thirdParty;
|
||||
}
|
||||
|
||||
// 创建新绑定
|
||||
$thirdParty = new MemberThirdParty();
|
||||
$thirdParty->member_id = $memberId;
|
||||
$thirdParty->platform = $platform;
|
||||
$thirdParty->openid = $openid;
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (in_array($key, $thirdParty->fillable)) {
|
||||
$thirdParty->$key = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$thirdParty->enabled = EnabledStatus::ENABLED->value;
|
||||
$thirdParty->save();
|
||||
|
||||
return $thirdParty;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据会员ID和平台查找第三方账号
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function findByMemberAndPlatform(int|string $memberId, int $platform): ?MemberThirdParty
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('platform', $platform)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据第三方账号查找会员
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function findMemberByThirdParty(int $platform, string $openid): ?Member
|
||||
{
|
||||
$thirdParty = $this->query()
|
||||
->where('platform', $platform)
|
||||
->where('openid', $openid)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->with('member')
|
||||
->first();
|
||||
|
||||
/** @var TYPE_NAME $thirdParty */
|
||||
return $thirdParty?->member;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据unionid查找会员
|
||||
*/
|
||||
public function findMemberByUnionId(string $unionid): ?Member
|
||||
{
|
||||
$thirdParty = $this->query()
|
||||
->where('unionid', $unionid)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->with('member')
|
||||
->first();
|
||||
|
||||
/** @var TYPE_NAME $thirdParty */
|
||||
return $thirdParty?->member;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员的第三方账号列表
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getMemberThirdParties(int|string $memberId): array
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑第三方账号
|
||||
*/
|
||||
public function unbind(MemberThirdParty $thirdParty): bool
|
||||
{
|
||||
$thirdParty->enabled = EnabledStatus::DISABLED->value;
|
||||
return $thirdParty->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查第三方账号是否已绑定
|
||||
*/
|
||||
public function isBound(int $platform, string $openid): bool
|
||||
{
|
||||
return $this->query()
|
||||
->where('platform', $platform)
|
||||
->where('openid', $openid)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新第三方登录令牌
|
||||
*/
|
||||
public function updateToken(MemberThirdParty $thirdParty, string $accessToken, string $refreshToken = '', int $expiresIn = 0): bool
|
||||
{
|
||||
$thirdParty->access_token = $accessToken;
|
||||
if ($refreshToken) {
|
||||
$thirdParty->refresh_token = $refreshToken;
|
||||
}
|
||||
if ($expiresIn > 0) {
|
||||
$thirdParty->expires_at = date('Y-m-d H:i:s', time() + $expiresIn);
|
||||
}
|
||||
return $thirdParty->save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\enum\common\EnabledStatus;
|
||||
use app\enum\member\WithdrawAccountType;
|
||||
use app\model\member\MemberWithdrawAccount;
|
||||
|
||||
/**
|
||||
* 会员提现账号数据访问对象
|
||||
*/
|
||||
class MemberWithdrawAccountDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberWithdrawAccount::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置默认账号
|
||||
*/
|
||||
public function setDefault(MemberWithdrawAccount $account): bool
|
||||
{
|
||||
// 先将该会员的所有提现账号设置为非默认
|
||||
$this->query()
|
||||
->where('member_id', $account->member_id)
|
||||
->update(['is_default' => 0]);
|
||||
|
||||
// 将当前账号设置为默认
|
||||
$account->is_default = 1;
|
||||
return $account->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员默认提现账号
|
||||
*/
|
||||
public function getDefaultAccount(int $memberId): ?MemberWithdrawAccount
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('is_default', 1)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员提现账号列表
|
||||
*/
|
||||
public function getMemberAccounts(int $memberId): array
|
||||
{
|
||||
return $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('enabled', EnabledStatus::ENABLED->value)
|
||||
->orderBy('is_default', 'desc')
|
||||
->orderBy('updated_at', 'desc')
|
||||
->get()
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建或更新提现账号
|
||||
*/
|
||||
public function saveAccount(int $memberId, array $data): MemberWithdrawAccount
|
||||
{
|
||||
$accountId = $data['id'] ?? 0;
|
||||
|
||||
if ($accountId) {
|
||||
// 更新账号
|
||||
$account = $this->find($accountId);
|
||||
if (!$account || $account->member_id != $memberId) {
|
||||
throw new \Exception('账号不存在或无权限');
|
||||
}
|
||||
} else {
|
||||
// 创建新账号
|
||||
$account = $this->getModel();
|
||||
$account->member_id = $memberId;
|
||||
$account->enabled = EnabledStatus::ENABLED->value;
|
||||
}
|
||||
|
||||
// 填充数据
|
||||
$fillable = [
|
||||
'type',
|
||||
'bank_name',
|
||||
'account_name',
|
||||
'account_number',
|
||||
'branch_name',
|
||||
];
|
||||
|
||||
foreach ($fillable as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$account->{$field} = $data[$field];
|
||||
}
|
||||
}
|
||||
|
||||
// 设置默认账号
|
||||
if (isset($data['is_default']) && $data['is_default'] == 1) {
|
||||
// 先将其他账号设置为非默认
|
||||
$this->query()
|
||||
->where('member_id', $memberId)
|
||||
->update(['is_default' => 0]);
|
||||
$account->is_default = 1;
|
||||
} elseif (!$accountId) {
|
||||
// 新账号默认为非默认
|
||||
$account->is_default = 0;
|
||||
}
|
||||
|
||||
$account->save();
|
||||
return $account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除提现账号
|
||||
*/
|
||||
public function deleteAccount(MemberWithdrawAccount $account): bool
|
||||
{
|
||||
// 检查是否有关联的提现记录
|
||||
$withdrawCount = $account->withdraws()->count();
|
||||
if ($withdrawCount > 0) {
|
||||
throw new \Exception('该账号有关联的提现记录,无法删除');
|
||||
}
|
||||
|
||||
$account->enabled = EnabledStatus::DISABLED->value;
|
||||
return $account->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证账号数据
|
||||
*/
|
||||
public function validateAccount(array $data): array
|
||||
{
|
||||
$errors = [];
|
||||
|
||||
if (empty($data['type'])) {
|
||||
$errors[] = '账号类型不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['bank_name'])) {
|
||||
$errors[] = '银行名称不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['account_name'])) {
|
||||
$errors[] = '账户姓名不能为空';
|
||||
}
|
||||
|
||||
if (empty($data['account_number'])) {
|
||||
$errors[] = '账号不能为空';
|
||||
}
|
||||
|
||||
// 根据账号类型进行特殊验证
|
||||
if (isset($data['type'])) {
|
||||
switch ($data['type']) {
|
||||
case WithdrawAccountType::BANK->value:
|
||||
if (empty($data['branch_name'])) {
|
||||
$errors[] = '开户行不能为空';
|
||||
}
|
||||
break;
|
||||
case WithdrawAccountType::ALIPAY->value:
|
||||
// 支付宝账号验证
|
||||
if (!preg_match('/^1[3-9]\d{9}$|^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/', $data['account_number'])) {
|
||||
$errors[] = '支付宝账号格式不正确(请输入手机号或邮箱)';
|
||||
}
|
||||
break;
|
||||
case WithdrawAccountType::WECHAT->value:
|
||||
// 微信账号验证
|
||||
if (!preg_match('/^[a-zA-Z0-9_-]{6,20}$/', $data['account_number'])) {
|
||||
$errors[] = '微信账号格式不正确';
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员账号数量
|
||||
*/
|
||||
public function getMemberAccountCount(int $memberId): int
|
||||
{
|
||||
return $this->count([
|
||||
'member_id' => $memberId,
|
||||
'enabled' => EnabledStatus::ENABLED->value
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账号使用次数
|
||||
*/
|
||||
public function getUsageCount(MemberWithdrawAccount $account): int
|
||||
{
|
||||
return $account->withdraws()->count();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace app\dao\member;
|
||||
|
||||
use core\base\BaseDao;
|
||||
use app\model\member\MemberWithdraw;
|
||||
use app\enum\member\WithdrawStatus;
|
||||
|
||||
/**
|
||||
* 会员提现DAO
|
||||
*/
|
||||
class MemberWithdrawDao extends BaseDao
|
||||
{
|
||||
/**
|
||||
* 设置模型
|
||||
*/
|
||||
protected function setModel(): string
|
||||
{
|
||||
return MemberWithdraw::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成提现订单号
|
||||
*/
|
||||
public function generateOrderSn(): string
|
||||
{
|
||||
return 'WD' . date('YmdHis') . substr(implode('', array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建提现记录
|
||||
*/
|
||||
public function create(int $memberId, int $accountId, float $amount, float $fee, float $actualAmount, string $bankName, string $bankAccount, string $bankCardholder, string $orderSn, string $remark = ''): MemberWithdraw
|
||||
{
|
||||
$withdraw = $this->getModel();
|
||||
$withdraw->member_id = $memberId;
|
||||
$withdraw->account_id = $accountId;
|
||||
$withdraw->amount = $amount;
|
||||
$withdraw->fee = $fee;
|
||||
$withdraw->actual_amount = $actualAmount;
|
||||
$withdraw->status = WithdrawStatus::PENDING->value;
|
||||
$withdraw->bank_name = $bankName;
|
||||
$withdraw->bank_account = $bankAccount;
|
||||
$withdraw->bank_cardholder = $bankCardholder;
|
||||
$withdraw->order_sn = $orderSn;
|
||||
$withdraw->remark = $remark;
|
||||
$withdraw->save();
|
||||
|
||||
return $withdraw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据订单号查找提现记录
|
||||
*/
|
||||
public function findByOrderSn(string $orderSn): ?MemberWithdraw
|
||||
{
|
||||
return $this->query()
|
||||
->where('order_sn', $orderSn)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新提现状态
|
||||
*/
|
||||
public function updateStatus(MemberWithdraw $withdraw, int $status, string $auditRemark = ''): bool
|
||||
{
|
||||
$withdraw->status = $status;
|
||||
if ($auditRemark) {
|
||||
$withdraw->audit_remark = $auditRemark;
|
||||
}
|
||||
$withdraw->audit_at = date('Y-m-d H:i:s');
|
||||
return $withdraw->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取会员提现列表
|
||||
*/
|
||||
public function getMemberWithdraws(int $memberId, array $params = []): array
|
||||
{
|
||||
$query = $this->query()->where('member_id', $memberId);
|
||||
|
||||
// 状态筛选
|
||||
if (isset($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (isset($params['start_date']) && isset($params['end_date'])) {
|
||||
$query->whereBetween('created_at', [
|
||||
$params['start_date'] . ' 00:00:00',
|
||||
$params['end_date'] . ' 23:59:59'
|
||||
]);
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
$page = $params['page'] ?? 1;
|
||||
$limit = $params['limit'] ?? 10;
|
||||
|
||||
return $query->orderBy('created_at', 'desc')
|
||||
->paginate($limit, ['*'], 'page', $page)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提现统计
|
||||
*/
|
||||
public function getWithdrawStats(int $memberId): array
|
||||
{
|
||||
$totalApply = $this->count(['member_id' => $memberId]);
|
||||
$totalSuccess = $this->count([
|
||||
'member_id' => $memberId,
|
||||
'status' => WithdrawStatus::COMPLETED->value
|
||||
]);
|
||||
$totalAmount = $this->query()
|
||||
->where('member_id', $memberId)
|
||||
->where('status', WithdrawStatus::COMPLETED->value)
|
||||
->sum('actual_amount');
|
||||
|
||||
return [
|
||||
'total_apply' => $totalApply,
|
||||
'total_success' => $totalSuccess,
|
||||
'total_amount' => $totalAmount,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取待审核提现列表
|
||||
*/
|
||||
public function getPendingWithdraws(array $params = []): array
|
||||
{
|
||||
$query = $this->query()
|
||||
->where('status', WithdrawStatus::PENDING->value);
|
||||
|
||||
// 会员ID筛选
|
||||
if (isset($params['member_id'])) {
|
||||
$query->where('member_id', $params['member_id']);
|
||||
}
|
||||
|
||||
// 分页参数
|
||||
$page = $params['page'] ?? 1;
|
||||
$limit = $params['limit'] ?? 10;
|
||||
|
||||
return $query->orderBy('created_at', 'desc')
|
||||
->with('member')
|
||||
->paginate($limit, ['*'], 'page', $page)
|
||||
->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取提现总数
|
||||
*/
|
||||
public function getCount(array $params = []): int
|
||||
{
|
||||
$query = $this->query();
|
||||
|
||||
// 会员ID筛选
|
||||
if (isset($params['member_id'])) {
|
||||
$query->where('member_id', $params['member_id']);
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
if (isset($params['status'])) {
|
||||
$query->where('status', $params['status']);
|
||||
}
|
||||
|
||||
return $query->count();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user