chore: 重写初始提交(清空历史,整理后全量提交)
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
|
||||
/**
|
||||
* Feedback 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Feedback extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
/**
|
||||
* 提交意见反馈
|
||||
* type: 1-功能建议 2-投诉举报 3-其他
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$content = trim((string) Request::post('content', ''));
|
||||
$type = (int) Request::post('type', 1);
|
||||
$contact = trim((string) Request::post('contact', ''));
|
||||
|
||||
if ($content === '') {
|
||||
$this->error('反馈内容不能为空');
|
||||
}
|
||||
if (mb_strlen($content) > 500) {
|
||||
$this->error('反馈内容过长(最多500字)');
|
||||
}
|
||||
|
||||
Db::name('wxchat_feedback')->insert([
|
||||
'uid' => $uid,
|
||||
'type' => $type,
|
||||
'content' => $content,
|
||||
'contact' => $contact,
|
||||
'status' => 0,
|
||||
'create_at' => time(),
|
||||
]);
|
||||
|
||||
$this->success([], '提交成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的反馈列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$list = Db::name('wxchat_feedback')
|
||||
->where('uid', $uid)
|
||||
->order('create_at', 'desc')
|
||||
->limit(50)
|
||||
->field('id,type,content,status,create_at')
|
||||
->select();
|
||||
$this->success(['list' => $list]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,389 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatFollow as UserFollow;
|
||||
use addon\wxchat\model\WxchatIntimacy as UserIntimacy;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\model\MemberUser as UserModel;
|
||||
|
||||
/**
|
||||
* Follow 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Follow extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* Summary of needLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['*'];
|
||||
|
||||
/**
|
||||
* Summary of needRight
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 _initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 关注/取消关注用户
|
||||
* @return Json
|
||||
*/
|
||||
public function toggleFollow(): Json
|
||||
{
|
||||
$uid = $this->request->param('uid', 88888888);
|
||||
$targetUid = $this->request->param('target_uid', 88888891);
|
||||
$action = $this->request->param('action', 1); // 1-关注,0-取消关注
|
||||
if (! $uid || ! $targetUid) {
|
||||
$this->error('用户ID和目标用户ID不能为空', 400);
|
||||
}
|
||||
if ($uid == $targetUid) {
|
||||
$this->error('不能关注自己', 400);
|
||||
}
|
||||
// 检查目标用户是否存在
|
||||
$targetUser = UserModel::where('uid', $targetUid)->findOrEmpty();
|
||||
if ($targetUser->isEmpty()) {
|
||||
$this->error('目标用户不存在', 404);
|
||||
}
|
||||
// 检查是否已关注
|
||||
$follow = UserFollow::where('uid', $uid)->where('fid', $targetUid)->findOrEmpty();
|
||||
DB::startTrans();
|
||||
try {
|
||||
if ($follow->isEmpty()) {
|
||||
$follow = new UserFollow();
|
||||
$follow->uid = $uid;
|
||||
$follow->fid = $targetUid;
|
||||
$follow->status = 1;
|
||||
$follow->save();
|
||||
|
||||
} else {
|
||||
$follow->status = $follow->status == 1 ? 0 : 1;
|
||||
$follow->save();
|
||||
}
|
||||
if ($follow->status == 1) {
|
||||
// UserModel::where('id', $uid)->inc('following_count')->update();
|
||||
// UserModel::where('id', $targetuid)->inc('follower_count')->update();
|
||||
$mutual = UserFollow::where('fid', $targetUid)
|
||||
->where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
$isMutual = $mutual ? 1 : 0;
|
||||
// 增加亲密度
|
||||
$this->updateIntimacy($uid, $targetUid, 5);
|
||||
DB::commit();
|
||||
$this->success(['is_mutual' => $isMutual, 'follow_status' => 1], '关注成功');
|
||||
} else {
|
||||
// 更新用户关注数
|
||||
// UserModel::where('id', $uid)->dec('following_count')->update();
|
||||
// UserModel::where('id', $targetUid)->dec('follower_count')->update();
|
||||
// 减少亲密度
|
||||
$this->updateIntimacy($uid, $targetUid, -5);
|
||||
DB::commit();
|
||||
$this->success(['follow_status' => 0], '取消关注成功');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
DB::rollback();
|
||||
$this->error('操作失败: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关注列表
|
||||
* @return Json
|
||||
*/
|
||||
public function getFollowingList(): Json
|
||||
{
|
||||
$uid = $this->request->param('uid', 0);
|
||||
$page = $this->request->param('page', 1);
|
||||
$size = $this->request->param('size', 20);
|
||||
|
||||
if (! $uid) {
|
||||
$this->error('用户ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 我关注的人:uid=当前用户,取 fid 列表
|
||||
$total = UserFollow::where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
|
||||
$follows = UserFollow::where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->page($page, $size)
|
||||
->select();
|
||||
|
||||
$targetIds = [];
|
||||
foreach ($follows as $follow) {
|
||||
$targetIds[] = $follow->fid;
|
||||
}
|
||||
|
||||
// 批量取用户资料(按 uid 匹配,规避关联主键歧义)
|
||||
$userMap = [];
|
||||
if (! empty($targetIds)) {
|
||||
$users = UserModel::whereIn('uid', $targetIds)
|
||||
->field('uid,nickname,avatar')
|
||||
->select();
|
||||
foreach ($users as $u) {
|
||||
$userMap[$u->uid] = $u;
|
||||
}
|
||||
}
|
||||
|
||||
// 对方是否也关注了我(互关判断)
|
||||
$backIds = UserFollow::where('fid', $uid)
|
||||
->where('status', 1)
|
||||
->column('uid');
|
||||
$backSet = array_flip($backIds);
|
||||
|
||||
$list = [];
|
||||
foreach ($follows as $follow) {
|
||||
$user = $userMap[$follow->fid] ?? null;
|
||||
$list[] = [
|
||||
'id' => $follow->fid,
|
||||
'nickname' => $user ? $user->nickname : '',
|
||||
'avatar' => $user ? $user->avatar : '',
|
||||
'is_mutual' => isset($backSet[$follow->fid]) ? 1 : 0,
|
||||
'follow_time' => date('Y-m-d H:i:s', (int) $follow->create_at),
|
||||
];
|
||||
}
|
||||
$this->success([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
], '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取粉丝列表
|
||||
* @return Json
|
||||
*/
|
||||
public function getFollowerList(): Json
|
||||
{
|
||||
$uid = $this->request->param('uid', 0);
|
||||
$page = $this->request->param('page', 1);
|
||||
$size = $this->request->param('size', 20);
|
||||
|
||||
if (! $uid) {
|
||||
$this->error('用户ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 粉丝:谁关注了我,fid=当前用户,取 uid 列表
|
||||
$follows = UserFollow::where('fid', $uid)
|
||||
->where('status', 1)
|
||||
->order('id', 'desc')
|
||||
->page($page, $size)
|
||||
->select();
|
||||
|
||||
$total = UserFollow::where('fid', $uid)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
|
||||
$fanIds = [];
|
||||
foreach ($follows as $follow) {
|
||||
$fanIds[] = $follow->uid;
|
||||
}
|
||||
|
||||
// 批量取粉丝资料(按 uid 匹配)
|
||||
$userMap = [];
|
||||
if (! empty($fanIds)) {
|
||||
$users = UserModel::whereIn('uid', $fanIds)
|
||||
->field('uid,nickname,avatar')
|
||||
->select();
|
||||
foreach ($users as $u) {
|
||||
$userMap[$u->uid] = $u;
|
||||
}
|
||||
}
|
||||
|
||||
// 我是否也关注了该粉丝(互关判断)
|
||||
$myFollowIds = UserFollow::where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->column('fid');
|
||||
$mySet = array_flip($myFollowIds);
|
||||
|
||||
$list = [];
|
||||
foreach ($follows as $follow) {
|
||||
$user = $userMap[$follow->uid] ?? null;
|
||||
$list[] = [
|
||||
'id' => $follow->uid,
|
||||
'nickname' => $user ? $user->nickname : '',
|
||||
'avatar' => $user ? $user->avatar : '',
|
||||
'is_mutual' => isset($mySet[$follow->uid]) ? 1 : 0,
|
||||
'follow_time' => date('Y-m-d H:i:s', (int) $follow->create_at),
|
||||
];
|
||||
}
|
||||
$this->success([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
], '获取成功');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取互关列表
|
||||
* @return Json
|
||||
*/
|
||||
public function getMutualList(): Json
|
||||
{
|
||||
$uid = $this->request->param('uid', 0);
|
||||
$page = $this->request->param('page', 1);
|
||||
$size = $this->request->param('size', 20);
|
||||
|
||||
if (! $uid) {
|
||||
$this->error('用户ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 获取互关用户ID
|
||||
$mutualIds = UserFollow::alias('f1')
|
||||
->join('user_follows f2', 'f1.uid = f2.fid AND f1.fid = f2.uid')
|
||||
->where('f1.uid', $uid)
|
||||
->where('f1.status', 1)
|
||||
->where('f2.status', 1)
|
||||
->column('f1.fid');
|
||||
|
||||
if (empty($mutualIds)) {
|
||||
$this->success([
|
||||
'list' => [],
|
||||
'total' => 0,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
], '获取成功');
|
||||
|
||||
}
|
||||
|
||||
$users = UserModel::whereIn('id', $mutualIds)
|
||||
->field('id,username,nickname,avatar,following_count,follower_count')
|
||||
->page($page, $size)
|
||||
->select();
|
||||
|
||||
$total = count($mutualIds);
|
||||
|
||||
$list = [];
|
||||
foreach ($users as $user) {
|
||||
// 获取亲密度
|
||||
$intimacy = $this->getIntimacy($uid, $user->id);
|
||||
|
||||
$list[] = [
|
||||
'id' => $user->id,
|
||||
'nickname' => $user->nickname,
|
||||
'avatar' => $user->avatar,
|
||||
'following_count' => $user->following_count,
|
||||
'follower_count' => $user->follower_count,
|
||||
'intimacy' => $intimacy['intimacy'],
|
||||
'intimacy_level' => $intimacy['level'],
|
||||
'free_message_count' => $this->getFreeMessageCount($intimacy['level']),
|
||||
];
|
||||
}
|
||||
$this->success([
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'size' => $size,
|
||||
], '获取成功');
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新亲密度
|
||||
*/
|
||||
private function updateIntimacy($uid, $targetuid, $value)
|
||||
{
|
||||
$record = UserIntimacy::where('uid', $uid)
|
||||
->where('target_uid', $targetuid)
|
||||
->find();
|
||||
|
||||
if ($record) {
|
||||
$newIntimacy = $record->intimacy + $value;
|
||||
$newIntimacy = max(0, min(100, $newIntimacy)); // 限制在0-100之间
|
||||
|
||||
$record->intimacy = $newIntimacy;
|
||||
$record->level = $this->calculateIntimacyLevel($newIntimacy);
|
||||
$record->last_interaction = date('Y-m-d H:i:s');
|
||||
$record->save();
|
||||
} else {
|
||||
$intimacy = max(0, min(100, $value));
|
||||
$record = new UserIntimacy();
|
||||
$record->uid = $uid;
|
||||
$record->target_uid = $targetuid;
|
||||
$record->intimacy = $intimacy;
|
||||
$record->level = $this->calculateIntimacyLevel($intimacy);
|
||||
$record->save();
|
||||
}
|
||||
|
||||
// 同时更新对方的亲密度
|
||||
$this->updateIntimacy($targetuid, $uid, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取亲密度
|
||||
*/
|
||||
private function getIntimacy($uid, $targetuid)
|
||||
{
|
||||
$record = UserIntimacy::where('uid', $uid)
|
||||
->where('target_uid', $targetuid)
|
||||
->find();
|
||||
|
||||
if ($record) {
|
||||
return [
|
||||
'intimacy' => $record->intimacy,
|
||||
'level' => $record->level,
|
||||
];
|
||||
}
|
||||
|
||||
return ['intimacy' => 0, 'level' => 1];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算亲密度等级
|
||||
*/
|
||||
private function calculateIntimacyLevel($intimacy)
|
||||
{
|
||||
if ($intimacy >= 90) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
if ($intimacy >= 80) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if ($intimacy >= 60) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
if ($intimacy >= 40) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取免费消息数量
|
||||
*/
|
||||
private function getFreeMessageCount($level)
|
||||
{
|
||||
$counts = [0, 0, 2, 4, 5, PHP_INT_MAX];
|
||||
return $counts[$level] ?? 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatFriend as FriendModel;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
|
||||
/**
|
||||
* Friend 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Friend extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* Summary of noNeedLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = [];
|
||||
|
||||
/**
|
||||
* Summary of noNeedVerify
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
/**
|
||||
* 当前用户好友列表(已通过)
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$friends = FriendModel::where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->order('create_at', 'desc')
|
||||
->select();
|
||||
$list = [];
|
||||
foreach ($friends as $f) {
|
||||
$u = Db::name('member')->where('uid', $f->fid)
|
||||
->field('uid,nickname,avatar,phone')->find();
|
||||
if ($u) {
|
||||
$u['friend_id'] = $f->id;
|
||||
$u['remark'] = $f->remark;
|
||||
$list[] = $u;
|
||||
}
|
||||
}
|
||||
$this->success(['list' => $list, 'total' => count($list)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 待处理的好友请求(别人加我,status=0)
|
||||
*/
|
||||
public function requests()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$rows = FriendModel::where('fid', $uid)
|
||||
->where('status', 0)
|
||||
->order('create_at', 'desc')
|
||||
->select();
|
||||
$list = [];
|
||||
foreach ($rows as $r) {
|
||||
$u = Db::name('member')->where('uid', $r->uid)
|
||||
->field('uid,nickname,avatar')->find();
|
||||
if ($u) {
|
||||
$u['friend_id'] = $r->id;
|
||||
$u['remark'] = $r->remark;
|
||||
$list[] = $u;
|
||||
}
|
||||
}
|
||||
$this->success(['list' => $list, 'total' => count($list)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送好友请求
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$fid = (int) Request::post('fid');
|
||||
$remark = Request::post('remark', '');
|
||||
if ($fid <= 0 || $fid == $uid) {
|
||||
$this->error('无效的好友ID');
|
||||
}
|
||||
$exist = FriendModel::where('uid', $uid)->where('fid', $fid)->find();
|
||||
if ($exist) {
|
||||
if ($exist->status == 1) {
|
||||
$this->error('你们已经是好友了');
|
||||
}
|
||||
// 重新发起请求
|
||||
$exist->status = 0;
|
||||
$exist->remark = $remark;
|
||||
$exist->save();
|
||||
$this->success([], '已重新发送好友请求');
|
||||
}
|
||||
FriendModel::create([
|
||||
'uid' => $uid,
|
||||
'fid' => $fid,
|
||||
'remark' => $remark,
|
||||
'status' => 0,
|
||||
]);
|
||||
$this->success([], '好友请求已发送');
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理好友请求(接受/拒绝)
|
||||
* post: { id, action } action=1 接受, action=2 拒绝
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$id = (int) Request::post('id');
|
||||
$action = (int) Request::post('action', 1);
|
||||
$row = FriendModel::where('id', $id)->where('fid', $uid)->where('status', 0)->find();
|
||||
if (! $row) {
|
||||
$this->error('请求不存在或已处理');
|
||||
}
|
||||
if ($action == 2) {
|
||||
$row->delete();
|
||||
$this->success([], '已拒绝');
|
||||
}
|
||||
$row->status = 1;
|
||||
$row->save();
|
||||
$this->success([], '已添加为好友');
|
||||
}
|
||||
|
||||
/**
|
||||
* 好友详情
|
||||
*/
|
||||
public function read()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$id = (int) Request::post('id');
|
||||
$row = FriendModel::where('id', $id)->where('uid', $uid)->find();
|
||||
if (! $row) {
|
||||
$this->error('好友不存在');
|
||||
}
|
||||
$u = Db::name('member')->where('uid', $row->fid)
|
||||
->field('uid,nickname,avatar,phone')->find();
|
||||
if ($u) {
|
||||
$u['friend_id'] = $row->id;
|
||||
$u['remark'] = $row->remark;
|
||||
}
|
||||
$this->success($u);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改备注
|
||||
* post: { id, remark }
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$id = (int) Request::post('id');
|
||||
$row = FriendModel::where('id', $id)->where('uid', $uid)->find();
|
||||
if (! $row) {
|
||||
$this->error('好友不存在');
|
||||
}
|
||||
$row->remark = Request::post('remark', '');
|
||||
$row->save();
|
||||
$this->success([], '备注已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除好友
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
$uid = $this->currentUid();
|
||||
$id = (int) Request::post('id');
|
||||
$row = FriendModel::where('id', $id)->where('uid', $uid)->find();
|
||||
if (! $row) {
|
||||
$this->error('好友不存在');
|
||||
}
|
||||
$row->delete();
|
||||
$this->success([], '好友已删除');
|
||||
}
|
||||
|
||||
/**
|
||||
* 取当前登录用户 uid
|
||||
*/
|
||||
private function currentUid() : int
|
||||
{
|
||||
return (int) ($this->auth->model->uid ?? $this->auth->uid ?? 0);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatGift;
|
||||
use addon\wxchat\model\WxchatGiftRecord;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\service\WalletService;
|
||||
|
||||
/**
|
||||
* Gift 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Gift extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
if ($this->auth->isLogin) {
|
||||
$this->uid = $this->auth->model->uid;
|
||||
}
|
||||
}
|
||||
|
||||
private $uid = 0;
|
||||
|
||||
/**
|
||||
* 礼物商城列表
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$list = WxchatGift::where('status', 1)
|
||||
->order('sort', 'asc')
|
||||
->field('id,name,icon,price')
|
||||
->select();
|
||||
$this->success($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 赠送礼物(扣金币 + 记录)
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$giftId = (int) Request::post('gift_id', 0);
|
||||
$toUid = (int) Request::post('to_uid', 0);
|
||||
$count = max(1, (int) Request::post('count', 1));
|
||||
$msg = (string) Request::post('message', '');
|
||||
|
||||
if (! $toUid || $toUid == $uid) {
|
||||
$this->error('接收者无效');
|
||||
}
|
||||
$gift = WxchatGift::where('id', $giftId)->where('status', 1)->find();
|
||||
if (! $gift) {
|
||||
$this->error('礼物不存在');
|
||||
}
|
||||
$receiver = Db::name('member')->where('uid', $toUid)->value('uid');
|
||||
if (! $receiver) {
|
||||
$this->error('接收用户不存在');
|
||||
}
|
||||
|
||||
$total = (int) $gift->price * $count;
|
||||
try {
|
||||
WalletService::decCoins($uid, $total, "赠送礼物:{$gift->name}x{$count}");
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
|
||||
$record = new WxchatGiftRecord();
|
||||
$record->sender_uid = $uid;
|
||||
$record->receiver_uid = $toUid;
|
||||
$record->gift_id = $gift->id;
|
||||
$record->gift_name = $gift->name;
|
||||
$record->price = $gift->price;
|
||||
$record->count = $count;
|
||||
$record->total_price = $total;
|
||||
$record->message = $msg;
|
||||
$record->save();
|
||||
|
||||
$this->success([
|
||||
'gift' => $gift->name,
|
||||
'count' => $count,
|
||||
'cost' => $total,
|
||||
'coins_left'=> WalletService::getCoins($uid),
|
||||
], '赠送成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 我收到的礼物
|
||||
*/
|
||||
public function received()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$list = WxchatGiftRecord::where('receiver_uid', $uid)
|
||||
->order('create_at', 'desc')
|
||||
->limit(50)
|
||||
->select();
|
||||
$this->success($list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,268 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use think\facade\View;
|
||||
use ywxapp\controller\FrontendBase as AddonController;
|
||||
|
||||
/**
|
||||
* Index 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Index extends AddonController
|
||||
{
|
||||
|
||||
/**
|
||||
* Summary of noNeedLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['*'];
|
||||
|
||||
/**
|
||||
* Summary of noNeedVerify
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
return View::fetch();
|
||||
event('user.UserLogin', ['id' => 123]);
|
||||
return '您好!这是一个[wxchat]示例应用';
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用配置接口
|
||||
* @return array
|
||||
*/
|
||||
public function appConf()
|
||||
{
|
||||
$defaults = [
|
||||
'chatEnabled' => true, //聊天功能启用:布尔值
|
||||
'videoCallEnabled' => true, //视频通话功能启用:布尔值
|
||||
'voiceRoomEnabled' => true, //语音聊天室功能启用:布尔值
|
||||
'giftShopEnabled' => true, //礼物商店功能启用:布尔值
|
||||
'rechargeEnabled' => true, //充值功能启用:布尔值
|
||||
'vipEnabled' => true, //VIP功能启用:布尔值
|
||||
'matchEnabled' => true, //匹配功能启用:布尔值
|
||||
'momentEnabled' => true, //动态功能启用:布尔值
|
||||
|
||||
// 业务限制配置
|
||||
'dailyMatchLimit' => 10, //每日匹配次数限制:数字
|
||||
'dailyMessageLimit' => 100, //每日消息发送次数限制:数字
|
||||
'maxFriends' => 100, //好友数量上限:数字
|
||||
'maxMomentImages' => 9, //动态图片数量上限:数字
|
||||
'maxChatImageSize' => 1024, //聊天图片大小上限:数字
|
||||
'maxVideoDuration' => 60, //视频时长上限:数字
|
||||
'maxVoiceDuration' => 60, //音频时长上限:数字
|
||||
// 广告配置
|
||||
'enableAd' => true, //启用
|
||||
'splashAdId' => 'splash_1', //启动广告ID:字符串
|
||||
'bannerAdId' => 'banner_1', //横幅广告ID:字符串
|
||||
'interstitialAdId' => 'interstitial_1', //间隙广告ID:字符串
|
||||
'rewardAdId' => 'reward_1', //奖励广告ID:字符串
|
||||
'adFrequency' => 10, //广告频率:数字
|
||||
// 服务器配置
|
||||
'socketUrl' => 'wss://dev.xixingwl.cn:2346',
|
||||
'uploadUrl' => 'https://dev.xixingwl.cn:',
|
||||
'cdnUrl' => 'https://dev.xixingwl.cn',
|
||||
'apiUrl' => 'https://dev.xixingwl.cn',
|
||||
'apiVersion' => 'v1',
|
||||
// 第三方服务配置
|
||||
'wechatAppId' => 'wechat_1', //微信AppId:字符串
|
||||
'qqAppId' => 'qq_1', //qq AppId:字符串
|
||||
'appleServiceId' => 'apple_1', //苹果 服务ID:字符串
|
||||
'pushService' => 'default', //推送服务:字符串
|
||||
'mapService' => 'amap', //地图服务
|
||||
// 客服配置
|
||||
//customerService ?: ICustomerService
|
||||
// 版本兼容性
|
||||
'minIosVersion' => '10.0', //最小iOS版本:字符串
|
||||
'minAndroidVersion' => '5.0', //最小Android版本:字符串
|
||||
'supportedPlatforms' => ['ios', 'android'], //支持平台:字符串数组(保持代码默认,不纳入后台配置)
|
||||
];
|
||||
// 统一从数据库配置表读取(后台"插件管理 → 配置"可改),覆盖默认值
|
||||
$saved = \ywxapp\service\AddonService::config('wxchat');
|
||||
$data = $defaults;
|
||||
foreach ($defaults as $k => $dv) {
|
||||
if (!array_key_exists($k, $saved)) {
|
||||
continue;
|
||||
}
|
||||
$val = $saved[$k];
|
||||
if (is_bool($dv)) {
|
||||
$data[$k] = in_array($val, [true, 'true', '1', 1], true);
|
||||
} elseif (is_int($dv)) {
|
||||
$data[$k] = (int) $val;
|
||||
} elseif (is_array($dv)) {
|
||||
$data[$k] = is_array($val) ? $val : $dv;
|
||||
} else {
|
||||
$data[$k] = $val;
|
||||
}
|
||||
}
|
||||
$this->success($data);
|
||||
}
|
||||
|
||||
|
||||
public function launchData()
|
||||
{
|
||||
$data = [
|
||||
// 启动页广告
|
||||
'splashAds' => [[
|
||||
'id' => 1,
|
||||
'image' => 'https://dev.xixingwl.cn:2346/splash.jpg',
|
||||
'title' => '启动页广告',
|
||||
'link' => 'https://dev.xixingwl.cn:2346',
|
||||
'type' => 'url',
|
||||
'order' => 1,
|
||||
], [
|
||||
'id' => 2,
|
||||
'image' => 'https://dev.xixingwl.cn:2346/splash.jpg',
|
||||
'title' => '启动页广告2',
|
||||
'link' => 'https://dev.xixingwl.cn:2346',
|
||||
'type' => 'url',
|
||||
'order' => 2,
|
||||
], [
|
||||
'id' => 3,
|
||||
'image' => 'https://dev.xixingwl.cn:2346/splash.jpg',
|
||||
'title' => '启动页广告3',
|
||||
'link' => 'https://dev.xixingwl.cn:2346',
|
||||
'type' => 'url',
|
||||
'order' => 3,
|
||||
]],
|
||||
// 首页轮播图
|
||||
'banners' => [[
|
||||
'id' => 1,
|
||||
'image' => 'https://dev.xixingwl.cn:2346/banner.jpg',
|
||||
'title' => '首页轮播图1',
|
||||
'link' => 'https://dev.xixingwl.cn:2346',
|
||||
'type' => 'url',
|
||||
'order' => 1,
|
||||
], [
|
||||
'id' => 2,
|
||||
'image' => 'https://dev.xixingwl.cn:2346/banner.jpg',
|
||||
'title' => '首页轮播图2',
|
||||
'link' => 'https://dev.xixingwl.cn:2346',
|
||||
'type' => 'url',
|
||||
'order' => 2,
|
||||
], [
|
||||
'id' => 3,
|
||||
'image' => 'https://dev.xixingwl.cn:2346/banner.jpg',
|
||||
'title' => '首页轮播图3',
|
||||
'link' => 'https://dev.xixingwl.cn:2346',
|
||||
'type' => 'url',
|
||||
'order' => 3,
|
||||
]],
|
||||
// 系统公告
|
||||
// 'announcements' => IAnnouncement[]
|
||||
// 运营活动
|
||||
'activities' => [[
|
||||
'id' => 1,
|
||||
'title' => '运营活动1',
|
||||
'description' => '运营活动1描述',
|
||||
'icon' => 'https://dev.xixingwl.cn:2346/activity1.png',
|
||||
'badge' => 'new',
|
||||
'link' => 'https://dev.xixingwl.cn:2346/activity1',
|
||||
'startTime' => 1609459200,
|
||||
'endTime' => 1609545600,
|
||||
'status' => 'ongoing',
|
||||
], [
|
||||
'id' => 2,
|
||||
'title' => '运营活动2',
|
||||
'description' => '运营活动2描述',
|
||||
'icon' => 'https://dev.xixingwl.cn:2346/activity2.png',
|
||||
'badge' => 'hot',
|
||||
'link' => 'https://dev.xixingwl.cn:2346/activity2',
|
||||
'startTime' => 1609459200,
|
||||
'endTime' => 1609545600,
|
||||
'status' => 'upcoming',
|
||||
], [
|
||||
'id' => 3,
|
||||
'title' => '运营活动3',
|
||||
'description' => '运营活动3描述',
|
||||
'icon' => 'https://dev.xixingwl.cn:2346/activity3.png',
|
||||
'badge' => '',
|
||||
'link' => 'https://dev.xixingwl.cn:2346/activity3',
|
||||
'startTime' => 1609459200,
|
||||
'endTime' => 1609545600,
|
||||
'status' => 'ended',
|
||||
]],
|
||||
// 快捷入口
|
||||
'quickActions' => [[
|
||||
'id' => 1,
|
||||
'name' => '快捷入口1',
|
||||
'icon' => 'https://dev.xixingwl.cn:2346/quick_action1.png',
|
||||
'color' => '#ff0000',
|
||||
'route' => '/quick_action1',
|
||||
'badge' => 5,
|
||||
'visible' => true,
|
||||
], [
|
||||
'id' => 2,
|
||||
'name' => '快捷入口2',
|
||||
'icon' => 'https://dev.xixingwl.cn:2346/quick_action2.png',
|
||||
'color' => '#00ff00',
|
||||
'route' => '/quick_action2',
|
||||
'badge' => 10,
|
||||
'visible' => true,
|
||||
], [
|
||||
'id' => 3,
|
||||
'name' => '快捷入口3',
|
||||
'icon' => 'https://dev.xixingwl.cn:2346/quick_action3.png',
|
||||
'color' => '#0000ff',
|
||||
'route' => '/quick_action3',
|
||||
'badge' => 15,
|
||||
'visible' => true,
|
||||
]],
|
||||
// 新手引导
|
||||
'newbieGuide' => [
|
||||
'enabled' => true,
|
||||
'steps' => [
|
||||
[
|
||||
'id' => 1,
|
||||
'title' => '新手引导步骤1',
|
||||
'content' => '这是新手引导步骤1的内容',
|
||||
'image' => 'https://dev.xixingwl.cn:2346/newbie_guide1.png',
|
||||
'action' => 'next',
|
||||
], [
|
||||
'id' => 2,
|
||||
'title' => '新手引导步骤2',
|
||||
'content' => '这是新手引导步骤2的内容',
|
||||
'image' => 'https://dev.xixingwl.cn:2346/newbie_guide2.png',
|
||||
'action' => 'prev',
|
||||
], [
|
||||
'id' => 3,
|
||||
'title' => '新手引导步骤3',
|
||||
'content' => '这是新手引导步骤3的内容',
|
||||
'image' => 'https://dev.xixingwl.cn:2346/newbie_guide3.png',
|
||||
'action' => 'finish',
|
||||
],
|
||||
],
|
||||
]];
|
||||
$this->success($data);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function baseData()
|
||||
{
|
||||
$this->error();
|
||||
$data = [];
|
||||
$this->success($data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
use ywxapp\controller\FrontendBase as AddonController;
|
||||
|
||||
/**
|
||||
* Indexi 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Indexi extends AddonController
|
||||
{
|
||||
|
||||
public function index()
|
||||
{
|
||||
return $this->fetch() ;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,398 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\utils\HttpClient;
|
||||
use think\facade\Db;
|
||||
use think\facade\Cache;
|
||||
use ywxapp\service\JwtService;
|
||||
|
||||
/**
|
||||
* Login 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Login extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* Summary of noNeedLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['*'];
|
||||
|
||||
/**
|
||||
* Summary of noNeedVerify
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
echo "test";
|
||||
}
|
||||
|
||||
// 短信验证码登录(验证码校验通过后,查找或自动注册用户,并签发 JWT)
|
||||
|
||||
public function smsLogin()
|
||||
{
|
||||
try {
|
||||
$mobile = $this->request->post("mobile");
|
||||
$event = $this->request->post("event", 'login');
|
||||
$captcha = $this->request->post("captcha");
|
||||
$data = ['mobile' => $mobile, 'captcha' => $captcha, 'event' => $event];
|
||||
validate(\addon\wxchat\validate\Login::class)
|
||||
->scene('sms')
|
||||
->check($data);
|
||||
$ret = app('sms')->check($mobile, $captcha, $event);
|
||||
if (! $ret) {
|
||||
$this->error("验证码不正确!");
|
||||
}
|
||||
$user = Db::name('member')->where('mobile', $mobile)->where('status', '>=', 0)->find();
|
||||
if (empty($user)) {
|
||||
$uid = Db::name('member')->insertGetId([
|
||||
'account' => $mobile,
|
||||
'mobile' => $mobile,
|
||||
'nickname' => $this->generateRandomNickname(),
|
||||
'password' => '',
|
||||
'status' => 1,
|
||||
'create_at' => time(),
|
||||
'create_ip' => Request::ip(),
|
||||
'update_at' => time(),
|
||||
'update_ip' => Request::ip(),
|
||||
]);
|
||||
$user = Db::name('member')->find($uid);
|
||||
} else {
|
||||
Db::name('member')->where('uid', $user['uid'])->update([
|
||||
'update_at' => time(),
|
||||
'update_ip' => Request::ip(),
|
||||
]);
|
||||
}
|
||||
$this->issueToken((int) $user['uid']);
|
||||
$this->success([
|
||||
'user_info' => $this->formatUserInfo($user),
|
||||
'is_registered' => true,
|
||||
], '登录成功');
|
||||
} catch (\think\exception\ValidateException $e) {
|
||||
$this->error($e->getMessage(), 400);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('系统错误:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 一键登录(本机号码):用预下发的一次性 pre_token 换取登录态
|
||||
// 流程:客户端先调用 getOneClickPreToken(phone) 拿到 pre_token(模拟运营商 SDK 下发的本机号码凭证),
|
||||
// 再携带 pre_token 调用本接口,服务端校验后签发 JWT。
|
||||
|
||||
public function oneClickLogin()
|
||||
{
|
||||
try {
|
||||
$preToken = $this->request->post('pre_token');
|
||||
if (empty($preToken)) {
|
||||
return json(['code' => 400, 'msg' => '缺少一键登录凭证', 'data' => null]);
|
||||
}
|
||||
$phone = Cache::get('wxchat_oneclick_' . $preToken);
|
||||
if (empty($phone)) {
|
||||
return json(['code' => 400, 'msg' => '一键登录凭证已失效,请重试', 'data' => null]);
|
||||
}
|
||||
Cache::delete('wxchat_oneclick_' . $preToken);
|
||||
|
||||
$user = Db::name('member')->where('mobile', $phone)->where('status', '>=', 0)->find();
|
||||
if (empty($user)) {
|
||||
$uid = Db::name('member')->insertGetId([
|
||||
'account' => $phone,
|
||||
'mobile' => $phone,
|
||||
'nickname' => $this->generateRandomNickname(),
|
||||
'password' => '',
|
||||
'status' => 1,
|
||||
'create_at' => time(),
|
||||
'create_ip' => Request::ip(),
|
||||
'update_at' => time(),
|
||||
'update_ip' => Request::ip(),
|
||||
]);
|
||||
$user = Db::name('member')->find($uid);
|
||||
} else {
|
||||
Db::name('member')->where('uid', $user['uid'])->update([
|
||||
'update_at' => time(),
|
||||
'update_ip' => Request::ip(),
|
||||
]);
|
||||
}
|
||||
$this->issueToken((int) $user['uid']);
|
||||
$this->success([
|
||||
'user_info' => $this->formatUserInfo($user),
|
||||
'is_registered' => true,
|
||||
], '登录成功');
|
||||
} catch (\think\exception\ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getMessage(), 'data' => null]);
|
||||
} catch (\Exception $e) {
|
||||
return json(['code' => 500, 'msg' => '系统错误:' . $e->getMessage(), 'data' => null]);
|
||||
}
|
||||
}
|
||||
|
||||
// 完善个人信息(注册)。仅写入 user 表真实存在的字段。
|
||||
|
||||
public function completeUserInfo()
|
||||
{
|
||||
try {
|
||||
$data = Request::only([
|
||||
'temp_token', 'nickname', 'avatar',
|
||||
], 'post');
|
||||
|
||||
// 验证临时token
|
||||
$tempData = Cache::get('temp_login_' . ($data['temp_token'] ?? ''));
|
||||
if (! $tempData || ($tempData['expire_time'] ?? 0) < time()) {
|
||||
return json(['code' => 401, 'msg' => '临时凭证已过期,请重新登录', 'data' => null]);
|
||||
}
|
||||
if (empty($tempData['verified'])) {
|
||||
return json(['code' => 403, 'msg' => '手机号未验证', 'data' => null]);
|
||||
}
|
||||
|
||||
$phone = $tempData['phone'];
|
||||
|
||||
// 检查手机号是否已被注册
|
||||
$exists = Db::name('member')
|
||||
->where('mobile', $phone)
|
||||
->where('status', 1)
|
||||
->find();
|
||||
if ($exists) {
|
||||
return json(['code' => 400, 'msg' => '该手机号已注册', 'data' => null]);
|
||||
}
|
||||
|
||||
// 处理头像上传
|
||||
$avatarPath = '';
|
||||
if (! empty($data['avatar'])) {
|
||||
$avatarPath = $this->handleAvatarUpload($data['avatar'], $phone);
|
||||
}
|
||||
|
||||
$nickname = ! empty($data['nickname']) ? $data['nickname'] : $this->generateRandomNickname();
|
||||
|
||||
$userData = [
|
||||
'account' => $phone,
|
||||
'mobile' => $phone,
|
||||
'nickname' => $nickname,
|
||||
'avatar' => $avatarPath,
|
||||
'password' => '',
|
||||
'status' => 1,
|
||||
'create_at' => time(),
|
||||
'create_ip' => Request::ip(),
|
||||
'update_at' => time(),
|
||||
'update_ip' => Request::ip(),
|
||||
];
|
||||
|
||||
$userId = Db::name('member')->insertGetId($userData);
|
||||
if (! $userId) {
|
||||
throw new \Exception('用户注册失败');
|
||||
}
|
||||
|
||||
Cache::delete('temp_login_' . ($data['temp_token'] ?? ''));
|
||||
$this->issueToken($userId);
|
||||
$this->logUserRegister($userId, $userData);
|
||||
|
||||
$user = Db::name('member')->find($userId);
|
||||
$this->success([
|
||||
'user_info' => $this->formatUserInfo($user),
|
||||
'is_registered' => true,
|
||||
], '注册成功');
|
||||
|
||||
} catch (\think\exception\ValidateException $e) {
|
||||
return json(['code' => 400, 'msg' => $e->getMessage(), 'data' => null]);
|
||||
} catch (\Exception $e) {
|
||||
return json(['code' => 500, 'msg' => '系统错误:' . $e->getMessage(), 'data' => null]);
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新 Access Token(无感续期):校验 refresh_token,重签 access 并轮转 refresh
|
||||
|
||||
public function refresh()
|
||||
{
|
||||
try {
|
||||
$refreshToken = $this->request->post('refresh_token');
|
||||
if (empty($refreshToken)) {
|
||||
$this->error('缺少刷新令牌', 400);
|
||||
}
|
||||
$jwt = JwtService::instance();
|
||||
$token = $jwt->parseRefreshToken($refreshToken);
|
||||
$claims = $token->claims()->all();
|
||||
$uid = (int) ($claims['uid'] ?? 0);
|
||||
if ($uid <= 0) {
|
||||
$this->error('刷新令牌无效', 400);
|
||||
}
|
||||
// 重新签发 access token,并轮转 refresh token
|
||||
$jwt->createAccessToken(['uid' => $uid]);
|
||||
$jwt->createRefreshToken(['uid' => $uid]);
|
||||
$this->success([], '刷新成功');
|
||||
} catch (\Exception $e) {
|
||||
$this->error('刷新令牌已失效,请重新登录', 401);
|
||||
}
|
||||
}
|
||||
|
||||
// 下发一键登录预凭证(模拟运营商 SDK 下发的本机号码凭证)
|
||||
|
||||
public function getOneClickPreToken()
|
||||
{
|
||||
try {
|
||||
$phone = $this->request->post('phone');
|
||||
if (! preg_match('/^1[3-9]\d{9}$/', (string) $phone)) {
|
||||
$this->error('手机号格式错误', 400);
|
||||
}
|
||||
$preToken = md5(uniqid((string) microtime(true), true) . $phone . time());
|
||||
Cache::set('wxchat_oneclick_' . $preToken, $phone, 300);
|
||||
$this->success(['pre_token' => $preToken]);
|
||||
} catch (\Exception $e) {
|
||||
$this->error('系统错误:' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
// 发送短信验证码
|
||||
|
||||
public function sendSmsCode()
|
||||
{
|
||||
try {
|
||||
$phone = Request::param('phone');
|
||||
// 验证手机号格式
|
||||
if (! preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return json(['code' => 400, 'msg' => '手机号格式错误', 'data' => null]);
|
||||
}
|
||||
// 检查发送频率
|
||||
$cacheKey = 'sms_limit_' . $phone;
|
||||
$lastSendTime = Cache::get($cacheKey);
|
||||
if ($lastSendTime && time() - $lastSendTime < 60) {
|
||||
return json(['code' => 400, 'msg' => '请稍后再试', 'data' => null]);
|
||||
}
|
||||
// 生成6位随机验证码
|
||||
$code = str_pad(random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
|
||||
// 调用短信服务
|
||||
$smsService = new SmsService();
|
||||
$sendResult = $smsService->sendLoginCode($phone, $code);
|
||||
|
||||
if ($sendResult) {
|
||||
// 记录发送时间
|
||||
Cache::set($cacheKey, time(), 60);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '验证码发送成功',
|
||||
'data' => [
|
||||
'expire_time' => 300, // 5分钟有效期
|
||||
],
|
||||
]);
|
||||
} else {
|
||||
return json(['code' => 500, 'msg' => '验证码发送失败', 'data' => null]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return json(['code' => 500, 'msg' => '系统错误:' . $e->getMessage(), 'data' => null]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 签发 JWT(access + refresh),借助 JwtService 内部写入 app()->result,
|
||||
* 最终由 $this->success() 在响应顶层输出 access_token/refresh_token/...
|
||||
*/
|
||||
private function issueToken(int $uid) : void
|
||||
{
|
||||
JwtService::instance()->createToken(['uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一输出用户信息(对齐客户端 IUserInfo 字段)
|
||||
*/
|
||||
private function formatUserInfo(array $user) : array
|
||||
{
|
||||
return [
|
||||
'uid' => (int) ($user['uid'] ?? 0),
|
||||
'account' => $user['account'] ?? '',
|
||||
'nickname' => $user['nickname'] ?? '',
|
||||
'avatar' => $user['avatar'] ?? '',
|
||||
'mobile' => $user['mobile'] ?? '',
|
||||
'status' => (int) ($user['status'] ?? 1),
|
||||
'create_time' => $user['create_at'] ?? 0,
|
||||
];
|
||||
}
|
||||
|
||||
// 处理头像上传
|
||||
|
||||
private function handleAvatarUpload($base64Image, $phone)
|
||||
{
|
||||
try {
|
||||
if (strpos($base64Image, 'data:image') === 0) {
|
||||
// Base64图片
|
||||
$imageData = base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $base64Image));
|
||||
|
||||
// 生成文件名
|
||||
$fileName = 'avatar_' . $phone . '_' . time() . '.jpg';
|
||||
$filePath = 'uploads/avatar/' . date('Ymd') . '/' . $fileName;
|
||||
|
||||
// 保存文件
|
||||
$savePath = public_path() . $filePath;
|
||||
$dir = dirname($savePath);
|
||||
if (! is_dir($dir)) {
|
||||
mkdir($dir, 0755, true);
|
||||
}
|
||||
|
||||
file_put_contents($savePath, $imageData);
|
||||
|
||||
return $filePath;
|
||||
} else {
|
||||
// URL或其他格式
|
||||
return $base64Image;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// 头像上传失败不影响注册
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// 生成随机昵称
|
||||
|
||||
private function generateRandomNickname()
|
||||
{
|
||||
$adjectives = ['快乐的', '聪明的', '勇敢的', '温柔的', '活泼的', '安静的', '热情的', '冷静的'];
|
||||
$nouns = ['小猫', '小狗', '小鸟', '小鱼', '小虎', '小兔', '小熊', '小鹿'];
|
||||
$numbers = ['123', '456', '789', '007', '888', '999'];
|
||||
|
||||
$adj = $adjectives[array_rand($adjectives)];
|
||||
$noun = $nouns[array_rand($nouns)];
|
||||
$num = $numbers[array_rand($numbers)];
|
||||
|
||||
return $adj . $noun . $num;
|
||||
}
|
||||
|
||||
// 记录用户注册日志
|
||||
|
||||
private function logUserRegister($userId, $userData)
|
||||
{
|
||||
$logData = [
|
||||
'user_id' => $userId,
|
||||
'mobile' => $userData['mobile'] ?? '',
|
||||
'register_time' => date('Y-m-d H:i:s'),
|
||||
'register_ip' => Request::ip(),
|
||||
'invite_user_id' => $userData['invite_user_id'] ?? 0,
|
||||
'create_at' => time(),
|
||||
];
|
||||
|
||||
try {
|
||||
Db::name('user_register_log')->insert($logData);
|
||||
} catch (\Exception $e) {
|
||||
// 注册日志表可能未初始化,不影响主流程
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatLotteryPrize;
|
||||
use addon\wxchat\model\WxchatLotteryRecord;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\service\WalletService;
|
||||
|
||||
/**
|
||||
* Lottery 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Lottery extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
// 每次抽奖消耗金币
|
||||
protected $cost = 10;
|
||||
// 每日免费次数
|
||||
protected $freeDaily = 1;
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
if ($this->auth->isLogin) {
|
||||
$this->uid = $this->auth->model->uid;
|
||||
}
|
||||
}
|
||||
|
||||
private $uid = 0;
|
||||
|
||||
/**
|
||||
* 抽奖配置(奖品列表 + 我的金币 + 剩余免费次数)
|
||||
*/
|
||||
public function config()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$prizes = WxchatLotteryPrize::where('status', 1)
|
||||
->order('sort', 'asc')
|
||||
->field('id,name,type,value,probability')
|
||||
->select();
|
||||
$freeUsed = WxchatLotteryRecord::where('uid', $uid)
|
||||
->where('is_free', 1)
|
||||
->where('create_at', '>=', strtotime('today'))
|
||||
->count();
|
||||
$this->success([
|
||||
'coins' => WalletService::getCoins($uid),
|
||||
'cost' => $this->cost,
|
||||
'free_daily' => $this->freeDaily,
|
||||
'free_left' => max(0, $this->freeDaily - $freeUsed),
|
||||
'prizes' => $prizes,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 抽奖(金币或免费)
|
||||
*/
|
||||
public function draw()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
|
||||
// 是否使用免费次数
|
||||
$freeUsed = WxchatLotteryRecord::where('uid', $uid)
|
||||
->where('is_free', 1)
|
||||
->where('create_at', '>=', strtotime('today'))
|
||||
->count();
|
||||
$useFree = Request::post('use_free', 0) && $freeUsed < $this->freeDaily;
|
||||
|
||||
if (! $useFree) {
|
||||
try {
|
||||
WalletService::decCoins($uid, $this->cost, '抽奖消耗');
|
||||
} catch (\Throwable $e) {
|
||||
$this->error($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$prize = $this->pickPrize();
|
||||
$record = new WxchatLotteryRecord();
|
||||
$record->uid = $uid;
|
||||
$record->prize_id = $prize ? $prize->id : 0;
|
||||
$record->prize_name = $prize ? $prize->name : '谢谢参与';
|
||||
$record->prize_type = $prize ? $prize->type : 3;
|
||||
$record->prize_value = $prize ? $prize->value : 0;
|
||||
$record->is_free = $useFree ? 1 : 0;
|
||||
$record->save();
|
||||
|
||||
// 金币奖品发放
|
||||
if ($prize && $prize->type == 1 && $prize->value > 0) {
|
||||
WalletService::incCoins($uid, (int) $prize->value, "抽奖中奖:{$prize->name}");
|
||||
}
|
||||
|
||||
$this->success([
|
||||
'prize' => $record->prize_name,
|
||||
'prize_type' => $record->prize_type,
|
||||
'prize_value'=> $record->prize_value,
|
||||
'coins_left' => WalletService::getCoins($uid),
|
||||
], '抽奖完成');
|
||||
}
|
||||
|
||||
/**
|
||||
* 按概率加权抽取奖品(无命中返回 null)
|
||||
*/
|
||||
private function pickPrize()
|
||||
{
|
||||
$prizes = WxchatLotteryPrize::where('status', 1)
|
||||
->where('stock', '<>', 0)
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
if ($prizes->isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
$rand = mt_rand(1, 10000) / 10000; // 0-1
|
||||
$acc = 0.0;
|
||||
foreach ($prizes as $p) {
|
||||
$acc += (float) $p->probability;
|
||||
if ($rand <= $acc) {
|
||||
// 扣库存
|
||||
if ($p->stock > 0) {
|
||||
Db::name('wxchat_lottery_prizes')->where('id', $p->id)->dec('stock')->update(['update_at' => time()]);
|
||||
}
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatMessage;
|
||||
use addon\wxchat\model\WxchatUnreadCount;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\model\MemberUser as UserModel;
|
||||
use ywxapp\model\MemberProfile as ProfileModel;
|
||||
|
||||
/**
|
||||
* Message 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Message extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
/**
|
||||
* 发送消息(HTTP 落库,WebSocket 实时推送由 worker 处理)
|
||||
*/
|
||||
public function send()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$receiverId = (int) Request::post('receiver_id', 0);
|
||||
$receiverType = (int) Request::post('receiver_type', 0); // 0-单聊 1-群聊
|
||||
$content = trim((string) Request::post('content', ''));
|
||||
$contentType = (int) Request::post('content_type', 0);
|
||||
|
||||
if (! $receiverId || $receiverId == $uid) {
|
||||
$this->error('接收者无效');
|
||||
}
|
||||
if ($content === '' && $contentType == 0) {
|
||||
$this->error('消息内容不能为空');
|
||||
}
|
||||
|
||||
$msg = new WxchatMessage();
|
||||
$msg->sender_id = $uid;
|
||||
$msg->receiver_type = $receiverType;
|
||||
$msg->receiver_id = $receiverId;
|
||||
$msg->content = $content;
|
||||
$msg->content_type = $contentType;
|
||||
$msg->save();
|
||||
|
||||
// 更新对方未读计数(单聊)
|
||||
if ($receiverType == 0) {
|
||||
WxchatUnreadCount::increment($receiverId, 0, $uid);
|
||||
}
|
||||
|
||||
$this->success($msg->toArray(), '发送成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话历史(单聊 / 群聊)
|
||||
*/
|
||||
public function history()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$contactId = (int) Request::get('contact_id', 0);
|
||||
$contactType = (int) Request::get('contact_type', 0);
|
||||
$limit = (int) Request::get('limit', 20);
|
||||
$page = (int) Request::get('page', 1);
|
||||
|
||||
if (! $contactId) {
|
||||
$this->error('会话对象无效');
|
||||
}
|
||||
|
||||
$query = WxchatMessage::where('is_recalled', 0)
|
||||
->where('receiver_type', $contactType);
|
||||
if ($contactType == 0) {
|
||||
// 单聊:取双方互发消息(修复原逻辑因外层 receiver_id 过滤而漏掉对方发来的消息)
|
||||
$query->where(function ($q) use ($uid, $contactId) {
|
||||
$q->where(function ($q) use ($uid, $contactId) {
|
||||
$q->where('sender_id', $uid)->where('receiver_id', $contactId);
|
||||
})->whereOr(function ($q) use ($uid, $contactId) {
|
||||
$q->where('sender_id', $contactId)->where('receiver_id', $uid);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
$query->where('receiver_id', $contactId);
|
||||
}
|
||||
$list = $query->order('create_at', 'desc')
|
||||
->paginate($limit, false, ['page' => $page]);
|
||||
|
||||
// 单聊:读取后清零未读
|
||||
if ($contactType == 0) {
|
||||
WxchatUnreadCount::reset($uid, 0, $contactId);
|
||||
}
|
||||
|
||||
$this->success([
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'pages' => $list->lastPage(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 未读消息列表
|
||||
*/
|
||||
public function unread()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$list = WxchatUnreadCount::where('user_id', $uid)
|
||||
->where('unread_count', '>', 0)
|
||||
->order('update_at', 'desc')
|
||||
->select();
|
||||
$this->success($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话列表(聚合单聊最新一条消息 + 未读 + 对方资料/在线状态)
|
||||
*/
|
||||
public function conversations()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
|
||||
$msgs = WxchatMessage::where('receiver_type', 0)
|
||||
->where('is_recalled', 0)
|
||||
->where(function ($q) use ($uid) {
|
||||
$q->where('sender_id', $uid)->whereOr('receiver_id', $uid);
|
||||
})
|
||||
->order('id', 'desc')
|
||||
->limit(1000)
|
||||
->select();
|
||||
|
||||
$convMap = [];
|
||||
$peerIds = [];
|
||||
foreach ($msgs as $m) {
|
||||
$sid = (int) $m->sender_id;
|
||||
$rid = (int) $m->receiver_id;
|
||||
$peer = ($sid == $uid) ? $rid : $sid;
|
||||
if (! isset($convMap[$peer])) {
|
||||
$convMap[$peer] = [
|
||||
'peer_id' => $peer,
|
||||
'last_content' => $m->content,
|
||||
'last_content_type' => (int) $m->content_type,
|
||||
'last_time' => (int) $m->create_at,
|
||||
'last_sender_id' => $sid,
|
||||
];
|
||||
$peerIds[] = $peer;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($peerIds)) {
|
||||
$this->success(['list' => [], 'total' => 0]);
|
||||
return;
|
||||
}
|
||||
|
||||
$users = UserModel::where('uid', 'in', $peerIds)->column('*', 'uid');
|
||||
$unread = WxchatUnreadCount::where('user_id', $uid)
|
||||
->where('contact_type', 0)
|
||||
->where('contact_id', 'in', $peerIds)
|
||||
->column('unread_count', 'contact_id');
|
||||
$onlineMap = MemberProfile::where('uid', 'in', $peerIds)->column('online_status', 'uid');
|
||||
|
||||
$list = [];
|
||||
foreach ($convMap as $peer => $c) {
|
||||
$u = $users[$peer] ?? [];
|
||||
$list[] = [
|
||||
'peer_id' => $peer,
|
||||
'nickname' => $u['nickname'] ?? '',
|
||||
'avatar' => $u['avatar'] ?? '',
|
||||
'online' => ((int) ($onlineMap[$peer] ?? 0)) == 1,
|
||||
'last_content' => $c['last_content'],
|
||||
'last_content_type' => $c['last_content_type'],
|
||||
'last_time' => $c['last_time'],
|
||||
'unread_count' => (int) ($unread[$peer] ?? 0),
|
||||
'is_self' => $c['last_sender_id'] == $uid,
|
||||
];
|
||||
}
|
||||
|
||||
usort($list, function ($a, $b) {
|
||||
return $b['last_time'] - $a['last_time'];
|
||||
});
|
||||
|
||||
$this->success(['list' => $list, 'total' => count($list)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回消息
|
||||
*/
|
||||
public function recall()
|
||||
{
|
||||
$userId = $this->auth->model->uid;
|
||||
$messageId = (int) Request::post('message_id', 0);
|
||||
|
||||
$message = WxchatMessage::find($messageId);
|
||||
if (! $message || $message->sender_id != $userId) {
|
||||
$this->error('无权撤回', 403);
|
||||
}
|
||||
|
||||
$message->is_recalled = 1;
|
||||
$message->recalled_at = date('Y-m-d H:i:s');
|
||||
$message->save();
|
||||
|
||||
$this->success([], '撤回成功');
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use ywxapp\controller\FrontendBase;
|
||||
|
||||
/**
|
||||
* Recommend 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Recommend extends FrontendBase
|
||||
{
|
||||
|
||||
/**
|
||||
* Summary of noNeedLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['*'];
|
||||
|
||||
/**
|
||||
* Summary of noNeedVerify
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
/**
|
||||
* 显示资源列表
|
||||
*
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
if ($this->auth->isLogin) {
|
||||
$this->success();
|
||||
}else{
|
||||
$this->error("用户未登录!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
namespace addon\wxchat\controller\api;
|
||||
// app/controller/Search.php
|
||||
|
||||
use app\model\Message;
|
||||
use think\Request;
|
||||
|
||||
use ywxapp\controller\FrontendBase as AddonController;
|
||||
|
||||
/**
|
||||
* Search 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Search extends AddonController
|
||||
{
|
||||
|
||||
/**
|
||||
* Summary of noNeedLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['*'];
|
||||
|
||||
/**
|
||||
* Summary of noNeedVerify
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$userId = $request->user->id;
|
||||
$keyword = $request->get('keyword');
|
||||
$contactId = $request->get('contact_id');
|
||||
$isGroup = $request->get('is_group', 0);
|
||||
|
||||
$query = Message::where(function($q) use ($userId, $contactId, $isGroup) {
|
||||
if ($isGroup) {
|
||||
$q->where('receiver_type', 1)->where('receiver_id', $contactId);
|
||||
} else {
|
||||
$q->where(function($q) use ($userId, $contactId) {
|
||||
$q->where('sender_id', $contactId)->where('receiver_id', $userId);
|
||||
})->whereOr(function($q) use ($userId, $contactId) {
|
||||
$q->where('sender_id', $userId)->where('receiver_id', $contactId);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 全文搜索(MySQL 5.7+)
|
||||
if (extension_loaded('mysqlnd')) {
|
||||
$query->whereRaw("MATCH(content) AGAINST(?)", [$keyword]);
|
||||
} else {
|
||||
$query->where('content', 'like', "%{$keyword}%");
|
||||
}
|
||||
|
||||
$messages = $query->order('create_at', 'desc')->limit(20)->select();
|
||||
return json(['code' => 200, 'data' => $messages]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatTaskRecord;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\service\WalletService;
|
||||
|
||||
/**
|
||||
* Task 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Task extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{
|
||||
if ($this->auth->isLogin) {
|
||||
$this->uid = $this->auth->model->uid;
|
||||
}
|
||||
}
|
||||
|
||||
private $uid = 0;
|
||||
|
||||
/**
|
||||
* 每日任务定义(硬编码;进度基于真实统计,领取记录防重复)
|
||||
* status: 0 进行中 / 1 可领取 / 2 今日已领取
|
||||
*/
|
||||
private function definitions() : array
|
||||
{
|
||||
return [
|
||||
['id' => 1, 'key' => 'login', 'title' => '每日登录', 'desc' => '每天登录 App 领取奖励', 'reward' => 5, 'target' => 1, 'icon' => 'calendar'],
|
||||
['id' => 2, 'key' => 'publish', 'title' => '发布动态', 'desc' => '今日发布 1 条动态', 'reward' => 10, 'target' => 1, 'icon' => 'camera'],
|
||||
['id' => 3, 'key' => 'like', 'title' => '点赞互动', 'desc' => '今日点赞 3 条动态', 'reward' => 8, 'target' => 3, 'icon' => 'heart'],
|
||||
['id' => 4, 'key' => 'share', 'title' => '分享动态', 'desc' => '今日分享 1 条动态', 'reward' => 6, 'target' => 1, 'icon' => 'share'],
|
||||
['id' => 5, 'key' => 'profile', 'title' => '完善资料', 'desc' => '完善个人交友资料', 'reward' => 20, 'target' => 1, 'icon' => 'person'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务列表(含真实进度 + 领取状态)
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$today = strtotime('today');
|
||||
$tasks = $this->definitions();
|
||||
$result = [];
|
||||
foreach ($tasks as $t) {
|
||||
$current = $this->calcProgress($t['key'], $uid);
|
||||
$done = $current >= $t['target'];
|
||||
$received = WxchatTaskRecord::where('uid', $uid)
|
||||
->where('task_id', $t['id'])
|
||||
->where('status', 2)
|
||||
->where('update_at', '>=', $today)
|
||||
->count();
|
||||
$status = $received > 0 ? 2 : ($done ? 1 : 0);
|
||||
$result[] = [
|
||||
'id' => $t['id'],
|
||||
'title' => $t['title'],
|
||||
'desc' => $t['desc'],
|
||||
'icon' => $t['icon'],
|
||||
'reward' => $t['reward'],
|
||||
'target' => $t['target'],
|
||||
'current' => min($current, $t['target']),
|
||||
'status' => $status,
|
||||
];
|
||||
}
|
||||
$this->success([
|
||||
'list' => $result,
|
||||
'coins' => WalletService::getCoins($uid),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取任务奖励(金币发放,每日每任务一次)
|
||||
*/
|
||||
public function receive()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$taskId = (int) Request::post('task_id');
|
||||
$task = null;
|
||||
foreach ($this->definitions() as $t) {
|
||||
if ($t['id'] == $taskId) {
|
||||
$task = $t;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (! $task) {
|
||||
$this->error('任务不存在');
|
||||
}
|
||||
|
||||
// 进度校验
|
||||
$current = $this->calcProgress($task['key'], $uid);
|
||||
if ($current < $task['target']) {
|
||||
$this->error('任务未完成');
|
||||
}
|
||||
|
||||
// 今日是否已领取
|
||||
$today = strtotime('today');
|
||||
$got = WxchatTaskRecord::where('uid', $uid)
|
||||
->where('task_id', $taskId)
|
||||
->where('status', 2)
|
||||
->where('update_at', '>=', $today)
|
||||
->count();
|
||||
if ($got > 0) {
|
||||
$this->error('今日已领取');
|
||||
}
|
||||
|
||||
// 发放金币
|
||||
WalletService::incCoins($uid, (int) $task['reward'], "任务奖励:{$task['title']}");
|
||||
|
||||
// 写领取记录;利用 UNIQUE(uid,task_id) 实现每日重置(次日触发冲突后更新)
|
||||
try {
|
||||
(new WxchatTaskRecord())->save([
|
||||
'uid' => $uid,
|
||||
'task_id' => $taskId,
|
||||
'progress' => $task['target'],
|
||||
'status' => 2,
|
||||
'reward_coins' => $task['reward'],
|
||||
'create_at' => time(),
|
||||
'update_at' => time(),
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
WxchatTaskRecord::where('uid', $uid)
|
||||
->where('task_id', $taskId)
|
||||
->update([
|
||||
'progress' => $task['target'],
|
||||
'status' => 2,
|
||||
'reward_coins' => $task['reward'],
|
||||
'update_at' => time(),
|
||||
]);
|
||||
}
|
||||
|
||||
$this->success([
|
||||
'coins_left' => WalletService::getCoins($uid),
|
||||
'reward' => $task['reward'],
|
||||
], '领取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算任务当前进度(基于真实业务统计)
|
||||
*/
|
||||
private function calcProgress(string $key, int $uid) : int
|
||||
{
|
||||
$today = strtotime('today');
|
||||
try {
|
||||
switch ($key) {
|
||||
case 'login':
|
||||
return 1;
|
||||
case 'publish':
|
||||
return (int) Db::name('wxchat_moments')
|
||||
->where('user_id', $uid)
|
||||
->where('create_at', '>=', $today)
|
||||
->count();
|
||||
case 'like':
|
||||
return (int) Db::name('wxchat_moment_likes')
|
||||
->where('user_id', $uid)
|
||||
->where('create_at', '>=', $today)
|
||||
->count();
|
||||
case 'share':
|
||||
return (int) Db::name('wxchat_moment_shares')
|
||||
->where('user_id', $uid)
|
||||
->where('create_at', '>=', $today)
|
||||
->count();
|
||||
case 'profile':
|
||||
$user = Db::name('member')->where('uid', $uid)->field('nickname,avatar')->find();
|
||||
$profile = Db::name('member_profile')->where('uid', $uid)
|
||||
->field('bio,birthday,gender')->find();
|
||||
$score = 0;
|
||||
if (! empty($user['nickname'])) {
|
||||
$score++;
|
||||
}
|
||||
if (! empty($user['avatar'])) {
|
||||
$score++;
|
||||
}
|
||||
if (! empty($profile['bio'])) {
|
||||
$score++;
|
||||
}
|
||||
if (! empty($profile['birthday'])) {
|
||||
$score++;
|
||||
}
|
||||
if (! empty($profile['gender'])) {
|
||||
$score++;
|
||||
}
|
||||
return $score >= 4 ? 1 : 0;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\worker\Header;
|
||||
use addon\wxchat\worker\Message;
|
||||
use addon\wxchat\worker\Payload;
|
||||
use think\Request;
|
||||
use ywxapp\controller\FrontendBase as AddonController;
|
||||
|
||||
/**
|
||||
* Test 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Test extends AddonController
|
||||
{
|
||||
/**
|
||||
* 显示资源列表
|
||||
*
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
// ===== 序列化:对象 → JSON =====
|
||||
$message = new Message() /* 通过构造函数或 fromArray 创建 */;
|
||||
|
||||
$message->header->to = ('22');
|
||||
dump($message);
|
||||
$jsonStr = json_encode($message, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
|
||||
// 或直接:$array = $message->toArray();
|
||||
dump($jsonStr);
|
||||
|
||||
dump('===== 反序列化:JSON → 对象 ===== ');
|
||||
// ===== 反序列化:JSON → 对象 =====
|
||||
$message = Message::fromJson($jsonStr); // 推荐:带异常处理
|
||||
// 或:$message = Message::fromArray(json_decode($jsonInput, true));
|
||||
dump($message);
|
||||
|
||||
|
||||
dump('===== 反序列化:设置 → 对象 ===== ');
|
||||
// ===== 反序列化:设置 → 对象 =====
|
||||
$message->payload = new Payload();
|
||||
$message->payload->name = "傻子";
|
||||
$message->header->to = ('11');
|
||||
dump($message);
|
||||
dump(json_encode($message));
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示创建资源表单页.
|
||||
*
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存新建的资源
|
||||
*
|
||||
* @param \think\Request $request
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function save(Request $request)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示指定的资源
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function read($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示编辑资源表单页.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function edit($id = null)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存更新的资源
|
||||
*
|
||||
* @param \think\Request $request
|
||||
* @param int $id
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
//
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除指定资源
|
||||
*
|
||||
* @param int $id
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function delete($id)
|
||||
{
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
// app/controller/Unread.php
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use think\Request;
|
||||
use think\facade\Cache;
|
||||
use GatewayClient\Gateway;
|
||||
|
||||
use ywxapp\controller\FrontendBase as AddonController;
|
||||
|
||||
/**
|
||||
* Unread 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Unread extends AddonController
|
||||
{
|
||||
|
||||
/**
|
||||
* Summary of noNeedLogin
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedLogin = ['*'];
|
||||
|
||||
/**
|
||||
* Summary of noNeedVerify
|
||||
* @var array
|
||||
*/
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
/**
|
||||
* 控制器初始化 initialize
|
||||
* @return void
|
||||
*/
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
// 获取未读计数
|
||||
|
||||
public function count(Request $request)
|
||||
{
|
||||
$userId = $request->user->id;
|
||||
|
||||
// 从Redis读取
|
||||
$redis = new \Redis();
|
||||
$redis->connect('127.0.0.1', 6379);
|
||||
$unreadData = $redis->hGetAll("unread:user:{$userId}");
|
||||
|
||||
// 格式化数据
|
||||
$result = [];
|
||||
foreach ($unreadData as $key => $count) {
|
||||
list($type, $id) = explode(':', $key);
|
||||
$result[] = [
|
||||
'contact_type' => $type,
|
||||
'contact_id' => $id,
|
||||
'unread_count' => $count
|
||||
];
|
||||
}
|
||||
|
||||
return json(['code' => 200, 'data' => $result]);
|
||||
}
|
||||
|
||||
// 标记已读
|
||||
|
||||
public function read(Request $request)
|
||||
{
|
||||
$userId = $request->user->id;
|
||||
$contactType = $request->post('contact_type');
|
||||
$contactId = $request->post('contact_id');
|
||||
|
||||
// 通知GatewayWorker
|
||||
Gateway::$registerAddress = '127.0.0.1:1238';
|
||||
Gateway::sendToUid($userId, json_encode([
|
||||
'type' => 'read',
|
||||
'contact_type' => $contactType,
|
||||
'contact_id' => $contactId
|
||||
]));
|
||||
|
||||
return json(['code' => 200, 'msg' => '已标记已读']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,611 @@
|
||||
<?php
|
||||
// +----------------------------------------------------------------------
|
||||
// | YwxApp [ WE CAN DO IT JUST THINK ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2026-2036 http://ywxapp.cn All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: ywxapp<admin@ywxapp.cn>
|
||||
// +----------------------------------------------------------------------
|
||||
declare (strict_types = 1);
|
||||
|
||||
namespace addon\wxchat\controller\api;
|
||||
|
||||
use addon\wxchat\model\WxchatFollow;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\model\MemberUser as UserModel;
|
||||
use ywxapp\model\MemberProfile as ProfileModel;
|
||||
|
||||
/**
|
||||
* Member 类
|
||||
*
|
||||
* @author ywxapp <admin@ywxapp.cn>
|
||||
*/
|
||||
class Member extends FrontendBase
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize()
|
||||
{}
|
||||
|
||||
/**
|
||||
* 当前用户交友档案
|
||||
*/
|
||||
public function profile()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$profile = ProfileModel::where('uid', $uid)->find();
|
||||
if (! $profile) {
|
||||
$this->error('资料不存在');
|
||||
}
|
||||
$this->success($profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查看他人公开资料(资料卡页用)
|
||||
* 返回:档案字段 + nickname/avatar/account + 计算后的 age + 是否关注 + 动态数 + 相册
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
$targetUid = (int) Request::get('uid');
|
||||
if ($targetUid <= 0) {
|
||||
$this->error('参数错误');
|
||||
}
|
||||
$profile = ProfileModel::where('uid', $targetUid)->find();
|
||||
if (! $profile) {
|
||||
$this->error('用户不存在');
|
||||
}
|
||||
$user = Db::name('member')->where('uid', $targetUid)
|
||||
->field('uid,nickname,avatar,account,status')->find();
|
||||
|
||||
$pc = ! empty($profile->privacy_config) ? json_decode($profile->privacy_config, true) : [];
|
||||
$pc = is_array($pc) ? $pc : $this->defaultPrivacy();
|
||||
|
||||
$data = $profile->toArray();
|
||||
$data['nickname'] = $user['nickname'] ?? '';
|
||||
$data['avatar'] = $user['avatar'] ?? '';
|
||||
$data['account'] = $user['account'] ?? '';
|
||||
$data['age'] = $profile->birthday
|
||||
? (int) floor((time() - strtotime($profile->birthday)) / 31557600) : 0;
|
||||
if (! empty($pc['hide_age'])) {
|
||||
unset($data['age']);
|
||||
}
|
||||
// 关注状态(基于 wxchat_follows)
|
||||
$meUid = $this->auth->model->uid;
|
||||
$followed = WxchatFollow::where('uid', $meUid)
|
||||
->where('fid', $targetUid)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
$data['is_followed'] = $followed > 0 ? 1 : 0;
|
||||
$data['is_online'] = ($profile->online_status == 1)
|
||||
|| (! empty($profile->last_active_at) && time() - (int) $profile->last_active_at < 300);
|
||||
// 动态数(表可能未初始化,容错)
|
||||
try {
|
||||
$momentCount = Db::name('wxchat_moments')->where('user_id', $targetUid)->count();
|
||||
} catch (\Throwable $e) {
|
||||
$momentCount = 0;
|
||||
}
|
||||
$data['moment_count'] = $momentCount;
|
||||
// 照片墙:优先用 user.avatar(profile 表无头像列)
|
||||
$data['photos'] = ! empty($user['avatar']) ? [$user['avatar']] : [];
|
||||
|
||||
$this->success($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新交友档案(定位 / 同城 / 择偶条件)
|
||||
*/
|
||||
public function updateProfile()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$allow = [
|
||||
'gender', 'birthday', 'latitude', 'longitude', 'geo_hash',
|
||||
'resideprovince', 'residecity', 'residedist',
|
||||
'height', 'weight', 'lookingfor', 'affectivestatus',
|
||||
'education', 'occupation', 'revenue', 'bloodtype',
|
||||
'signature', 'bio', 'address',
|
||||
];
|
||||
$data = Request::post();
|
||||
$update = array_intersect_key($data, array_flip($allow));
|
||||
|
||||
if (! empty($update['birthday'])) {
|
||||
$update['constellation'] = $this->calcConstellation($update['birthday']);
|
||||
$update['zodiac'] = $this->calcZodiac($update['birthday']);
|
||||
}
|
||||
if (! empty($update['latitude']) && ! empty($update['longitude'])) {
|
||||
$update['geo_hash'] = $this->geoHash((float) $update['latitude'], (float) $update['longitude']);
|
||||
}
|
||||
|
||||
ProfileModel::where('uid', $uid)->update($update);
|
||||
$this->success([], '更新成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 同城智能匹配(基于经纬度距离排序)
|
||||
*/
|
||||
public function nearby()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$me = ProfileModel::where('uid', $uid)->field('latitude,longitude,residecity')->find();
|
||||
$lat = (float) Request::get('lat', $me['latitude'] ?? 0);
|
||||
$lng = (float) Request::get('lng', $me['longitude'] ?? 0);
|
||||
$radius = (float) Request::get('radius', 10);
|
||||
$gender = (int) Request::get('gender', 0);
|
||||
$ageMin = (int) Request::get('age_min', 0);
|
||||
$ageMax = (int) Request::get('age_max', 0);
|
||||
$limit = (int) Request::get('limit', 20);
|
||||
$page = (int) Request::get('page', 1);
|
||||
|
||||
if (! $lat || ! $lng) {
|
||||
$this->error('请先更新你的定位');
|
||||
}
|
||||
|
||||
$dist = "(6371 * acos(cos(radians({$lat})) * cos(radians(p.latitude)) "
|
||||
. "* cos(radians(p.longitude) - radians({$lng})) "
|
||||
. "+ sin(radians({$lat})) * sin(radians(p.latitude))))";
|
||||
|
||||
$query = Db::name('member_profile')->alias('p')
|
||||
->join('user u', 'u.uid = p.uid')
|
||||
->where('p.uid', '<>', $uid)
|
||||
->whereNotNull('p.latitude')
|
||||
->whereNotNull('p.longitude')
|
||||
->where('p.latitude', '<>', 0)
|
||||
->where('p.longitude', '<>', 0)
|
||||
->field([
|
||||
'p.uid', 'u.nickname', 'u.avatar', 'p.gender', 'p.birthday',
|
||||
'p.resideprovince', 'p.residecity', 'p.residedist',
|
||||
'p.height', 'p.weight', 'p.bio', 'p.online_status', 'p.last_active_at',
|
||||
'p.privacy_config',
|
||||
Db::raw("{$dist} AS distance"),
|
||||
]);
|
||||
|
||||
if ($gender) {
|
||||
$query->where('p.gender', $gender);
|
||||
}
|
||||
if ($ageMin || $ageMax) {
|
||||
$lo = max(1, $ageMin);
|
||||
$hi = max($lo, $ageMax ?: 100);
|
||||
$query->whereRaw('TIMESTAMPDIFF(YEAR, p.birthday, CURDATE()) BETWEEN ? AND ?', [$lo, $hi]);
|
||||
}
|
||||
$query->whereRaw("{$dist} <= ?", [$radius]);
|
||||
$query->order('distance', 'asc');
|
||||
|
||||
$list = $query->paginate($limit, false, ['page' => $page]);
|
||||
$this->success($this->formatUsers($list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地同城(按居住城市匹配,无定位也能用)
|
||||
*/
|
||||
public function local()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$me = ProfileModel::where('uid', $uid)->field('residecity,geo_hash')->find();
|
||||
$city = Request::get('city', $me['residecity'] ?? '');
|
||||
$gender = (int) Request::get('gender', 0);
|
||||
$ageMin = (int) Request::get('age_min', 0);
|
||||
$ageMax = (int) Request::get('age_max', 0);
|
||||
$limit = (int) Request::get('limit', 20);
|
||||
$page = (int) Request::get('page', 1);
|
||||
|
||||
$query = Db::name('member_profile')->alias('p')
|
||||
->join('user u', 'u.uid = p.uid')
|
||||
->where('p.uid', '<>', $uid)
|
||||
->field([
|
||||
'p.uid', 'u.nickname', 'u.avatar', 'p.gender', 'p.birthday',
|
||||
'p.resideprovince', 'p.residecity', 'p.residedist',
|
||||
'p.height', 'p.weight', 'p.bio', 'p.online_status', 'p.last_active_at',
|
||||
'p.privacy_config',
|
||||
]);
|
||||
|
||||
if ($city) {
|
||||
$query->where('p.residecity', $city);
|
||||
} elseif (! empty($me['geo_hash'])) {
|
||||
$query->where('p.geo_hash', 'like', substr($me['geo_hash'], 0, 4) . '%');
|
||||
} else {
|
||||
$this->success(['list' => [], 'total' => 0, 'pages' => 0]);
|
||||
return;
|
||||
}
|
||||
if ($gender) {
|
||||
$query->where('p.gender', $gender);
|
||||
}
|
||||
if ($ageMin || $ageMax) {
|
||||
$lo = max(1, $ageMin);
|
||||
$hi = max($lo, $ageMax ?: 100);
|
||||
$query->whereRaw('TIMESTAMPDIFF(YEAR, p.birthday, CURDATE()) BETWEEN ? AND ?', [$lo, $hi]);
|
||||
}
|
||||
$query->order('p.last_active_at', 'desc');
|
||||
|
||||
$list = $query->paginate($limit, false, ['page' => $page]);
|
||||
$this->success($this->formatUsers($list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 24 小时在线列表(实时在线活跃用户)
|
||||
*/
|
||||
public function online()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$gender = (int) Request::get('gender', 0);
|
||||
$limit = (int) Request::get('limit', 50);
|
||||
$page = (int) Request::get('page', 1);
|
||||
|
||||
$query = Db::name('member_profile')->alias('p')
|
||||
->join('user u', 'u.uid = p.uid')
|
||||
->where('p.uid', '<>', $uid)
|
||||
->where(function ($q) {
|
||||
$q->where('p.online_status', 1)
|
||||
->whereOr('p.last_active_at', '>', time() - 300);
|
||||
})
|
||||
->field([
|
||||
'p.uid', 'u.nickname', 'u.avatar', 'p.gender', 'p.birthday',
|
||||
'p.resideprovince', 'p.residecity', 'p.height', 'p.weight',
|
||||
'p.bio', 'p.online_status', 'p.last_active_at',
|
||||
'p.privacy_config',
|
||||
]);
|
||||
if ($gender) {
|
||||
$query->where('p.gender', $gender);
|
||||
}
|
||||
$query->order('p.last_active_at', 'desc');
|
||||
|
||||
$list = $query->paginate($limit, false, ['page' => $page]);
|
||||
$this->success($this->formatUsers($list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 心跳:上报在线状态
|
||||
*/
|
||||
public function heartbeat()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
ProfileModel::where('uid', $uid)->update([
|
||||
'last_active_at' => time(),
|
||||
'online_status' => 1,
|
||||
]);
|
||||
$this->success(['online' => 1], 'ok');
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一格式化用户列表(附带距离/年龄/在线)
|
||||
*/
|
||||
private function formatUsers($paginate)
|
||||
{
|
||||
$items = $paginate->items();
|
||||
foreach ($items as &$it) {
|
||||
$it['age'] = $it['birthday'] ? (int) floor((time() - strtotime($it['birthday'])) / 31557600) : 0;
|
||||
$it['is_online'] = $it['online_status'] == 1
|
||||
|| (! empty($it['last_active_at']) && time() - (int) $it['last_active_at'] < 300);
|
||||
if (isset($it['distance'])) {
|
||||
$it['distance'] = round((float) $it['distance'], 2);
|
||||
}
|
||||
// 应用对方隐私设置:隐藏其年龄/在线/距离
|
||||
$pc = ! empty($it['privacy_config']) ? json_decode($it['privacy_config'], true) : [];
|
||||
$pc = is_array($pc) ? $pc : [];
|
||||
if (! empty($pc['hide_age'])) {
|
||||
unset($it['age']);
|
||||
}
|
||||
if (! empty($pc['hide_online'])) {
|
||||
$it['is_online'] = false;
|
||||
}
|
||||
if (! empty($pc['hide_distance']) && isset($it['distance'])) {
|
||||
unset($it['distance']);
|
||||
}
|
||||
unset($it['online_status'], $it['last_active_at'], $it['birthday'], $it['privacy_config']);
|
||||
}
|
||||
return [
|
||||
'list' => $items,
|
||||
'total' => $paginate->total(),
|
||||
'pages' => $paginate->lastPage(),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算星座
|
||||
*/
|
||||
private function calcConstellation($date)
|
||||
{
|
||||
$ts = strtotime($date);
|
||||
if (! $ts) {
|
||||
return '';
|
||||
}
|
||||
$m = (int) date('m', $ts);
|
||||
$d = (int) date('d', $ts);
|
||||
$map = [
|
||||
[1, 20, '摩羯座'], [2, 19, '水瓶座'], [3, 21, '双鱼座'], [4, 20, '白羊座'],
|
||||
[5, 21, '金牛座'], [6, 22, '双子座'], [7, 23, '巨蟹座'], [8, 23, '狮子座'],
|
||||
[9, 23, '处女座'], [10, 24, '天秤座'], [11, 23, '天蝎座'], [12, 22, '射手座'], [13, 0, '摩羯座'],
|
||||
];
|
||||
foreach ($map as [$mm, $dd, $name]) {
|
||||
if ($m < $mm || ($m == $mm && $d < $dd)) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
return '摩羯座';
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算生肖
|
||||
*/
|
||||
private function calcZodiac($date)
|
||||
{
|
||||
$ts = strtotime($date);
|
||||
if (! $ts) {
|
||||
return '';
|
||||
}
|
||||
$zodiacs = ['鼠', '牛', '虎', '兔', '龙', '蛇', '马', '羊', '猴', '鸡', '狗', '猪'];
|
||||
$year = (int) date('Y', $ts);
|
||||
return $zodiacs[($year - 4) % 12];
|
||||
}
|
||||
|
||||
/**
|
||||
* 简易 geohash 编码(用于同城粗匹配前缀)
|
||||
*/
|
||||
private function geoHash($lat, $lng, $precision = 8)
|
||||
{
|
||||
$base32 = '0123456789bcdefghjkmnpqrstuvwxyz';
|
||||
$latRange = [-90.0, 90.0];
|
||||
$lngRange = [-180.0, 180.0];
|
||||
$hash = '';
|
||||
$bits = 0;
|
||||
$bit = 0;
|
||||
$even = true;
|
||||
$i = 0;
|
||||
while (strlen($hash) < $precision) {
|
||||
if ($even) {
|
||||
$mid = ($lngRange[0] + $lngRange[1]) / 2;
|
||||
if ($lng > $mid) {
|
||||
$bit = 1;
|
||||
$lngRange[0] = $mid;
|
||||
} else {
|
||||
$bit = 0;
|
||||
$lngRange[1] = $mid;
|
||||
}
|
||||
} else {
|
||||
$mid = ($latRange[0] + $latRange[1]) / 2;
|
||||
if ($lat > $mid) {
|
||||
$bit = 1;
|
||||
$latRange[0] = $mid;
|
||||
} else {
|
||||
$bit = 0;
|
||||
$latRange[1] = $mid;
|
||||
}
|
||||
}
|
||||
$even = ! $even;
|
||||
$bits = ($bits << 1) + $bit;
|
||||
if (++$i == 5) {
|
||||
$hash .= $base32[$bits];
|
||||
$bits = 0;
|
||||
$i = 0;
|
||||
}
|
||||
}
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认隐私 / 设置项
|
||||
*/
|
||||
private function defaultPrivacy(): array
|
||||
{
|
||||
return [
|
||||
'hide_age' => 0,
|
||||
'hide_online' => 0,
|
||||
'hide_distance' => 0,
|
||||
'hide_album' => 0,
|
||||
'danmaku_global' => 1, // 弹幕总开关
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取我的隐私 / 设置
|
||||
*/
|
||||
public function privacy()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$profile = ProfileModel::where('uid', $uid)->field('privacy_config')->find();
|
||||
$pc = $profile && $profile->privacy_config ? json_decode($profile->privacy_config, true) : [];
|
||||
$pc = is_array($pc) ? $pc : [];
|
||||
$this->success(array_merge($this->defaultPrivacy(), $pc));
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新隐私 / 设置(隐藏年龄/在线/距离/相册 + 弹幕总开关)
|
||||
*/
|
||||
public function updatePrivacy()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$allow = ['hide_age', 'hide_online', 'hide_distance', 'hide_album', 'danmaku_global'];
|
||||
$data = Request::post();
|
||||
$update = array_intersect_key($data, array_flip($allow));
|
||||
if (empty($update)) {
|
||||
$this->error('无有效字段');
|
||||
}
|
||||
$profile = ProfileModel::where('uid', $uid)->field('privacy_config')->find();
|
||||
$pc = $profile && $profile->privacy_config ? json_decode($profile->privacy_config, true) : [];
|
||||
$pc = is_array($pc) ? $pc : [];
|
||||
foreach ($update as $k => $v) {
|
||||
$pc[$k] = $v ? 1 : 0;
|
||||
}
|
||||
ProfileModel::where('uid', $uid)->update(['privacy_config' => json_encode($pc, JSON_UNESCAPED_UNICODE)]);
|
||||
$this->success($pc, '已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号注销(逻辑注销:匿名化资料 + 标记注销时间)
|
||||
* 注:JWT 为无状态令牌,注销后需客户端清除本地令牌;服务端标记后拒绝以该账号发起的新业务
|
||||
*/
|
||||
public function cancelAccount()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
// 匿名化资料
|
||||
ProfileModel::where('uid', $uid)->update([
|
||||
'bio' => '',
|
||||
'address' => '',
|
||||
'latitude' => null,
|
||||
'longitude' => null,
|
||||
'geo_hash' => '',
|
||||
'canceled_at' => time(),
|
||||
'online_status'=> 0,
|
||||
]);
|
||||
// 匿名化账号(昵称/头像)
|
||||
try {
|
||||
UserModel::where('uid', $uid)->update([
|
||||
'nickname' => '已注销用户',
|
||||
'avatar' => '',
|
||||
'status' => 0,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
// 个别环境 user 表无 status 字段时忽略
|
||||
}
|
||||
$this->success([], '账号已注销');
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的聚合统计(缘友/粉丝/关注/获赞/动态数)
|
||||
* 供「我的」中心页展示使用
|
||||
*/
|
||||
public function summary()
|
||||
{
|
||||
$uid = $this->auth->model->uid;
|
||||
$stat = function (callable $cb, $default = 0) {
|
||||
try {
|
||||
return $cb();
|
||||
} catch (\Throwable $e) {
|
||||
return $default;
|
||||
}
|
||||
};
|
||||
|
||||
// 关注:我关注别人(uid 为关注者)
|
||||
$following = $stat(function () use ($uid) {
|
||||
return (int) Db::name('wxchat_follow')
|
||||
->where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
});
|
||||
|
||||
// 粉丝:别人关注我(fid 为被关注者)
|
||||
$followers = $stat(function () use ($uid) {
|
||||
return (int) Db::name('wxchat_follow')
|
||||
->where('fid', $uid)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
});
|
||||
|
||||
// 缘友:互为好友数
|
||||
$friends = $stat(function () use ($uid) {
|
||||
return (int) Db::name('wxchat_friend')
|
||||
->where(function ($q) use ($uid) {
|
||||
$q->where('uid', $uid)->whereOr('fid', $uid);
|
||||
})
|
||||
->where('status', 1)
|
||||
->count();
|
||||
});
|
||||
|
||||
// 获赞:我的动态被点赞总数
|
||||
$likes = $stat(function () use ($uid) {
|
||||
return (int) Db::name('wxchat_moments')
|
||||
->where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->sum('like_count');
|
||||
});
|
||||
|
||||
// 我的动态数
|
||||
$moments = $stat(function () use ($uid) {
|
||||
return (int) Db::name('wxchat_moments')
|
||||
->where('uid', $uid)
|
||||
->where('status', 1)
|
||||
->count();
|
||||
});
|
||||
|
||||
$this->success([
|
||||
'following' => $following,
|
||||
'followers' => $followers,
|
||||
'friends' => $friends,
|
||||
'likes' => $likes,
|
||||
'moments' => $moments,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 排行榜(财富榜/魅力榜)
|
||||
* type = rich 按金币,type = charm 按粉丝数
|
||||
*/
|
||||
public function ranking()
|
||||
{
|
||||
$type = Request::get('type', 'charm');
|
||||
$limit = 50;
|
||||
|
||||
if ($type == 'rich') {
|
||||
$list = Db::name('member_wallets')->alias('w')
|
||||
->join('user u', 'u.uid = w.uid')
|
||||
->order('w.coins', 'desc')
|
||||
->limit($limit)
|
||||
->field('u.uid,u.nickname,u.avatar,w.coins as value')
|
||||
->select();
|
||||
$metric = 'coins';
|
||||
} else {
|
||||
$list = Db::name('wxchat_follow')->alias('f')
|
||||
->join('user u', 'u.uid = f.fid')
|
||||
->where('f.status', 1)
|
||||
->group('f.fid')
|
||||
->order('count(f.fid) desc')
|
||||
->limit($limit)
|
||||
->field('f.fid as uid,u.nickname,u.avatar,count(f.fid) as value')
|
||||
->select();
|
||||
$metric = 'fans';
|
||||
}
|
||||
|
||||
$rank = 1;
|
||||
$result = [];
|
||||
foreach ($list as $it) {
|
||||
$result[] = [
|
||||
'rank' => $rank++,
|
||||
'uid' => $it['uid'],
|
||||
'nickname' => $it['nickname'],
|
||||
'avatar' => $it['avatar'],
|
||||
'value' => (int) $it['value'],
|
||||
'coins' => isset($it['coins']) ? (int) $it['coins'] : 0,
|
||||
];
|
||||
}
|
||||
$this->success(['type' => $type, 'metric' => $metric, 'list' => $result]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的钱包(余额 / 金币 / 汇总 / 明细)
|
||||
*/
|
||||
public function wallet()
|
||||
{
|
||||
$uid = $this->uid;
|
||||
$row = Db::name('member_wallets')->where('uid', $uid)->find();
|
||||
if (empty($row)) {
|
||||
$row = [
|
||||
'balance' => '0.00',
|
||||
'coins' => 0,
|
||||
'total_recharge' => '0.00',
|
||||
'total_consume' => '0.00',
|
||||
];
|
||||
}
|
||||
|
||||
// 金币/余额变动明细(表不存在时返回空)
|
||||
try {
|
||||
$logs = Db::name('wxchat_coin_logs')
|
||||
->where('uid', $uid)
|
||||
->order('create_at', 'desc')
|
||||
->limit(50)
|
||||
->field('id,type,change_num,balance_after,remark,create_at')
|
||||
->select();
|
||||
} catch (\Exception $e) {
|
||||
$logs = [];
|
||||
}
|
||||
|
||||
$this->success([
|
||||
'balance' => (float) ($row['balance'] ?? 0),
|
||||
'coins' => (int) ($row['coins'] ?? 0),
|
||||
'total_recharge' => (float) ($row['total_recharge'] ?? 0),
|
||||
'total_consume' => (float) ($row['total_consume'] ?? 0),
|
||||
'logs' => $logs,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user