363 lines
9.8 KiB
PHP
363 lines
9.8 KiB
PHP
<?php
|
||||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
namespace app\service\api\member;
|
|||
|
|
|
|||
|
|
use app\api\CurrentMember;
|
|||
|
|
use app\dao\member\MemberDao;
|
|||
|
|
use app\dao\member\MemberThirdPartyDao;
|
|||
|
|
use app\model\member\Member;
|
|||
|
|
use app\model\member\MemberAddress;
|
|||
|
|
use app\enum\common\EnabledStatus;
|
|||
|
|
use core\base\BaseService;
|
|||
|
|
use support\Container;
|
|||
|
|
use support\Redis;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 会员用户服务
|
|||
|
|
*/
|
|||
|
|
class MemberService extends BaseService
|
|||
|
|
{
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 构造方法
|
|||
|
|
*/
|
|||
|
|
public function __construct(MemberDao $dao)
|
|||
|
|
{
|
|||
|
|
$this->dao = $dao;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新会员信息
|
|||
|
|
*
|
|||
|
|
* @throws \Exception
|
|||
|
|
* @throws \Throwable
|
|||
|
|
*/
|
|||
|
|
public function updateInfo(array $data): void
|
|||
|
|
{
|
|||
|
|
$currentMember = Container::make(CurrentMember::class);
|
|||
|
|
$id = $currentMember->id();
|
|||
|
|
|
|||
|
|
if (empty($id)) {
|
|||
|
|
throw new \Exception('用户未登录', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$model = $this->dao->get($id);
|
|||
|
|
if (!$model) {
|
|||
|
|
throw new \Exception('用户不存在', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$allowedFields = ['nickname', 'avatar', 'gender', 'birthday','bio'];
|
|||
|
|
$updateData = [];
|
|||
|
|
foreach ($allowedFields as $field) {
|
|||
|
|
if (isset($data[$field])) {
|
|||
|
|
$updateData[$field] = $data[$field];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (empty($updateData)) {
|
|||
|
|
throw new \Exception('没有可更新的数据', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$model->fill($updateData);
|
|||
|
|
|
|||
|
|
if (!$model->isDirty()) {
|
|||
|
|
throw new \Exception('数据没有变化', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$model->save();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 绑定会员手机号
|
|||
|
|
*/
|
|||
|
|
public function bindPhone(array $data): array
|
|||
|
|
{
|
|||
|
|
if (empty($data['phone']) || empty($data['verify_code'])) {
|
|||
|
|
throw new \Exception('手机号和验证码不能为空', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证短信验证码
|
|||
|
|
$this->verifySmsCode($data['phone'], $data['verify_code']);
|
|||
|
|
|
|||
|
|
$memberId = $this->getCurrentMemberId();
|
|||
|
|
$member = Member::find($memberId);
|
|||
|
|
|
|||
|
|
if (!$member) {
|
|||
|
|
throw new \Exception('用户不存在', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查手机号是否已被其他用户绑定
|
|||
|
|
$existingMember = Member::where('phone', $data['phone'])
|
|||
|
|
->where('id', '!=', $memberId)
|
|||
|
|
->first();
|
|||
|
|
|
|||
|
|
if ($existingMember) {
|
|||
|
|
throw new \Exception('手机号已被其他用户绑定', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 更新手机号
|
|||
|
|
$member->phone = $data['phone'];
|
|||
|
|
$member->save();
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
'code' => 200,
|
|||
|
|
'msg' => '绑定成功',
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 修改密码
|
|||
|
|
*
|
|||
|
|
* @throws \Exception
|
|||
|
|
*/
|
|||
|
|
public function changePassword(array $data): void
|
|||
|
|
{
|
|||
|
|
if (empty($data['old_password']) || empty($data['new_password'])) {
|
|||
|
|
throw new \Exception('旧密码和新密码不能为空', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$currentMember = Container::make(CurrentMember::class);
|
|||
|
|
$id = $currentMember->id();
|
|||
|
|
|
|||
|
|
if (empty($id)) {
|
|||
|
|
throw new \Exception('用户未登录', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$model = $this->dao->get($id);
|
|||
|
|
if (!$model) {
|
|||
|
|
throw new \Exception('用户不存在', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!$model->verifyPassword($data['old_password'])) {
|
|||
|
|
throw new \Exception('旧密码错误', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$model->password = $data['new_password'];
|
|||
|
|
$model->save();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取地址列表
|
|||
|
|
*/
|
|||
|
|
public function getAddressList(): array
|
|||
|
|
{
|
|||
|
|
$memberId = $this->getCurrentMemberId();
|
|||
|
|
$addresses = MemberAddress::where('member_id', $memberId)
|
|||
|
|
->where('enabled', EnabledStatus::ENABLED->value)
|
|||
|
|
->orderBy('is_default', 'desc')
|
|||
|
|
->orderBy('id', 'desc')
|
|||
|
|
->get();
|
|||
|
|
|
|||
|
|
return $addresses->toArray();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 创建地址
|
|||
|
|
*/
|
|||
|
|
public function createAddress(array $data): array
|
|||
|
|
{
|
|||
|
|
if (empty($data['name']) || empty($data['phone']) || empty($data['province']) ||
|
|||
|
|
empty($data['city']) || empty($data['district']) || empty($data['address'])) {
|
|||
|
|
throw new \Exception('请填写完整的地址信息', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$memberId = $this->getCurrentMemberId();
|
|||
|
|
|
|||
|
|
// 如果设置为默认地址,取消其他默认地址
|
|||
|
|
if ($data['is_default'] ?? false) {
|
|||
|
|
MemberAddress::where('member_id', $memberId)
|
|||
|
|
->where('is_default', true)
|
|||
|
|
->update(['is_default' => false]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$addressData = [
|
|||
|
|
'member_id' => $memberId,
|
|||
|
|
'name' => $data['name'],
|
|||
|
|
'phone' => $data['phone'],
|
|||
|
|
'province' => $data['province'],
|
|||
|
|
'city' => $data['city'],
|
|||
|
|
'district' => $data['district'],
|
|||
|
|
'address' => $data['address'],
|
|||
|
|
'is_default' => $data['is_default'] ?? false,
|
|||
|
|
'enabled' => EnabledStatus::ENABLED->value,
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
$address = MemberAddress::create($addressData);
|
|||
|
|
|
|||
|
|
return ['id' => $address->id];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 更新地址
|
|||
|
|
*/
|
|||
|
|
public function updateAddress(int $id, array $data): array
|
|||
|
|
{
|
|||
|
|
$memberId = $this->getCurrentMemberId();
|
|||
|
|
$address = MemberAddress::where('id', $id)
|
|||
|
|
->where('member_id', $memberId)
|
|||
|
|
->first();
|
|||
|
|
|
|||
|
|
if (!$address) {
|
|||
|
|
throw new \Exception('地址不存在', 404);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果设置为默认地址,取消其他默认地址
|
|||
|
|
if ($data['is_default'] ?? false) {
|
|||
|
|
MemberAddress::where('member_id', $memberId)
|
|||
|
|
->where('id', '!=', $id)
|
|||
|
|
->where('is_default', true)
|
|||
|
|
->update(['is_default' => false]);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 允许更新的字段
|
|||
|
|
$allowedFields = ['name', 'phone', 'province', 'city', 'district', 'address', 'is_default'];
|
|||
|
|
$updateData = [];
|
|||
|
|
|
|||
|
|
foreach ($allowedFields as $field) {
|
|||
|
|
if (isset($data[$field])) {
|
|||
|
|
$updateData[$field] = $data[$field];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (empty($updateData)) {
|
|||
|
|
throw new \Exception('没有可更新的数据', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$address->update($updateData);
|
|||
|
|
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除地址
|
|||
|
|
*/
|
|||
|
|
public function deleteAddress(int $id): array
|
|||
|
|
{
|
|||
|
|
$memberId = $this->getCurrentMemberId();
|
|||
|
|
$address = MemberAddress::where('id', $id)
|
|||
|
|
->where('member_id', $memberId)
|
|||
|
|
->first();
|
|||
|
|
|
|||
|
|
if (!$address) {
|
|||
|
|
throw new \Exception('地址不存在', 404);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 软删除
|
|||
|
|
$address->enabled = EnabledStatus::DISABLED->value;
|
|||
|
|
$address->save();
|
|||
|
|
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 验证短信验证码
|
|||
|
|
*/
|
|||
|
|
private function verifySmsCode(string $mobile, string $code): void
|
|||
|
|
{
|
|||
|
|
$cacheKey = 'sms_code:' . $mobile;
|
|||
|
|
$cachedCode = Redis::get($cacheKey);
|
|||
|
|
|
|||
|
|
if (!$cachedCode || $cachedCode !== $code) {
|
|||
|
|
throw new \Exception('验证码错误或已过期', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证成功后删除验证码
|
|||
|
|
Redis::del($cacheKey);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取当前用户ID
|
|||
|
|
*/
|
|||
|
|
private function getCurrentMemberId(): int
|
|||
|
|
{
|
|||
|
|
$request = \request();
|
|||
|
|
$token = $request->header('Authorization');
|
|||
|
|
|
|||
|
|
if (!$token) {
|
|||
|
|
throw new \Exception('未登录', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$tokenData = Redis::get('user_token:' . md5($token));
|
|||
|
|
if (!$tokenData) {
|
|||
|
|
throw new \Exception('登录已过期', 401);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$data = json_decode($tokenData, true);
|
|||
|
|
return $data['member_id'] ?? 0;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 上传头像
|
|||
|
|
*/
|
|||
|
|
public function uploadAvatar(\Webman\Http\UploadFile $file): array
|
|||
|
|
{
|
|||
|
|
$memberId = $this->getCurrentMemberId();
|
|||
|
|
|
|||
|
|
// 验证文件类型
|
|||
|
|
$allowedTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp'];
|
|||
|
|
if (!in_array($file->getUploadMimeType(), $allowedTypes)) {
|
|||
|
|
throw new \Exception('只支持jpg、png、gif、webp格式的图片', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 验证文件大小(最大2MB)
|
|||
|
|
$maxSize = 2 * 1024 * 1024;
|
|||
|
|
if ($file->getSize() > $maxSize) {
|
|||
|
|
throw new \Exception('图片大小不能超过2MB', 400);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 生成文件名
|
|||
|
|
$extension = pathinfo($file->getUploadName(), PATHINFO_EXTENSION);
|
|||
|
|
$filename = 'avatar_' . $memberId . '_' . time() . '.' . $extension;
|
|||
|
|
|
|||
|
|
// 保存文件
|
|||
|
|
$uploadPath = public_path() . '/uploads/avatar/';
|
|||
|
|
if (!is_dir($uploadPath)) {
|
|||
|
|
mkdir($uploadPath, 0755, true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$file->moveTo($uploadPath . $filename);
|
|||
|
|
|
|||
|
|
// 生成访问URL
|
|||
|
|
$url = '/uploads/avatar/' . $filename;
|
|||
|
|
|
|||
|
|
// 更新会员头像
|
|||
|
|
$member = Member::find($memberId);
|
|||
|
|
if ($member) {
|
|||
|
|
$member->avatar = $url;
|
|||
|
|
$member->save();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return [
|
|||
|
|
'url' => $url,
|
|||
|
|
'filename' => $filename
|
|||
|
|
];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取活跃用户
|
|||
|
|
*/
|
|||
|
|
public function getActiveUsers(int $limit = 10): array
|
|||
|
|
{
|
|||
|
|
$member = $this->dao->getActiveUsers($limit);
|
|||
|
|
return $member->map(function ($item) {
|
|||
|
|
return [
|
|||
|
|
'id' => $item->id,
|
|||
|
|
'username' => $item->username,
|
|||
|
|
'nickname' => $item->nickname,
|
|||
|
|
'avatar' => $item->avatar,
|
|||
|
|
'intro' => $item->intro ?? "",
|
|||
|
|
'last_active' => $item->last_time,
|
|||
|
|
];
|
|||
|
|
})->toArray();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 检查用户是否是管理员
|
|||
|
|
* 暂时返回 false,只有问题作者可以设置最佳答案
|
|||
|
|
*/
|
|||
|
|
public function isAdmin(int|string $userId): bool
|
|||
|
|
{
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|