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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
<?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\backend;
|
||||
|
||||
use addon\wxchat\model\WxchatRealnameAuth;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use ywxapp\controller\BackendBase;
|
||||
|
||||
/**
|
||||
* 真人认证 - 后台审核(列表 / 详情 / 通过 / 驳回)
|
||||
*/
|
||||
class WxchatAuth extends BackendBase
|
||||
{
|
||||
// 插件后台按框架惯例放宽登录校验(与 mqttbroker 等插件后台一致)
|
||||
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize() {}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
return $this->lists();
|
||||
}
|
||||
return View::fetch('wxchatauth/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 认证记录列表(关联用户昵称)
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$page = (int) $this->request->param('page', 1);
|
||||
$limit = (int) $this->request->param('limit', 15);
|
||||
$kw = trim((string) $this->request->param('keyword', ''));
|
||||
$status = $this->request->param('status', '');
|
||||
|
||||
$query = WxchatRealnameAuth::alias('a')
|
||||
->join('user u', 'u.uid = a.uid', 'left')
|
||||
->field('a.*, u.nickname, u.avatar');
|
||||
if ($kw !== '') {
|
||||
$query->where('a.real_name', 'like', "%{$kw}%")
|
||||
->whereOr('u.nickname', 'like', "%{$kw}%");
|
||||
}
|
||||
if ($status !== '') {
|
||||
$query->where('a.status', (int) $status);
|
||||
}
|
||||
$total = $query->count();
|
||||
$rows = $query->order('a.create_at', 'desc')
|
||||
->page($page, $limit)->select();
|
||||
|
||||
$this->result->setCount($total)->success($rows->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 详情
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
$id = (int) $this->request->param('id', 0);
|
||||
$row = WxchatRealnameAuth::alias('a')
|
||||
->join('user u', 'u.uid = a.uid', 'left')
|
||||
->field('a.*, u.nickname, u.avatar')
|
||||
->where('a.id', $id)->find();
|
||||
if (! $row) {
|
||||
$this->result->error('记录不存在');
|
||||
}
|
||||
$this->result->success($row->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 人工复审:通过 / 驳回
|
||||
*/
|
||||
public function review()
|
||||
{
|
||||
$id = (int) $this->request->post('id', 0);
|
||||
$pass = (int) $this->request->post('pass', 0);
|
||||
$reason = trim((string) $this->request->post('reason', ''));
|
||||
|
||||
$auth = WxchatRealnameAuth::find($id);
|
||||
if (! $auth) {
|
||||
$this->result->error('记录不存在');
|
||||
}
|
||||
$auth->status = $pass ? 1 : 2;
|
||||
$auth->reject_reason = $pass ? '' : $reason;
|
||||
// 插件后台上下文 $this->auth 为会员体系,reviewer 仅作兜底记录
|
||||
try {
|
||||
$auth->reviewer_id = $this->auth->id ?? 0;
|
||||
} catch (\Throwable $e) {
|
||||
$auth->reviewer_id = 0;
|
||||
}
|
||||
$auth->review_at = time();
|
||||
$auth->save();
|
||||
|
||||
if ($pass) {
|
||||
\ywxapp\model\MemberProfile::where('uid', $auth->uid)
|
||||
->update(['is_realname' => 1]);
|
||||
}
|
||||
$this->result->success([], $pass ? '已通过' : '已驳回');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除认证记录
|
||||
*/
|
||||
public function del($ids = '')
|
||||
{
|
||||
$ids = $this->request->param('ids', '');
|
||||
if (! $ids) {
|
||||
$this->result->error('请选择要删除的数据');
|
||||
}
|
||||
$idArr = array_filter(array_map('intval', explode(',', $ids)));
|
||||
WxchatRealnameAuth::destroy($idArr);
|
||||
$this->result->success([], '删除成功');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
<?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\backend;
|
||||
|
||||
use addon\wxchat\model\WxchatRoom as WxchatRoomModel;
|
||||
use addon\wxchat\model\WxchatRoomMember;
|
||||
use addon\wxchat\model\WxchatRoomMic;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use ywxapp\controller\BackendBase;
|
||||
|
||||
/**
|
||||
* 语音 / 视频房 - 后台管理(列表 / 详情 / 编辑 / 关闭 / 删除)
|
||||
*/
|
||||
class WxchatRoom extends BackendBase
|
||||
{
|
||||
// 插件后台按框架惯例放宽登录校验(与 mqttbroker 等插件后台一致)
|
||||
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize() {}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
return $this->lists();
|
||||
}
|
||||
return View::fetch('wxchatroom/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间列表(关联房主昵称 + 实时成员数)
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$page = (int) $this->request->param('page', 1);
|
||||
$limit = (int) $this->request->param('limit', 15);
|
||||
$kw = trim((string) $this->request->param('keyword', ''));
|
||||
$type = (int) $this->request->param('type', 0);
|
||||
|
||||
$query = WxchatRoomModel::alias('r')
|
||||
->join('user u', 'u.uid = r.owner_uid', 'left')
|
||||
->field('r.*, u.nickname AS owner_name');
|
||||
if ($kw !== '') {
|
||||
$query->where('r.title', 'like', "%{$kw}%");
|
||||
}
|
||||
if ($type) {
|
||||
$query->where('r.type', $type);
|
||||
}
|
||||
$total = $query->count();
|
||||
$rows = $query->order('r.hot', 'desc')
|
||||
->order('r.create_at', 'desc')
|
||||
->page($page, $limit)->select();
|
||||
|
||||
foreach ($rows as $r) {
|
||||
$r->member_count = (int) WxchatRoomMember::where('room_id', $r->id)->count();
|
||||
}
|
||||
$this->result->setCount($total)->success($rows->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 房间详情 + 成员列表
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
$id = (int) $this->request->param('id', 0);
|
||||
$room = WxchatRoomModel::find($id);
|
||||
if (! $room) {
|
||||
$this->result->error('房间不存在');
|
||||
}
|
||||
$members = WxchatRoomMember::alias('m')
|
||||
->join('user u', 'u.uid = m.uid', 'left')
|
||||
->where('m.room_id', $id)
|
||||
->field('m.uid, m.role, m.join_at, u.nickname, u.avatar')
|
||||
->select();
|
||||
$mics = WxchatRoomMic::where('room_id', $id)->order('mic_index')->select();
|
||||
|
||||
$this->result->success([
|
||||
'room' => $room->toArray(),
|
||||
'members' => $members->toArray(),
|
||||
'mics' => $mics->toArray(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑(标题 / 公告 / 开关房)
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$id = (int) $this->request->post('id', 0);
|
||||
$data = $this->request->only(['title', 'notice', 'status', 'danmaku_on'], 'post');
|
||||
$room = WxchatRoomModel::find($id);
|
||||
if (! $room) {
|
||||
$this->result->error('房间不存在');
|
||||
}
|
||||
if (isset($data['status'])) {
|
||||
$data['status'] = (int) $data['status'];
|
||||
}
|
||||
if (isset($data['danmaku_on'])) {
|
||||
$data['danmaku_on'] = (int) $data['danmaku_on'];
|
||||
}
|
||||
$room->save($data);
|
||||
$this->result->success([], '保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭房间(置 status=0)
|
||||
*/
|
||||
public function close()
|
||||
{
|
||||
$id = (int) $this->request->post('id', 0);
|
||||
$room = WxchatRoomModel::find($id);
|
||||
if (! $room) {
|
||||
$this->result->error('房间不存在');
|
||||
}
|
||||
$room->status = 0;
|
||||
$room->save();
|
||||
$this->result->success([], '已关闭房间');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除房间(级联清理成员与麦位)
|
||||
*/
|
||||
public function del($ids = '')
|
||||
{
|
||||
$ids = $this->request->param('ids', '');
|
||||
if (! $ids) {
|
||||
$this->result->error('请选择要删除的数据');
|
||||
}
|
||||
$idArr = array_filter(array_map('intval', explode(',', $ids)));
|
||||
Db::transaction(function () use ($idArr) {
|
||||
WxchatRoomMember::whereIn('room_id', $idArr)->delete();
|
||||
WxchatRoomMic::whereIn('room_id', $idArr)->delete();
|
||||
WxchatRoomModel::destroy($idArr);
|
||||
});
|
||||
$this->result->success([], '删除成功');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
<?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\backend;
|
||||
|
||||
use addon\wxchat\model\WxchatTask as WxchatTaskModel;
|
||||
use addon\wxchat\model\WxchatTaskRecord;
|
||||
use think\facade\Db;
|
||||
use think\facade\View;
|
||||
use ywxapp\controller\BackendBase;
|
||||
|
||||
/**
|
||||
* 任务赚钱 - 后台管理(任务配置 / 参与统计 / 上线开关)
|
||||
*/
|
||||
class WxchatTask extends BackendBase
|
||||
{
|
||||
// 插件后台按框架惯例放宽登录校验(与 mqttbroker 等插件后台一致)
|
||||
|
||||
protected $noNeedVerify = ['*'];
|
||||
|
||||
|
||||
protected function initialize() {}
|
||||
|
||||
|
||||
public function index()
|
||||
{
|
||||
if ($this->request->isAjax()) {
|
||||
return $this->lists();
|
||||
}
|
||||
return View::fetch('wxchattask/index');
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务列表(含参与/领取统计)
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$page = (int) $this->request->param('page', 1);
|
||||
$limit = (int) $this->request->param('limit', 15);
|
||||
$kw = trim((string) $this->request->param('keyword', ''));
|
||||
|
||||
$query = WxchatTaskModel::order('sort', 'asc');
|
||||
if ($kw !== '') {
|
||||
$query->where('title', 'like', "%{$kw}%");
|
||||
}
|
||||
$total = $query->count();
|
||||
$rows = $query->page($page, $limit)->select();
|
||||
|
||||
foreach ($rows as $t) {
|
||||
$t->participants = (int) WxchatTaskRecord::where('task_id', $t->id)->count();
|
||||
$t->claimed = (int) WxchatTaskRecord::where('task_id', $t->id)
|
||||
->where('status', 2)->count();
|
||||
}
|
||||
$this->result->setCount($total)->success($rows->toArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存(新增/编辑)
|
||||
*/
|
||||
public function save()
|
||||
{
|
||||
$id = (int) $this->request->post('id', 0);
|
||||
$data = $this->request->only(
|
||||
['title', 'type', 'desc', 'reward_coins', 'target', 'sort', 'status'],
|
||||
'post'
|
||||
);
|
||||
$data['reward_coins'] = (int) ($data['reward_coins'] ?? 0);
|
||||
$data['target'] = max(1, (int) ($data['target'] ?? 1));
|
||||
$data['sort'] = (int) ($data['sort'] ?? 0);
|
||||
$data['status'] = (int) ($data['status'] ?? 1);
|
||||
|
||||
if ($id) {
|
||||
WxchatTaskModel::where('id', $id)->update($data);
|
||||
} else {
|
||||
$data['create_at'] = time();
|
||||
(new WxchatTaskModel())->save($data);
|
||||
}
|
||||
$this->result->success([], '保存成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除任务(同时清理其进度记录)
|
||||
*/
|
||||
public function del($ids = '')
|
||||
{
|
||||
$ids = $this->request->param('ids', '');
|
||||
if (! $ids) {
|
||||
$this->result->error('请选择要删除的数据');
|
||||
}
|
||||
$idArr = array_filter(array_map('intval', explode(',', $ids)));
|
||||
Db::transaction(function () use ($idArr) {
|
||||
WxchatTaskRecord::whereIn('task_id', $idArr)->delete();
|
||||
WxchatTaskModel::destroy($idArr);
|
||||
});
|
||||
$this->result->success([], '删除成功');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?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\member;
|
||||
|
||||
use addon\wxchat\model\WxchatRealnameAuth;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
|
||||
/**
|
||||
* 真人认证:多重认证 + AI 智能审核 + 人工复审
|
||||
*/
|
||||
class Auth extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* 提交认证资料
|
||||
*/
|
||||
public function submit()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$realName = trim((string) Request::post('real_name', ''));
|
||||
$idCard = trim((string) Request::post('id_card', ''));
|
||||
$frontImg = trim((string) Request::post('front_img', ''));
|
||||
$backImg = trim((string) Request::post('back_img', ''));
|
||||
$holdImg = trim((string) Request::post('hold_img', ''));
|
||||
$faceImg = trim((string) Request::post('face_img', ''));
|
||||
|
||||
if (! $realName || strlen($idCard) < 15) {
|
||||
$this->error('请填写真实姓名与证件号');
|
||||
}
|
||||
|
||||
// 证件号脱敏存储(保留前4后4)
|
||||
$masked = substr($idCard, 0, 4) . str_repeat('*', max(0, strlen($idCard) - 8)) . substr($idCard, -4);
|
||||
|
||||
$data = [
|
||||
'uid' => $uid,
|
||||
'real_name' => $realName,
|
||||
'id_card' => $masked,
|
||||
'front_img' => $frontImg,
|
||||
'back_img' => $backImg,
|
||||
'hold_img' => $holdImg,
|
||||
'face_img' => $faceImg,
|
||||
'status' => 0,
|
||||
'ai_result' => 0,
|
||||
'ai_score' => 0,
|
||||
'reject_reason' => '',
|
||||
'update_at' => time(),
|
||||
];
|
||||
|
||||
$exist = WxchatRealnameAuth::where('uid', $uid)->find();
|
||||
if ($exist) {
|
||||
$exist->save($data);
|
||||
} else {
|
||||
$data['create_at'] = time();
|
||||
(new WxchatRealnameAuth())->save($data);
|
||||
}
|
||||
|
||||
// 触发 AI 智能审核
|
||||
$this->aiCheck($uid);
|
||||
|
||||
$this->success([], '已提交,审核中');
|
||||
}
|
||||
|
||||
/**
|
||||
* 我的认证状态(补充 AI 审核详情)
|
||||
*/
|
||||
public function status()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$auth = WxchatRealnameAuth::where('uid', $uid)->find();
|
||||
$this->success([
|
||||
'status' => $auth ? (int) $auth->status : -1,
|
||||
'ai_result' => $auth ? (int) $auth->ai_result : 0,
|
||||
'ai_score' => $auth ? (float) $auth->ai_score : 0,
|
||||
'ai_detail' => $auth ? ($auth->ai_detail ?? '') : '',
|
||||
'reject_reason' => $auth ? $auth->reject_reason : '',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI 智能审核(接入 AiAuditService:本地启发式 / 真实 HTTP 服务,可配置切换)
|
||||
*/
|
||||
private function aiCheck($uid)
|
||||
{
|
||||
$auth = WxchatRealnameAuth::where('uid', $uid)->find();
|
||||
if (! $auth) {
|
||||
return;
|
||||
}
|
||||
$result = \addon\wxchat\service\AiAuditService::audit([
|
||||
'uid' => $uid,
|
||||
'real_name' => $auth->real_name,
|
||||
'id_card' => $auth->id_card,
|
||||
'front_img' => $auth->front_img,
|
||||
'back_img' => $auth->back_img,
|
||||
'hold_img' => $auth->hold_img,
|
||||
'face_img' => $auth->face_img,
|
||||
]);
|
||||
$auth->ai_score = $result['score'];
|
||||
$auth->ai_result = $result['result']; // 1-通过初筛 2-不通过
|
||||
$auth->ai_detail = $result['detail'] ?? '';
|
||||
$auth->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* 人工复审(后台调用,建议加管理员鉴权)
|
||||
*/
|
||||
public function review()
|
||||
{
|
||||
$id = (int) Request::post('id', 0);
|
||||
$pass = (int) Request::post('pass', 0);
|
||||
$reason = trim((string) Request::post('reason', ''));
|
||||
$auth = WxchatRealnameAuth::find($id);
|
||||
if (! $auth) {
|
||||
$this->error('记录不存在');
|
||||
}
|
||||
$auth->status = $pass ? 1 : 2;
|
||||
$auth->reject_reason = $pass ? '' : $reason;
|
||||
$auth->reviewer_id = $this->auth->uid ?? 0;
|
||||
$auth->review_at = time();
|
||||
$auth->save();
|
||||
|
||||
// 审核通过 -> 标记资料真人认证
|
||||
if ($pass) {
|
||||
\ywxapp\model\MemberProfile::where('uid', $auth->uid)
|
||||
->update(['is_realname' => 1]);
|
||||
}
|
||||
$this->success([], $pass ? '已通过' : '已驳回');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
<?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\member;
|
||||
|
||||
use addon\wxchat\model\WxchatFamily;
|
||||
use addon\wxchat\model\WxchatFamilyMember;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\model\MemberProfile as ProfileModel;
|
||||
|
||||
/**
|
||||
* 家族:创建 / 加入 / 退出 / 成员 / 家族群聊(复用 group 消息通道)
|
||||
*/
|
||||
class Family extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* 创建家族
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$name = trim((string) Request::post('name', ''));
|
||||
$avatar = trim((string) Request::post('avatar', ''));
|
||||
$notice = trim((string) Request::post('notice', ''));
|
||||
if (! $name) {
|
||||
$this->error('请填写家族名称');
|
||||
}
|
||||
|
||||
$family = new WxchatFamily();
|
||||
$family->name = $name;
|
||||
$family->avatar = $avatar;
|
||||
$family->owner_uid = $uid;
|
||||
$family->notice = $notice;
|
||||
$family->member_count = 1;
|
||||
$family->status = 1;
|
||||
$family->create_at = time();
|
||||
$family->save();
|
||||
|
||||
$member = new WxchatFamilyMember();
|
||||
$member->family_id = $family->id;
|
||||
$member->uid = $uid;
|
||||
$member->role = 2; // 族长
|
||||
$member->join_at = time();
|
||||
$member->save();
|
||||
|
||||
$this->success(['family_id' => $family->id], '创建成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 加入家族
|
||||
*/
|
||||
public function join()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$familyId = (int) Request::post('family_id', 0);
|
||||
$family = WxchatFamily::where('id', $familyId)->where('status', 1)->find();
|
||||
if (! $family) {
|
||||
$this->error('家族不存在或已解散');
|
||||
}
|
||||
if (WxchatFamilyMember::where('family_id', $familyId)->where('uid', $uid)->find()) {
|
||||
$this->error('你已在家族中');
|
||||
}
|
||||
$member = new WxchatFamilyMember();
|
||||
$member->family_id = $familyId;
|
||||
$member->uid = $uid;
|
||||
$member->role = 0;
|
||||
$member->join_at = time();
|
||||
$member->save();
|
||||
|
||||
$family->where('id', $familyId)->inc('member_count')->update();
|
||||
$this->success([], '已加入');
|
||||
}
|
||||
|
||||
/**
|
||||
* 退出家族(族长退出则解散家族)
|
||||
*/
|
||||
public function quit()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$familyId = (int) Request::post('family_id', 0);
|
||||
$member = WxchatFamilyMember::where('family_id', $familyId)->where('uid', $uid)->find();
|
||||
if (! $member) {
|
||||
$this->error('你不在该家族');
|
||||
}
|
||||
if ($member->role == 2) {
|
||||
// 族长退出 -> 解散
|
||||
WxchatFamily::where('id', $familyId)->update(['status' => 0]);
|
||||
WxchatFamilyMember::where('family_id', $familyId)->delete();
|
||||
} else {
|
||||
$member->delete();
|
||||
WxchatFamily::where('id', $familyId)->where('member_count', '>', 0)->dec('member_count')->update();
|
||||
}
|
||||
$this->success([], '已退出');
|
||||
}
|
||||
|
||||
/**
|
||||
* 家族信息
|
||||
*/
|
||||
public function info()
|
||||
{
|
||||
$familyId = (int) Request::get('family_id', 0);
|
||||
$family = WxchatFamily::where('id', $familyId)->where('status', 1)->find();
|
||||
if (! $family) {
|
||||
$this->error('家族不存在');
|
||||
}
|
||||
$this->success($family);
|
||||
}
|
||||
|
||||
/**
|
||||
* 成员列表(含昵称/头像)
|
||||
*/
|
||||
public function members()
|
||||
{
|
||||
$familyId = (int) Request::get('family_id', 0);
|
||||
$list = Db::name('wxchat_family_members')->alias('m')
|
||||
->join('user u', 'u.uid = m.uid')
|
||||
->join('user_profile p', 'p.uid = m.uid', 'left')
|
||||
->where('m.family_id', $familyId)
|
||||
->field('m.uid,m.role,m.join_at,u.nickname,u.avatar,p.gender,p.residecity')
|
||||
->order('m.role', 'desc')
|
||||
->select();
|
||||
$this->success($list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
<?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\member;
|
||||
|
||||
use addon\wxchat\model\WxchatRoom;
|
||||
use addon\wxchat\model\WxchatRoomMember;
|
||||
use addon\wxchat\model\WxchatRoomMic;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
|
||||
/**
|
||||
* 语音 / 视频房:创建 / 加入 / 麦位管理 / 弹幕开关
|
||||
* 房间公屏聊天复用 group 消息通道(session_id = room_id)
|
||||
*/
|
||||
class Room extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* 房间列表(直播中按热度)
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$type = (int) Request::get('type', 0);
|
||||
$query = WxchatRoom::where('status', 1);
|
||||
if ($type) {
|
||||
$query->where('type', $type);
|
||||
}
|
||||
$list = $query->order('hot', 'desc')->order('create_at', 'desc')
|
||||
->paginate(20, false, ['page' => (int) Request::get('page', 1)]);
|
||||
$this->success([
|
||||
'list' => $list->items(),
|
||||
'total' => $list->total(),
|
||||
'pages' => $list->lastPage(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建房间(自动初始化麦位 + 房主上 0 号麦)
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$title = trim((string) Request::post('title', ''));
|
||||
$type = (int) Request::post('type', 1);
|
||||
$cover = trim((string) Request::post('cover', ''));
|
||||
$notice = trim((string) Request::post('notice', ''));
|
||||
$micCount = max(2, min(20, (int) Request::post('mic_count', 8)));
|
||||
if (! $title) {
|
||||
$this->error('请填写房间标题');
|
||||
}
|
||||
|
||||
$room = new WxchatRoom();
|
||||
$room->title = $title;
|
||||
$room->type = $type;
|
||||
$room->owner_uid = $uid;
|
||||
$room->cover = $cover;
|
||||
$room->notice = $notice;
|
||||
$room->mic_count = $micCount;
|
||||
$room->danmaku_on = 1;
|
||||
$room->status = 1;
|
||||
$room->create_at = time();
|
||||
$room->save();
|
||||
|
||||
// 初始化麦位
|
||||
$mics = [];
|
||||
for ($i = 0; $i < $micCount; $i++) {
|
||||
$mics[] = [
|
||||
'room_id' => $room->id,
|
||||
'mic_index' => $i,
|
||||
'uid' => $i == 0 ? $uid : 0,
|
||||
'status' => $i == 0 ? 1 : 0,
|
||||
'create_at' => time(),
|
||||
];
|
||||
}
|
||||
(new WxchatRoomMic())->saveAll($mics);
|
||||
|
||||
// 房主进入
|
||||
$member = new WxchatRoomMember();
|
||||
$member->room_id = $room->id;
|
||||
$member->uid = $uid;
|
||||
$member->role = 1; // 主播
|
||||
$member->join_at = time();
|
||||
$member->save();
|
||||
|
||||
$this->success(['room_id' => $room->id], '开播成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 进入房间(观众)
|
||||
*/
|
||||
public function join()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$roomId = (int) Request::post('room_id', 0);
|
||||
$room = WxchatRoom::where('id', $roomId)->where('status', 1)->find();
|
||||
if (! $room) {
|
||||
$this->error('房间不存在或已关闭');
|
||||
}
|
||||
if (! WxchatRoomMember::where('room_id', $roomId)->where('uid', $uid)->find()) {
|
||||
$member = new WxchatRoomMember();
|
||||
$member->room_id = $roomId;
|
||||
$member->uid = $uid;
|
||||
$member->role = 0;
|
||||
$member->join_at = time();
|
||||
$member->save();
|
||||
$room->where('id', $roomId)->inc('hot')->update();
|
||||
}
|
||||
$mics = WxchatRoomMic::where('room_id', $roomId)->order('mic_index')->select();
|
||||
$this->success([
|
||||
'room' => $room,
|
||||
'mics' => $mics,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 离开房间
|
||||
*/
|
||||
public function leave()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$roomId = (int) Request::post('room_id', 0);
|
||||
WxchatRoomMember::where('room_id', $roomId)->where('uid', $uid)->delete();
|
||||
// 释放麦位
|
||||
WxchatRoomMic::where('room_id', $roomId)->where('uid', $uid)->update(['uid' => 0, 'status' => 0]);
|
||||
$room = WxchatRoom::find($roomId);
|
||||
if ($room && $room->owner_uid == $uid) {
|
||||
$room->status = 0;
|
||||
$room->save();
|
||||
}
|
||||
$this->success([], '已离开');
|
||||
}
|
||||
|
||||
/**
|
||||
* 上麦
|
||||
*/
|
||||
public function micUp()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$roomId = (int) Request::post('room_id', 0);
|
||||
$micIdx = (int) Request::post('mic_index', 0);
|
||||
$mic = WxchatRoomMic::where('room_id', $roomId)->where('mic_index', $micIdx)->find();
|
||||
if (! $mic) {
|
||||
$this->error('麦位不存在');
|
||||
}
|
||||
if ($mic->status == 2) {
|
||||
$this->error('该麦位已锁定');
|
||||
}
|
||||
if ($mic->status == 1 && $mic->uid != $uid) {
|
||||
$this->error('麦位已被占用');
|
||||
}
|
||||
$mic->uid = $uid;
|
||||
$mic->status = 1;
|
||||
$mic->save();
|
||||
$this->success([], '上麦成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 下麦
|
||||
*/
|
||||
public function micDown()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$roomId = (int) Request::post('room_id', 0);
|
||||
$micIdx = (int) Request::post('mic_index', 0);
|
||||
$mic = WxchatRoomMic::where('room_id', $roomId)->where('mic_index', $micIdx)->where('uid', $uid)->find();
|
||||
if (! $mic) {
|
||||
$this->error('你不在该麦位');
|
||||
}
|
||||
$mic->uid = 0;
|
||||
$mic->status = 0;
|
||||
$mic->save();
|
||||
$this->success([], '下麦成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 锁定 / 解锁 / 禁麦(房主)
|
||||
*/
|
||||
public function micManage()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$roomId = (int) Request::post('room_id', 0);
|
||||
$micIdx = (int) Request::post('mic_index', 0);
|
||||
$op = (int) Request::post('op', 2); // 2锁定 0解锁 3禁麦
|
||||
$room = WxchatRoom::find($roomId);
|
||||
if (! $room || $room->owner_uid != $uid) {
|
||||
$this->error('仅房主可操作');
|
||||
}
|
||||
WxchatRoomMic::where('room_id', $roomId)->where('mic_index', $micIdx)
|
||||
->update(['status' => $op, 'uid' => $op == 0 ? 0 : Db::raw('uid')]);
|
||||
$this->success([], '操作成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹幕开关(房主房间级 / 或用户快捷关闭)
|
||||
*/
|
||||
public function danmaku()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$roomId = (int) Request::post('room_id', 0);
|
||||
$on = (int) Request::post('on', 1);
|
||||
$room = WxchatRoom::find($roomId);
|
||||
if (! $room) {
|
||||
$this->error('房间不存在');
|
||||
}
|
||||
// 房主可切换房间弹幕总开关;普通用户仅对自己快捷关闭(落到个人设置)
|
||||
if ($room->owner_uid == $uid) {
|
||||
$room->danmaku_on = $on ? 1 : 0;
|
||||
$room->save();
|
||||
$this->success(['danmaku_on' => $room->danmaku_on], '房间弹幕已' . ($on ? '开启' : '关闭'));
|
||||
}
|
||||
// 非房主:更新个人弹幕总开关(设置-互动-弹幕管理)
|
||||
$pc = \ywxapp\model\MemberProfile::where('uid', $uid)->value('privacy_config');
|
||||
$pc = $pc ? json_decode($pc, true) : [];
|
||||
$pc = is_array($pc) ? $pc : [];
|
||||
$pc['danmaku_global'] = $on ? 1 : 0;
|
||||
\ywxapp\model\MemberProfile::where('uid', $uid)
|
||||
->update(['privacy_config' => json_encode($pc, JSON_UNESCAPED_UNICODE)]);
|
||||
$this->success(['danmaku_global' => $on], '个人弹幕已' . ($on ? '开启' : '关闭'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
<?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\member;
|
||||
|
||||
use addon\wxchat\model\WxchatTask;
|
||||
use addon\wxchat\model\WxchatTaskRecord;
|
||||
use think\facade\Db;
|
||||
use think\facade\Request;
|
||||
use ywxapp\controller\FrontendBase;
|
||||
use ywxapp\service\WalletService;
|
||||
|
||||
/**
|
||||
* 任务赚钱体系:每日任务 / 邀请好友 / 完善资料 -> 领取金币
|
||||
*/
|
||||
class Task extends FrontendBase
|
||||
{
|
||||
/**
|
||||
* 任务列表(附带我的进度)
|
||||
*/
|
||||
public function lists()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$tasks = WxchatTask::where('status', 1)
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
$records = WxchatTaskRecord::where('uid', $uid)->column('*', 'task_id');
|
||||
$list = [];
|
||||
foreach ($tasks as $t) {
|
||||
$rec = $records[$t->id] ?? null;
|
||||
$list[] = [
|
||||
'task_id' => $t->id,
|
||||
'title' => $t->title,
|
||||
'type' => $t->type,
|
||||
'desc' => $t->desc,
|
||||
'reward_coins' => $t->reward_coins,
|
||||
'target' => $t->target,
|
||||
'progress' => $rec ? (int) $rec['progress'] : 0,
|
||||
'status' => $rec ? (int) $rec['status'] : 0, // 0进行中 1待领 2已领
|
||||
];
|
||||
}
|
||||
$this->success(['list' => $list, 'coins' => WalletService::getCoins($uid)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度上报(由业务事件调用:发布动态/完善资料/邀请成功等)
|
||||
*/
|
||||
public function progress()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$taskId = (int) Request::post('task_id', 0);
|
||||
$step = max(1, (int) Request::post('step', 1));
|
||||
$task = WxchatTask::where('id', $taskId)->where('status', 1)->find();
|
||||
if (! $task) {
|
||||
$this->error('任务不存在');
|
||||
}
|
||||
$rec = WxchatTaskRecord::where('uid', $uid)->where('task_id', $taskId)->find();
|
||||
if (! $rec) {
|
||||
$rec = new WxchatTaskRecord();
|
||||
$rec->uid = $uid;
|
||||
$rec->task_id = $taskId;
|
||||
$rec->progress = 0;
|
||||
$rec->status = 0;
|
||||
$rec->reward_coins = 0;
|
||||
$rec->create_at = time();
|
||||
}
|
||||
if ($rec->status == 2) {
|
||||
$this->error('任务已领取');
|
||||
}
|
||||
$rec->progress = min($task->target, $rec->progress + $step);
|
||||
if ($rec->progress >= $task->target) {
|
||||
$rec->status = 1; // 完成待领
|
||||
}
|
||||
$rec->update_at = time();
|
||||
$rec->save();
|
||||
$this->success(['status' => $rec->status, 'progress' => $rec->progress], '进度已更新');
|
||||
}
|
||||
|
||||
/**
|
||||
* 领取奖励(发放金币)
|
||||
*/
|
||||
public function claim()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$taskId = (int) Request::post('task_id', 0);
|
||||
$task = WxchatTask::where('id', $taskId)->find();
|
||||
if (! $task) {
|
||||
$this->error('任务不存在');
|
||||
}
|
||||
$rec = WxchatTaskRecord::where('uid', $uid)->where('task_id', $taskId)->find();
|
||||
if (! $rec || $rec->status == 0) {
|
||||
$this->error('任务未完成');
|
||||
}
|
||||
if ($rec->status == 2) {
|
||||
$this->error('奖励已领取');
|
||||
}
|
||||
$reward = $task->reward_coins;
|
||||
WalletService::incCoins($uid, $reward, '完成任务:' . $task->title);
|
||||
$rec->status = 2;
|
||||
$rec->reward_coins = $reward;
|
||||
$rec->update_at = time();
|
||||
$rec->save();
|
||||
$this->success(['coins' => WalletService::getCoins($uid), 'reward' => $reward], '领取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 邀请好友(模拟:记录一次有效邀请并推进邀请任务)
|
||||
*/
|
||||
public function invite()
|
||||
{
|
||||
$uid = $this->auth->uid;
|
||||
$task = WxchatTask::where('type', 'invite')->where('status', 1)->order('id')->find();
|
||||
if (! $task) {
|
||||
$this->success([], '暂无邀请任务');
|
||||
return;
|
||||
}
|
||||
// 上报一次邀请进度(真实环境应在被邀请人注册成功后触发)
|
||||
$rec = WxchatTaskRecord::where('uid', $uid)->where('task_id', $task->id)->find();
|
||||
if (! $rec) {
|
||||
$rec = new WxchatTaskRecord();
|
||||
$rec->uid = $uid;
|
||||
$rec->task_id = $task->id;
|
||||
$rec->progress = 0;
|
||||
$rec->status = 0;
|
||||
$rec->reward_coins = 0;
|
||||
$rec->create_at = time();
|
||||
}
|
||||
if ($rec->status == 2) {
|
||||
$this->success([], '邀请任务已完成');
|
||||
return;
|
||||
}
|
||||
$rec->progress = min($task->target, $rec->progress + 1);
|
||||
if ($rec->progress >= $task->target) {
|
||||
$rec->status = 1;
|
||||
}
|
||||
$rec->update_at = time();
|
||||
$rec->save();
|
||||
$this->success(['progress' => $rec->progress, 'status' => $rec->status], '邀请成功');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user