1. 前端个人中心新增真实姓名输入与实名状态展示,支持微信提现核验 2. 后端Auth接口新增真实姓名修改与手机号冲突校验逻辑 3. 后台新增用户详情综合看板,展示佣金、提现、分销等全量数据 4. 优化微信转账逻辑,支持paying状态重试时查询微信单号避免重复创建 5. 新增数据库ALTER脚本同步transfer_status字段结构 6. 新增部署与数据库变更工具脚本,支持线上线下环境同步
396 行
14 KiB
PHP
396 行
14 KiB
PHP
<?php
|
||
|
||
namespace app\admin\controller\fc;
|
||
|
||
use app\common\controller\Backend;
|
||
use think\Db;
|
||
use think\exception\PDOException;
|
||
use think\exception\ValidateException;
|
||
|
||
/**
|
||
* 同城-用户扩展与用户管理控制器
|
||
*
|
||
* @icon fa fa-user-circle
|
||
*/
|
||
class Userext extends Backend
|
||
{
|
||
|
||
/**
|
||
* FcUserExt模型对象
|
||
* @var \app\admin\model\FcUserExt
|
||
*/
|
||
protected $model = null;
|
||
|
||
/**
|
||
* 无需鉴权的公开方法(详情弹窗开放给后台已登录管理角色查看)
|
||
*/
|
||
protected $noNeedRight = ['detail'];
|
||
|
||
/**
|
||
* 控制器初始化
|
||
*
|
||
* @return void
|
||
*/
|
||
public function _initialize()
|
||
{
|
||
parent::_initialize();
|
||
$this->model = new \app\admin\model\FcUserExt;
|
||
}
|
||
|
||
/**
|
||
* 会员列表(关联 fa_user 展示昵称 / 手机号 / 账户余额 / 真实姓名)
|
||
*
|
||
* @return string|\think\response\Json
|
||
*/
|
||
public function index()
|
||
{
|
||
if ($this->request->isAjax()) {
|
||
list($where, $sort, $order, $offset, $limit) = $this->buildparams();
|
||
|
||
$total = $this->model->alias('e')
|
||
->join('user u', 'u.id = e.user_id', 'LEFT')
|
||
->where($where)
|
||
->count();
|
||
|
||
$list = $this->model->alias('e')
|
||
->join('user u', 'u.id = e.user_id', 'LEFT')
|
||
->where($where)
|
||
->field('e.*, u.nickname, u.mobile, u.money')
|
||
->order($sort, $order)
|
||
->limit($offset, $limit)
|
||
->select();
|
||
|
||
return json(['total' => $total, 'rows' => $list]);
|
||
}
|
||
return $this->view->fetch();
|
||
}
|
||
|
||
/**
|
||
* 用户详细信息综合看板(包含基本资料、实名信息、微信提现账户、分销佣金、会员特权及近期提现记录)
|
||
*
|
||
* @param int|null $ids 用户扩展记录ID
|
||
* @return string
|
||
*/
|
||
public function detail($ids = null)
|
||
{
|
||
$id = $ids ? $ids : $this->request->param('ids');
|
||
$row = $this->model->get($id);
|
||
if (!$row) {
|
||
$this->error(__('No Results were found'));
|
||
}
|
||
|
||
$userId = (int)$row['user_id'];
|
||
|
||
// 1. 主表 fa_user 用户基本信息
|
||
$user = Db::name('user')->where('id', $userId)->find();
|
||
if (!$user) {
|
||
$user = [
|
||
'id' => $userId,
|
||
'nickname' => '未注册用户',
|
||
'mobile' => '-',
|
||
'avatar' => '',
|
||
'money' => '0.00',
|
||
'score' => 0,
|
||
'createtime' => null,
|
||
'logintime' => null,
|
||
'loginip' => '',
|
||
];
|
||
}
|
||
|
||
// 2. 上级邀请人信息
|
||
$inviterUser = null;
|
||
if (!empty($row['inviter_id'])) {
|
||
$inviterUser = Db::name('user')
|
||
->where('id', (int)$row['inviter_id'])
|
||
->field('id,nickname,mobile,avatar')
|
||
->find();
|
||
}
|
||
|
||
// 3. 分销与佣金统计
|
||
$commissionTotal = (float)Db::name('fc_commission')
|
||
->where('user_id', $userId)
|
||
->where('status', '<>', 2)
|
||
->sum('amount');
|
||
|
||
$commissionSettled = (float)Db::name('fc_commission')
|
||
->where('user_id', $userId)
|
||
->where('status', 1)
|
||
->sum('amount');
|
||
|
||
$commissionPending = (float)Db::name('fc_commission')
|
||
->where('user_id', $userId)
|
||
->where('status', 0)
|
||
->sum('amount');
|
||
|
||
// 累计已成功提现金额
|
||
$withdrawPaidTotal = (float)Db::name('fc_withdraw')
|
||
->where('user_id', $userId)
|
||
->where('status', 'paid')
|
||
->sum('amount');
|
||
|
||
// 已占用提现金额(包含审核中、转账中、已打款)
|
||
$withdrawAppliedTotal = (float)Db::name('fc_withdraw')
|
||
->where('user_id', $userId)
|
||
->where('status', 'in', ['auditing', 'paying', 'paid'])
|
||
->sum('amount');
|
||
|
||
// 当前剩余可提现金额
|
||
$withdrawable = max(0, $commissionSettled - $withdrawAppliedTotal);
|
||
|
||
// 下级团队人数统计(一级、二级)
|
||
$level1Count = (int)Db::name('fc_dist_relation')
|
||
->where('parent_id', $userId)
|
||
->where('level', 1)
|
||
->count();
|
||
|
||
$level2Count = (int)Db::name('fc_dist_relation')
|
||
->where('parent_id', $userId)
|
||
->where('level', 2)
|
||
->count();
|
||
|
||
// 4. 业务数据概览(发帖数与消费订单数)
|
||
$postCount = (int)Db::name('fc_post')->where('user_id', $userId)->count();
|
||
$orderCount = (int)Db::name('fc_order')->where('user_id', $userId)->where('status', 'paid')->count();
|
||
$orderSpend = (float)Db::name('fc_order')->where('user_id', $userId)->where('status', 'paid')->sum('amount');
|
||
|
||
// 5. 最近 5 笔提现记录
|
||
$recentWithdraws = Db::name('fc_withdraw')
|
||
->where('user_id', $userId)
|
||
->order('id', 'desc')
|
||
->limit(5)
|
||
->select();
|
||
|
||
// 6. 最近 5 笔佣金明细
|
||
$recentCommissions = Db::name('fc_commission')->alias('c')
|
||
->join('user fu', 'fu.id = c.from_user_id', 'LEFT')
|
||
->where('c.user_id', $userId)
|
||
->field('c.*, fu.nickname as from_nickname')
|
||
->order('c.id', 'desc')
|
||
->limit(5)
|
||
->select();
|
||
|
||
$this->view->assign([
|
||
'row' => $row,
|
||
'userInfo' => $user,
|
||
'inviterUser' => $inviterUser,
|
||
'commissionTotal' => round($commissionTotal, 2),
|
||
'commissionSettled' => round($commissionSettled, 2),
|
||
'commissionPending' => round($commissionPending, 2),
|
||
'withdrawPaidTotal' => round($withdrawPaidTotal, 2),
|
||
'withdrawable' => round($withdrawable, 2),
|
||
'level1Count' => $level1Count,
|
||
'level2Count' => $level2Count,
|
||
'postCount' => $postCount,
|
||
'orderCount' => $orderCount,
|
||
'orderSpend' => round($orderSpend, 2),
|
||
'recentWithdraws' => $recentWithdraws,
|
||
'recentCommissions' => $recentCommissions,
|
||
]);
|
||
|
||
return $this->view->fetch();
|
||
}
|
||
|
||
/**
|
||
* 编辑用户扩展资料(包含实名姓名、会员等级、发帖额度等)
|
||
*
|
||
* @param int|null $ids 用户扩展记录ID
|
||
* @return string
|
||
*/
|
||
public function edit($ids = null)
|
||
{
|
||
$id = $ids ? $ids : $this->request->param('ids');
|
||
$row = $this->model->get($id);
|
||
if (!$row) {
|
||
$this->error(__('No Results were found'));
|
||
}
|
||
|
||
if ($this->request->isPost()) {
|
||
$params = $this->request->post('row/a');
|
||
if ($params) {
|
||
$params = $this->preExcludeFields($params);
|
||
|
||
// 1. 真实姓名与实名状态清洗
|
||
if (isset($params['realname'])) {
|
||
$params['realname'] = trim((string)$params['realname']);
|
||
}
|
||
if (isset($params['realname_status'])) {
|
||
$params['realname_status'] = (int)$params['realname_status'] === 1 ? 1 : 0;
|
||
}
|
||
|
||
// 2. 用户身份(1: 个人用户, 2: 商家)
|
||
if (isset($params['user_type'])) {
|
||
$params['user_type'] = (int)$params['user_type'] === 2 ? 2 : 1;
|
||
}
|
||
|
||
// 3. 会员状态与到期时间处理
|
||
if (isset($params['is_member'])) {
|
||
$params['is_member'] = (int)$params['is_member'] === 1 ? 1 : 0;
|
||
}
|
||
if (isset($params['member_expiretime'])) {
|
||
$expire = trim((string)$params['member_expiretime']);
|
||
$params['member_expiretime'] = $expire !== '' ? (is_numeric($expire) ? (int)$expire : strtotime($expire)) : null;
|
||
}
|
||
|
||
// 4. 发帖配额及到期时间处理
|
||
if (isset($params['post_quota_left'])) {
|
||
$params['post_quota_left'] = max(0, (int)$params['post_quota_left']);
|
||
}
|
||
if (isset($params['post_quota_expire'])) {
|
||
$expire = trim((string)$params['post_quota_expire']);
|
||
$params['post_quota_expire'] = $expire !== '' ? (is_numeric($expire) ? (int)$expire : strtotime($expire)) : null;
|
||
}
|
||
|
||
// 5. 免费电话查看配额处理
|
||
if (isset($params['free_view_quota'])) {
|
||
$params['free_view_quota'] = max(0, (int)$params['free_view_quota']);
|
||
}
|
||
if (isset($params['free_view_used'])) {
|
||
$params['free_view_used'] = max(0, (int)$params['free_view_used']);
|
||
}
|
||
|
||
// 6. 邀请人处理(避免自身成为自身邀请人)
|
||
if (isset($params['inviter_id'])) {
|
||
$inviterId = (int)$params['inviter_id'];
|
||
if ($inviterId > 0 && $inviterId === (int)$row['user_id']) {
|
||
$this->error('上级邀请人不能设置为当前用户本人');
|
||
}
|
||
$params['inviter_id'] = $inviterId;
|
||
}
|
||
|
||
Db::startTrans();
|
||
try {
|
||
$row->allowField(true)->save($params);
|
||
Db::commit();
|
||
} catch (ValidateException $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
} catch (PDOException $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
} catch (\Exception $e) {
|
||
Db::rollback();
|
||
$this->error($e->getMessage());
|
||
}
|
||
$this->success('更新成功');
|
||
}
|
||
$this->error(__('Parameter %s can not be empty', ''));
|
||
}
|
||
|
||
// 关联主表用户信息
|
||
$userInfo = Db::name('user')->where('id', (int)$row['user_id'])->find();
|
||
$inviterInfo = null;
|
||
if (!empty($row['inviter_id'])) {
|
||
$inviterInfo = Db::name('user')->where('id', (int)$row['inviter_id'])->field('id,nickname,mobile')->find();
|
||
}
|
||
|
||
$this->view->assign([
|
||
'row' => $row,
|
||
'userInfo' => $userInfo ?: [],
|
||
'inviterInfo' => $inviterInfo,
|
||
]);
|
||
|
||
return $this->view->fetch();
|
||
}
|
||
|
||
/**
|
||
* 余额充值(给选中用户增加账户余额)
|
||
*
|
||
* POST /s.php/fc/userext/recharge
|
||
* 入参:{ ids: '1001,1002' , amount: 100, remark?: '备注' }
|
||
*/
|
||
public function recharge()
|
||
{
|
||
$ids = (string)$this->request->post('ids', '');
|
||
$userIds = array_filter(array_map('intval', explode(',', $ids)));
|
||
$userIds = array_values(array_unique($userIds));
|
||
if (!$userIds) {
|
||
$this->error('请选择要充值的用户');
|
||
}
|
||
|
||
$amount = round((float)$this->request->post('amount', 0), 2);
|
||
if ($amount <= 0) {
|
||
$this->error('充值金额需大于 0');
|
||
}
|
||
if ($amount > 100000) {
|
||
$this->error('单次充值金额过大');
|
||
}
|
||
$remark = trim((string)$this->request->post('remark', ''));
|
||
$now = time();
|
||
$done = 0;
|
||
$missing = [];
|
||
|
||
Db::startTrans();
|
||
try {
|
||
foreach ($userIds as $uid) {
|
||
// 加行锁,避免并发充值互相覆盖余额
|
||
$user = Db::name('user')->where('id', $uid)->lock(true)->find();
|
||
if (!$user) {
|
||
$missing[] = $uid;
|
||
continue;
|
||
}
|
||
|
||
$balance = round((float)$user['money'] + $amount, 2);
|
||
Db::name('user')->where('id', $uid)->update([
|
||
'money' => $balance,
|
||
'updatetime' => $now,
|
||
]);
|
||
|
||
// 资金流水(平台口径:充值属于收入;scene=后台充值 与用户微信充值区分)
|
||
Db::name('fc_finance_flow')->insert([
|
||
'user_id' => $uid,
|
||
'flow_no' => 'R' . date('YmdHis') . mt_rand(100, 999) . $uid,
|
||
'type' => 'income',
|
||
'scene' => '后台充值',
|
||
'amount' => $amount,
|
||
'balance' => $balance,
|
||
'related_id' => $uid,
|
||
'remark' => $remark !== '' ? $remark : ('后台充值给用户 #' . $uid),
|
||
'createtime' => $now,
|
||
]);
|
||
|
||
// 用户钱包流水(fa_user.money 增)→ 余额中心「余额明细」
|
||
Db::name('fc_wallet_flow')->insert([
|
||
'user_id' => $uid,
|
||
'flow_no' => 'W' . date('YmdHis') . mt_rand(100, 999) . $uid,
|
||
'type' => 'income',
|
||
'scene' => '后台充值',
|
||
'amount' => $amount,
|
||
'balance' => $balance,
|
||
'related_id' => $uid,
|
||
'remark' => $remark !== '' ? $remark : ('后台充值给用户 #' . $uid),
|
||
'createtime' => $now,
|
||
]);
|
||
|
||
// 站内通知
|
||
Db::name('fc_message')->insert([
|
||
'user_id' => $uid,
|
||
'type' => 'system',
|
||
'title' => '余额充值到账',
|
||
'content' => '您的账户已充值 ¥' . number_format($amount, 2, '.', '')
|
||
. ',当前余额 ¥' . number_format($balance, 2, '.', '')
|
||
. '。' . ($remark !== '' ? '备注:' . $remark : ''),
|
||
'isread' => 0,
|
||
'readtime' => null,
|
||
'createtime' => $now,
|
||
]);
|
||
|
||
$done++;
|
||
}
|
||
Db::commit();
|
||
} catch (\Exception $e) {
|
||
Db::rollback();
|
||
$this->error('充值失败:' . $e->getMessage());
|
||
}
|
||
|
||
if (!$done) {
|
||
$this->error('未找到对应用户:' . implode(',', $missing) . '(请确认传的是用户ID)');
|
||
}
|
||
|
||
$msg = '充值成功:' . $done . ' 个用户,每人 ¥' . number_format($amount, 2, '.', '');
|
||
if ($missing) {
|
||
$msg .= ';已跳过不存在的用户:' . implode(',', $missing);
|
||
}
|
||
$this->success($msg);
|
||
}
|
||
}
|