Files
cunkebao_v3/Server/application/cunkebao/controller/distribution/ChannelController.php

1455 lines
56 KiB
PHP
Raw Normal View History

2025-12-17 16:20:46 +08:00
<?php
namespace app\cunkebao\controller\distribution;
use app\cunkebao\controller\BaseController;
use app\cunkebao\model\DistributionChannel;
use app\cunkebao\model\DistributionWithdrawal;
use library\ResponseHelper;
use think\Db;
use think\Exception;
use think\facade\Env;
use think\facade\Cache;
2025-12-17 16:20:46 +08:00
use Endroid\QrCode\QrCode;
use Endroid\QrCode\ErrorCorrectionLevel;
use EasyWeChat\Factory;
use EasyWeChat\Kernel\Http\StreamResponse;
/**
* 分销渠道控制器
*/
class ChannelController extends BaseController
{
/**
* 添加渠道
* @return \think\response\Json
*/
public function create()
{
try {
// 获取参数
$name = $this->request->param('name', '');
$phone = $this->request->param('phone', '');
$wechatId = $this->request->param('wechatId', '');
$remarks = $this->request->param('remarks', '');
$createType = $this->request->param('createType', DistributionChannel::CREATE_TYPE_MANUAL); // 默认为手动创建
$companyId = $this->getUserInfo('companyId');
2025-12-29 15:12:57 +08:00
$userId = $this->getUserInfo('id');
2025-12-17 16:20:46 +08:00
// 参数验证
if (empty($name)) {
return ResponseHelper::error('渠道名称不能为空', 400);
}
// 验证渠道名称长度
if (mb_strlen($name) > 50) {
return ResponseHelper::error('渠道名称长度不能超过50个字符', 400);
}
// 验证手机号格式(如果提供)
if (!empty($phone)) {
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
return ResponseHelper::error('手机号格式不正确请输入11位数字且以1开头', 400);
}
// 检查手机号是否已存在(排除已删除的渠道)
$existChannel = Db::name('distribution_channel')
->where([
['companyId', '=', $companyId],
['phone', '=', $phone],
['deleteTime', '=', 0]
])
->find();
if ($existChannel) {
return ResponseHelper::error('手机号已被使用', 400);
}
}
// 验证微信号长度(如果提供)
if (!empty($wechatId) && mb_strlen($wechatId) > 50) {
return ResponseHelper::error('微信号长度不能超过50个字符', 400);
}
// 验证备注长度(如果提供)
if (!empty($remarks) && mb_strlen($remarks) > 200) {
return ResponseHelper::error('备注信息长度不能超过200个字符', 400);
}
// 验证创建类型
if (!in_array($createType, [DistributionChannel::CREATE_TYPE_MANUAL, DistributionChannel::CREATE_TYPE_AUTO])) {
$createType = DistributionChannel::CREATE_TYPE_MANUAL;
}
// 生成渠道编码
$code = DistributionChannel::generateChannelCode();
// 准备插入数据
$data = [
'companyId' => $companyId,
2025-12-29 15:12:57 +08:00
'userId' => $userId,
2025-12-17 16:20:46 +08:00
'name' => $name,
'code' => $code,
'phone' => $phone ?: '',
'password' => md5('123456'), // 默认密码123456MD5加密
'wechatId' => $wechatId ?: '',
'remarks' => $remarks ?: '',
'createType' => $createType,
'status' => DistributionChannel::STATUS_ENABLED,
'totalCustomers' => 0,
'todayCustomers' => 0,
'totalFriends' => 0,
'todayFriends' => 0,
'withdrawableAmount' => 0, // 以分为单位存储
'createTime' => time(),
'updateTime' => time(),
];
// 插入数据库
$channelId = Db::name('distribution_channel')->insertGetId($data);
if (!$channelId) {
return ResponseHelper::error('创建渠道失败', 500);
}
// 获取创建的数据
$channel = Db::name('distribution_channel')->where('id', $channelId)->find();
// 格式化返回数据
$result = [
'id' => $channel['id'],
'name' => $channel['name'],
'code' => $channel['code'],
'phone' => $channel['phone'] ?: '',
'wechatId' => $channel['wechatId'] ?: '',
2025-12-20 11:17:30 +08:00
'companyId' => (int)$companyId, // 返回companyId方便小程序自动跳转
2025-12-29 15:12:57 +08:00
'userId' => (int)($channel['userId'] ?? 0),
2025-12-17 16:20:46 +08:00
'createType' => $channel['createType'],
'status' => $channel['status'],
'totalCustomers' => (int)$channel['totalCustomers'],
'todayCustomers' => (int)$channel['todayCustomers'],
'totalFriends' => (int)$channel['totalFriends'],
'todayFriends' => (int)$channel['todayFriends'],
'withdrawableAmount' => round(($channel['withdrawableAmount'] ?? 0) / 100, 2), // 分转元保留2位小数
'createTime' => !empty($channel['createTime']) ? date('Y-m-d H:i:s', $channel['createTime']) : date('Y-m-d H:i:s'),
];
// 返回符合需求的格式包含success字段
return json([
'code' => 200,
'success' => true,
'msg' => '创建成功',
'data' => $result
]);
} catch (Exception $e) {
return ResponseHelper::error('创建渠道失败:' . $e->getMessage(), $e->getCode() ?: 500);
}
}
/**
* 获取渠道列表
* @return \think\response\Json
*/
public function index()
{
try {
// 获取参数
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 20);
$keyword = $this->request->param('keyword', '');
$status = $this->request->param('status', 'all'); // all、enabled、disabled
$companyId = $this->getUserInfo('companyId');
// 参数验证
$page = max(1, intval($page));
$limit = max(1, min(100, intval($limit))); // 限制最大100
// 验证状态参数
$validStatuses = ['all', DistributionChannel::STATUS_ENABLED, DistributionChannel::STATUS_DISABLED];
if (!in_array($status, $validStatuses)) {
$status = 'all';
}
// 构建查询条件
$where = [];
$where[] = ['companyId', '=', $companyId];
$where[] = ['deleteTime', '=', 0];
2025-12-29 15:12:57 +08:00
// 如果不是管理员,只能查看自己创建的数据
if (!$this->getUserInfo('isAdmin')) {
$where[] = ['userId', '=', $this->getUserInfo('id')];
}
2025-12-17 16:20:46 +08:00
// 状态筛选
if ($status !== 'all') {
$where[] = ['status', '=', $status];
}
// 关键词搜索(模糊匹配 name、code、phone、wechatId
if (!empty($keyword)) {
$keyword = trim($keyword);
// 使用 | 分隔字段表示OR关系ThinkPHP语法
$where[] = ['name|code|phone|wechatId', 'like', '%' . $keyword . '%'];
}
// 查询总数
$total = Db::name('distribution_channel')
->where($where)
->count();
// 查询列表(按创建时间倒序)
$list = Db::name('distribution_channel')
->where($where)
->order('createTime DESC')
->page($page, $limit)
->select();
// 格式化数据
$formattedList = [];
foreach ($list as $item) {
$formattedItem = [
'id' => (int)$item['id'],
'name' => $item['name'] ?? '',
'code' => $item['code'] ?? '',
'phone' => !empty($item['phone']) ? $item['phone'] : null,
'wechatId' => !empty($item['wechatId']) ? $item['wechatId'] : null,
2025-12-29 15:12:57 +08:00
'companyId' => (int)($item['companyId'] ?? 0),
'userId' => (int)($item['userId'] ?? 0),
2025-12-17 16:20:46 +08:00
'createType' => $item['createType'] ?? 'manual',
'status' => $item['status'] ?? 'enabled',
'totalCustomers' => (int)($item['totalCustomers'] ?? 0),
'todayCustomers' => (int)($item['todayCustomers'] ?? 0),
'totalFriends' => (int)($item['totalFriends'] ?? 0),
'todayFriends' => (int)($item['todayFriends'] ?? 0),
'withdrawableAmount' => round(($item['withdrawableAmount'] ?? 0) / 100, 2), // 分转元保留2位小数
'createTime' => !empty($item['createTime']) ? date('Y-m-d H:i:s', $item['createTime']) : date('Y-m-d H:i:s'),
];
$formattedList[] = $formattedItem;
}
// 返回结果
return json([
'code' => 200,
'success' => true,
'msg' => '获取成功',
'data' => [
'list' => $formattedList,
'total' => (int)$total
]
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '获取渠道列表失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 编辑渠道
* @return \think\response\Json
*/
public function update()
{
try {
// 获取参数
$id = $this->request->param('id', 0);
$name = $this->request->param('name', '');
$phone = $this->request->param('phone', '');
$wechatId = $this->request->param('wechatId', '');
$remarks = $this->request->param('remarks', '');
$companyId = $this->getUserInfo('companyId');
// 参数验证
if (empty($id)) {
return json([
'code' => 400,
'success' => false,
'msg' => '渠道ID不能为空',
'data' => null
]);
}
// 检查渠道是否存在且属于当前公司
$channel = Db::name('distribution_channel')
->where(['id' => $id, 'companyId' => $companyId, 'deleteTime' => 0])
->find();
if (!$channel) {
return json([
'code' => 404,
'success' => false,
'msg' => '渠道不存在或没有权限',
'data' => null
]);
}
// 准备更新数据
$updateData = [];
$updateData['updateTime'] = time();
// 更新渠道名称
if (!empty($name)) {
if (mb_strlen($name) > 50) {
return json([
'code' => 400,
'success' => false,
'msg' => '渠道名称长度不能超过50个字符',
'data' => null
]);
}
$updateData['name'] = $name;
}
// 更新手机号
if (isset($phone)) { // 允许设置为空
if (!empty($phone)) {
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
return json([
'code' => 400,
'success' => false,
'msg' => '手机号格式不正确请输入11位数字且以1开头',
'data' => null
]);
}
// 检查手机号是否已被其他渠道使用(排除当前渠道和已删除的渠道)
$existChannel = Db::name('distribution_channel')
->where([
['companyId', '=', $companyId],
['phone', '=', $phone],
['id', '<>', $id],
['deleteTime', '=', 0]
])
->find();
if ($existChannel) {
return json([
'code' => 400,
'success' => false,
'msg' => '手机号已被其他渠道使用',
'data' => null
]);
}
}
$updateData['phone'] = $phone ?: '';
}
// 更新微信号
if (isset($wechatId)) { // 允许设置为空
if (!empty($wechatId) && mb_strlen($wechatId) > 50) {
return json([
'code' => 400,
'success' => false,
'msg' => '微信号长度不能超过50个字符',
'data' => null
]);
}
$updateData['wechatId'] = $wechatId ?: '';
}
// 更新备注
if (isset($remarks)) { // 允许设置为空
if (!empty($remarks) && mb_strlen($remarks) > 200) {
return json([
'code' => 400,
'success' => false,
'msg' => '备注信息长度不能超过200个字符',
'data' => null
]);
}
$updateData['remarks'] = $remarks ?: '';
}
// 如果没有要更新的数据
if (count($updateData) <= 1) { // 只有updateTime
return json([
'code' => 400,
'success' => false,
'msg' => '没有要更新的数据',
'data' => null
]);
}
// 更新数据库
$result = Db::name('distribution_channel')
->where(['id' => $id, 'companyId' => $companyId])
->update($updateData);
if ($result === false) {
return json([
'code' => 500,
'success' => false,
'msg' => '更新渠道失败',
'data' => null
]);
}
// 获取更新后的数据
$updatedChannel = Db::name('distribution_channel')
->where('id', $id)
->find();
// 格式化返回数据
$resultData = [
'id' => (int)$updatedChannel['id'],
'name' => $updatedChannel['name'],
'code' => $updatedChannel['code'],
'phone' => !empty($updatedChannel['phone']) ? $updatedChannel['phone'] : null,
'wechatId' => !empty($updatedChannel['wechatId']) ? $updatedChannel['wechatId'] : null,
2025-12-29 15:12:57 +08:00
'companyId' => (int)($updatedChannel['companyId'] ?? 0),
'userId' => (int)($updatedChannel['userId'] ?? 0),
2025-12-17 16:20:46 +08:00
'createType' => $updatedChannel['createType'],
'status' => $updatedChannel['status'],
'totalCustomers' => (int)$updatedChannel['totalCustomers'],
'todayCustomers' => (int)$updatedChannel['todayCustomers'],
'totalFriends' => (int)$updatedChannel['totalFriends'],
'todayFriends' => (int)$updatedChannel['todayFriends'],
'withdrawableAmount' => round(($updatedChannel['withdrawableAmount'] ?? 0) / 100, 2), // 分转元保留2位小数
'createTime' => !empty($updatedChannel['createTime']) ? date('Y-m-d H:i:s', $updatedChannel['createTime']) : date('Y-m-d H:i:s'),
];
return json([
'code' => 200,
'success' => true,
'msg' => '更新成功',
'data' => $resultData
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '更新渠道失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 删除渠道(软删除)
* @return \think\response\Json
*/
public function delete()
{
try {
// 获取参数
$id = $this->request->param('id', 0);
$companyId = $this->getUserInfo('companyId');
// 参数验证
if (empty($id)) {
return json([
'code' => 400,
'success' => false,
'msg' => '渠道ID不能为空',
'data' => null
]);
}
// 检查渠道是否存在且属于当前公司
$channel = Db::name('distribution_channel')
->where(['id' => $id, 'companyId' => $companyId, 'deleteTime' => 0])
->find();
if (!$channel) {
return json([
'code' => 404,
'success' => false,
'msg' => '渠道不存在或没有权限',
'data' => null
]);
}
// 软删除
$result = Db::name('distribution_channel')
->where(['id' => $id, 'companyId' => $companyId])
->update([
'deleteTime' => time(),
'updateTime' => time()
]);
if ($result === false) {
return json([
'code' => 500,
'success' => false,
'msg' => '删除渠道失败',
'data' => null
]);
}
return json([
'code' => 200,
'success' => true,
'msg' => '删除成功',
'data' => null
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '删除渠道失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 禁用/启用渠道
* @return \think\response\Json
*/
public function toggleStatus()
{
try {
// 获取参数
$id = $this->request->param('id', 0);
$status = $this->request->param('status', ''); // enabled 或 disabled
$companyId = $this->getUserInfo('companyId');
// 参数验证
if (empty($id)) {
return json([
'code' => 400,
'success' => false,
'msg' => '渠道ID不能为空',
'data' => null
]);
}
if (!in_array($status, [DistributionChannel::STATUS_ENABLED, DistributionChannel::STATUS_DISABLED])) {
return json([
'code' => 400,
'success' => false,
'msg' => '状态参数错误,必须为 enabled 或 disabled',
'data' => null
]);
}
// 检查渠道是否存在且属于当前公司
$channel = Db::name('distribution_channel')
->where(['id' => $id, 'companyId' => $companyId, 'deleteTime' => 0])
->find();
if (!$channel) {
return json([
'code' => 404,
'success' => false,
'msg' => '渠道不存在或没有权限',
'data' => null
]);
}
// 如果状态相同,直接返回成功
if ($channel['status'] === $status) {
$msg = $status === DistributionChannel::STATUS_ENABLED ? '渠道已启用' : '渠道已禁用';
return json([
'code' => 200,
'success' => true,
'msg' => $msg,
'data' => null
]);
}
// 更新状态
$result = Db::name('distribution_channel')
->where(['id' => $id, 'companyId' => $companyId])
->update([
'status' => $status,
'updateTime' => time()
]);
if ($result === false) {
return json([
'code' => 500,
'success' => false,
'msg' => '更新状态失败',
'data' => null
]);
}
$msg = $status === DistributionChannel::STATUS_ENABLED ? '启用成功' : '禁用成功';
return json([
'code' => 200,
'success' => true,
'msg' => $msg,
'data' => [
'id' => (int)$id,
'status' => $status
]
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '更新状态失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 获取渠道统计数据
* @return \think\response\Json
*/
public function statistics()
{
try {
$companyId = $this->getUserInfo('companyId');
// 获取今日开始和结束时间戳
$todayStart = strtotime(date('Y-m-d 00:00:00'));
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
// 构建基础查询条件
$baseWhere = [
['companyId', '=', $companyId],
['deleteTime', '=', 0]
];
2025-12-29 15:12:57 +08:00
// 如果不是管理员,只能查看自己创建的数据
if (!$this->getUserInfo('isAdmin')) {
$baseWhere[] = ['userId', '=', $this->getUserInfo('id')];
}
2025-12-17 16:20:46 +08:00
// 1. 总渠道数
$totalChannels = Db::name('distribution_channel')
->where($baseWhere)
->count();
// 2. 今日新增渠道数
$todayChannels = Db::name('distribution_channel')
->where($baseWhere)
->where('createTime', 'between', [$todayStart, $todayEnd])
->count();
// 3. 统计所有渠道的获客数和加好友数(使用聚合函数)
$statistics = Db::name('distribution_channel')
->where($baseWhere)
->field([
'SUM(totalCustomers) as totalCustomers',
'SUM(todayCustomers) as todayCustomers',
'SUM(totalFriends) as totalFriends',
'SUM(todayFriends) as todayFriends'
])
->find();
// 格式化统计数据
$data = [
'totalChannels' => (int)$totalChannels,
'todayChannels' => (int)$todayChannels,
'totalCustomers' => (int)($statistics['totalCustomers'] ?? 0),
'todayCustomers' => (int)($statistics['todayCustomers'] ?? 0),
'totalFriends' => (int)($statistics['totalFriends'] ?? 0),
'todayFriends' => (int)($statistics['todayFriends'] ?? 0),
];
return json([
'code' => 200,
'msg' => '获取成功',
'data' => $data
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'msg' => '获取统计数据失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 获取渠道收益统计(全局统计)
* @return \think\response\Json
*/
public function revenueStatistics()
{
try {
$companyId = $this->getUserInfo('companyId');
// 构建基础查询条件
$baseWhere = [
['companyId', '=', $companyId]
];
2025-12-29 15:12:57 +08:00
// 如果不是管理员,只能查看自己创建的提现申请
if (!$this->getUserInfo('isAdmin')) {
$baseWhere[] = ['userId', '=', $this->getUserInfo('id')];
}
2025-12-17 16:20:46 +08:00
// 1. 总支出所有已打款的提现申请金额总和状态为paid
$totalExpenditure = Db::name('distribution_withdrawal')
->where($baseWhere)
->where('status', DistributionWithdrawal::STATUS_PAID)
->sum('amount');
$totalExpenditure = intval($totalExpenditure ?? 0);
// 2. 已提现所有已打款的提现申请金额总和状态为paid
$withdrawn = $totalExpenditure; // 已提现 = 总支出
// 3. 待审核所有待审核的提现申请金额总和状态为pending
$pendingReview = Db::name('distribution_withdrawal')
->where($baseWhere)
->where('status', DistributionWithdrawal::STATUS_PENDING)
->sum('amount');
$pendingReview = intval($pendingReview ?? 0);
// 格式化返回数据(分转元)
$data = [
'totalExpenditure' => round($totalExpenditure / 100, 2), // 总支出(元)
'withdrawn' => round($withdrawn / 100, 2), // 已提现(元)
'pendingReview' => round($pendingReview / 100, 2), // 待审核(元)
];
return json([
'code' => 200,
'success' => true,
'msg' => '获取成功',
'data' => $data
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '获取收益统计失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 获取渠道收益明细列表(多个渠道的统计)
* @return \think\response\Json
*/
public function revenueDetail()
{
try {
// 获取参数
$page = $this->request->param('page', 1);
$limit = $this->request->param('limit', 20);
$keyword = $this->request->param('keyword', '');
$companyId = $this->getUserInfo('companyId');
// 参数验证
$page = max(1, intval($page));
$limit = max(1, min(100, intval($limit))); // 限制最大100
// 构建查询条件
$where = [];
$where[] = ['companyId', '=', $companyId];
$where[] = ['deleteTime', '=', 0];
2025-12-29 15:12:57 +08:00
// 如果不是管理员,只能查看自己创建的数据
if (!$this->getUserInfo('isAdmin')) {
$where[] = ['userId', '=', $this->getUserInfo('id')];
}
2025-12-17 16:20:46 +08:00
// 关键词搜索(模糊匹配 name、code
if (!empty($keyword)) {
$keyword = trim($keyword);
$where[] = ['name|code', 'like', '%' . $keyword . '%'];
}
// 查询总数
$total = Db::name('distribution_channel')
->where($where)
->count();
// 查询渠道列表(按创建时间倒序)
$channels = Db::name('distribution_channel')
->where($where)
->order('createTime DESC')
->page($page, $limit)
->select();
// 批量获取所有渠道的提现统计数据(提高性能)
$channelIds = array_column($channels, 'id');
$withdrawalStats = [];
if (!empty($channelIds)) {
2025-12-29 15:12:57 +08:00
// 构建提现查询条件
$withdrawalWhere = [
['companyId', '=', $companyId],
['channelId', 'in', $channelIds]
2025-12-29 15:12:57 +08:00
];
// 如果不是管理员,只能查看自己创建的提现申请
if (!$this->getUserInfo('isAdmin')) {
$withdrawalWhere[] = ['userId', '=', $this->getUserInfo('id')];
}
2025-12-17 16:20:46 +08:00
// 按渠道ID和状态分组统计提现金额
$stats = Db::name('distribution_withdrawal')
2025-12-29 15:12:57 +08:00
->where($withdrawalWhere)
2025-12-17 16:20:46 +08:00
->field([
'channelId',
'status',
'SUM(amount) as totalAmount'
])
->group('channelId, status')
->select();
// 组织统计数据
foreach ($stats as $stat) {
$cid = $stat['channelId'];
if (!isset($withdrawalStats[$cid])) {
$withdrawalStats[$cid] = [
2025-12-29 18:00:46 +08:00
'totalRevenue' => 0, // 总收益(不包括驳回的)
2025-12-17 16:20:46 +08:00
'withdrawn' => 0, // 已打款paid
'pendingReview' => 0 // 待审核pending
];
}
$amount = intval($stat['totalAmount'] ?? 0);
2025-12-29 18:00:46 +08:00
$status = $stat['status'];
// totalRevenue 不包括驳回rejected状态的金额
if ($status !== DistributionWithdrawal::STATUS_REJECTED) {
$withdrawalStats[$cid]['totalRevenue'] += $amount;
2025-12-29 18:00:46 +08:00
}
if ($status === DistributionWithdrawal::STATUS_PAID) {
2025-12-17 16:20:46 +08:00
$withdrawalStats[$cid]['withdrawn'] += $amount;
2025-12-29 18:00:46 +08:00
} elseif ($status === DistributionWithdrawal::STATUS_PENDING) {
2025-12-17 16:20:46 +08:00
$withdrawalStats[$cid]['pendingReview'] += $amount;
}
}
}
// 格式化数据
$formattedList = [];
foreach ($channels as $channel) {
$channelId = $channel['id'];
$stats = $withdrawalStats[$channelId] ?? [
'totalRevenue' => 0,
'withdrawn' => 0,
'pendingReview' => 0
];
// 可提现金额渠道的withdrawableAmount
$withdrawableAmount = intval($channel['withdrawableAmount'] ?? 0);
$formattedItem = [
'channelId' => (string)$channelId,
'channelName' => $channel['name'] ?? '',
'channelCode' => $channel['code'] ?? '',
'totalRevenue' => round($stats['totalRevenue'] / 100, 2), // 总收益(元)
'withdrawable' => round($withdrawableAmount / 100, 2), // 可提现(元)
'withdrawn' => round($stats['withdrawn'] / 100, 2), // 已提现(元)
'pendingReview' => round($stats['pendingReview'] / 100, 2), // 待审核(元)
];
$formattedList[] = $formattedItem;
}
// 返回结果
return json([
'code' => 200,
'success' => true,
'msg' => '获取成功',
'data' => [
'list' => $formattedList,
'total' => (int)$total
]
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '获取收益明细失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 生成渠道二维码H5或小程序码
* @return \think\response\Json
*/
public function generateQrCode()
{
try {
// 获取参数
$type = $this->request->param('type', 'h5'); // h5 或 miniprogram
$companyId = $this->getUserInfo('companyId');
2025-12-29 15:25:04 +08:00
$userId = $this->getUserInfo('id');
2025-12-17 16:20:46 +08:00
// 参数验证
if (!in_array($type, ['h5', 'miniprogram'])) {
return json([
'code' => 400,
'success' => false,
'msg' => '类型参数错误,必须为 h5 或 miniprogram',
'data' => null
]);
}
2025-12-29 15:25:04 +08:00
// 生成临时token包含公司ID和用户ID有效期24小时
2025-12-17 16:20:46 +08:00
// 用户扫码后需要自己填写所有信息
$tokenData = [
'companyId' => $companyId,
2025-12-29 15:25:04 +08:00
'userId' => $userId,
2025-12-17 16:20:46 +08:00
'expireTime' => time() + 86400 // 24小时后过期
];
$token = base64_encode(json_encode($tokenData));
// 如果是小程序码提前计算scene并存储映射关系到数据库
if ($type === 'miniprogram') {
$scene = substr(md5($token), 0, 32);
// 使用数据库存储映射关系(更可靠)
try {
Db::name('distribution_channel_scene_token')->insert([
'scene' => $scene,
'token' => $token,
'companyId' => $companyId,
'expireTime' => time() + 86400,
'createTime' => time()
]);
} catch (\Exception $e) {
// 如果表不存在,尝试创建表
$this->createSceneTokenTable();
// 重试一次
try {
Db::name('distribution_channel_scene_token')->insert([
'scene' => $scene,
'token' => $token,
'companyId' => $companyId,
'expireTime' => time() + 86400,
'createTime' => time()
]);
} catch (\Exception $e2) {
2025-12-20 11:17:30 +08:00
// 静默失败,不影响主流程
}
}
// 同时存储到缓存(双重保险)
$sceneCacheKey = 'channel_register_scene_' . $scene;
Cache::set($sceneCacheKey, $token, 86400);
}
2025-12-17 16:20:46 +08:00
if ($type === 'h5') {
// 生成H5二维码
// 获取H5页面URL需要根据实际项目配置
2025-12-18 15:36:25 +08:00
$h5BaseUrl = Env::get('rpc.H5_FORM_URL', 'https://h5.ckb.quwanzhi.com/#');
2025-12-17 16:20:46 +08:00
// 确保URL格式正确去除末尾斜杠
$h5BaseUrl = rtrim($h5BaseUrl, '/');
$h5Url = $h5BaseUrl . '/pages/channel/add?token=' . urlencode($token);
// 生成二维码
$qrCode = new QrCode($h5Url);
$qrCode->setSize(300);
$qrCode->setMargin(10);
$qrCode->setWriterByName('png');
$qrCode->setEncoding('UTF-8');
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
// 转换为base64
$qrCodeBase64 = 'data:image/png;base64,' . base64_encode($qrCode->writeString());
return json([
'code' => 200,
'success' => true,
'msg' => '生成H5二维码成功',
'data' => [
'type' => 'h5',
'qrCode' => $qrCodeBase64,
'url' => $h5Url
]
]);
} else {
// 生成小程序码
try {
// 从环境变量获取小程序配置
$miniProgramConfig = [
'app_id' => Env::get('weChat.appidMiniApp', 'wx789850448e26c91d'),
'secret' => Env::get('weChat.secretMiniApp', 'd18f75b3a3623cb40da05648b08365a1'),
'response_type' => 'array'
];
$app = Factory::miniProgram($miniProgramConfig);
// scene参数长度限制为32位使用token的MD5值
$scene = substr(md5($token), 0, 32);
// 再次确保映射关系已存储到数据库和缓存(双重保险)
$sceneCacheKey = 'channel_register_scene_' . $scene;
Cache::set($sceneCacheKey, $token, 86400);
2025-12-17 16:20:46 +08:00
// 调用接口生成小程序码
2025-12-17 17:58:08 +08:00
// 注意page 必须是小程序里已经存在且发布过的页面路径
// 根据你的前端约定,改为和 H5 一致的添加渠道页面
2025-12-17 16:20:46 +08:00
$response = $app->app_code->getUnlimit($scene, [
2025-12-17 17:58:08 +08:00
'page' => 'pages/channel/add', // 请确保小程序里存在该页面
'width' => 430, // 二维码的宽度
2025-12-17 16:20:46 +08:00
]);
2025-12-17 17:04:57 +08:00
// 成功时返回的是 StreamResponse失败时通常返回数组包含 errcode/errmsg
if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
2025-12-17 16:20:46 +08:00
$img = $response->getBody()->getContents();
$imgBase64 = 'data:image/png;base64,' . base64_encode($img);
return json([
'code' => 200,
'success' => true,
'msg' => '生成小程序码成功',
'data' => [
'type' => 'miniprogram',
'qrCode' => $imgBase64,
'scene' => $scene, // 返回scene供小程序端使用
'token' => $token // 返回token小程序端可以通过scene查询
]
]);
2025-12-17 17:04:57 +08:00
}
// 如果不是流响应,而是数组(错误信息),则解析错误返回
if (is_array($response) && isset($response['errcode']) && $response['errcode'] != 0) {
$errMsg = isset($response['errmsg']) ? $response['errmsg'] : '微信接口返回错误';
2025-12-17 16:20:46 +08:00
return json([
'code' => 500,
'success' => false,
2025-12-17 17:04:57 +08:00
'msg' => '生成小程序码失败:' . $errMsg,
'data' => $response
2025-12-17 16:20:46 +08:00
]);
}
2025-12-17 17:04:57 +08:00
// 其他未知格式
return json([
'code' => 500,
'success' => false,
'msg' => '生成小程序码失败:响应格式错误',
'data' => $response
]);
2025-12-17 16:20:46 +08:00
} catch (\Exception $e) {
return json([
'code' => 500,
'success' => false,
'msg' => '生成小程序码失败:' . $e->getMessage(),
'data' => null
]);
}
}
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '生成二维码失败:' . $e->getMessage(),
'data' => null
]);
}
}
2025-12-18 15:36:25 +08:00
/**
* 生成渠道登录二维码H5或小程序码
* 通用登录二维码,不绑定特定渠道,用户扫码后输入手机号和密码登录
* @return \think\response\Json
*/
public function generateLoginQrCode()
{
try {
// 获取参数
$type = $this->request->param('type', 'h5'); // h5 或 miniprogram
$companyId = $this->getUserInfo('companyId');
// 参数验证
if (!in_array($type, ['h5', 'miniprogram'])) {
return json([
'code' => 400,
'success' => false,
'msg' => '类型参数错误,必须为 h5 或 miniprogram',
'data' => null
]);
}
if ($type === 'h5') {
// 生成H5登录二维码
$h5BaseUrl = Env::get('rpc.H5_FORM_URL', 'https://h5.ckb.quwanzhi.com/#');
$h5BaseUrl = rtrim($h5BaseUrl, '/');
// H5登录页面路径需要根据实际项目调整
2025-12-18 16:23:01 +08:00
// 登录入口只需要携带 companyId 参数
$h5Url = $h5BaseUrl . '/pages/channel/login?companyId=' . urlencode($companyId);
2025-12-18 15:36:25 +08:00
// 生成二维码
$qrCode = new QrCode($h5Url);
$qrCode->setSize(300);
$qrCode->setMargin(10);
$qrCode->setWriterByName('png');
$qrCode->setEncoding('UTF-8');
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
// 转换为base64
$qrCodeBase64 = 'data:image/png;base64,' . base64_encode($qrCode->writeString());
return json([
'code' => 200,
'success' => true,
'msg' => '生成H5登录二维码成功',
'data' => [
'type' => 'h5',
'qrCode' => $qrCodeBase64,
'url' => $h5Url
]
]);
} else {
// 生成小程序登录码
try {
// 从环境变量获取小程序配置
$miniProgramConfig = [
'app_id' => Env::get('weChat.appidMiniApp', 'wx789850448e26c91d'),
'secret' => Env::get('weChat.secretMiniApp', 'd18f75b3a3623cb40da05648b08365a1'),
'response_type' => 'array'
];
$app = Factory::miniProgram($miniProgramConfig);
2025-12-18 16:23:01 +08:00
// scene 参数直接使用 companyId字符串并确保长度不超过32
$scene = (string)$companyId;
if (strlen($scene) > 32) {
$scene = substr($scene, 0, 32);
}
2025-12-18 15:36:25 +08:00
// 调用接口生成小程序码
// 小程序登录页面路径,需要根据实际项目调整
$response = $app->app_code->getUnlimit($scene, [
'page' => 'pages/channel/login', // 请确保小程序里存在该页面
'width' => 430,
]);
// 成功时返回的是 StreamResponse
if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
$img = $response->getBody()->getContents();
$imgBase64 = 'data:image/png;base64,' . base64_encode($img);
return json([
'code' => 200,
'success' => true,
'msg' => '生成小程序登录码成功',
'data' => [
'type' => 'miniprogram',
'qrCode' => $imgBase64,
2025-12-18 16:23:01 +08:00
'scene' => $scene
2025-12-18 15:36:25 +08:00
]
]);
}
// 如果不是流响应,而是数组(错误信息),则解析错误返回
if (is_array($response) && isset($response['errcode']) && $response['errcode'] != 0) {
$errMsg = isset($response['errmsg']) ? $response['errmsg'] : '微信接口返回错误';
return json([
'code' => 500,
'success' => false,
'msg' => '生成小程序登录码失败:' . $errMsg,
'data' => $response
]);
}
// 其他未知格式
return json([
'code' => 500,
'success' => false,
'msg' => '生成小程序登录码失败:响应格式错误',
'data' => $response
]);
} catch (\Exception $e) {
return json([
'code' => 500,
'success' => false,
'msg' => '生成小程序登录码失败:' . $e->getMessage(),
'data' => null
]);
}
}
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '生成登录二维码失败:' . $e->getMessage(),
'data' => null
]);
}
}
2025-12-17 16:20:46 +08:00
/**
* 扫码提交渠道信息H5和小程序共用
* GET请求返回预填信息
* POST请求提交渠道信息
* @return \think\response\Json
*/
public function registerByQrCode()
{
try {
// 获取参数
$token = $this->request->param('token', '');
// 参数验证
if (empty($token)) {
return json([
'code' => 400,
'success' => false,
'msg' => 'token不能为空',
'data' => null
]);
}
// 判断传入的是scene32位MD5还是tokenbase64编码
// 如果是32位MD5字符串则从数据库或缓存中查找对应的token
if (strlen($token) == 32 && preg_match('/^[a-f0-9]{32}$/i', $token)) {
// 这是scene先从数据库查找对应的token
$sceneData = Db::name('distribution_channel_scene_token')
->where('scene', $token)
->where('expireTime', '>', time())
->find();
if ($sceneData && !empty($sceneData['token'])) {
$realToken = $sceneData['token'];
} else {
// 如果数据库中没有,尝试从缓存获取
$sceneCacheKey = 'channel_register_scene_' . $token;
$realToken = Cache::get($sceneCacheKey);
if (empty($realToken)) {
return json([
'code' => 400,
'success' => false,
'msg' => '二维码已过期,请重新生成',
'data' => null
]);
}
}
$token = $realToken;
}
2025-12-17 16:20:46 +08:00
// 解析token
$tokenData = json_decode(base64_decode($token), true);
if (!$tokenData || !isset($tokenData['companyId'])) {
return json([
'code' => 400,
'success' => false,
'msg' => 'token无效',
'data' => null
]);
}
// 检查token是否过期
if (isset($tokenData['expireTime']) && $tokenData['expireTime'] < time()) {
return json([
'code' => 400,
'success' => false,
'msg' => '二维码已过期,请重新生成',
'data' => null
]);
}
$companyId = $tokenData['companyId'];
2025-12-29 15:25:04 +08:00
$userId = isset($tokenData['userId']) ? $tokenData['userId'] : 0; // 兼容旧token如果没有userId则默认为0
2025-12-17 16:20:46 +08:00
// GET请求返回token验证成功信息前端可以显示表单
if ($this->request->isGet()) {
return json([
'code' => 200,
'success' => true,
'msg' => 'token验证成功',
'data' => [
'valid' => true
]
]);
}
// POST请求提交渠道信息所有信息都需要用户填写
$name = $this->request->param('name', '');
$phone = $this->request->param('phone', '');
$wechatId = $this->request->param('wechatId', '');
$remarks = $this->request->param('remarks', '');
// 参数验证
if (empty($name)) {
return json([
'code' => 400,
'success' => false,
'msg' => '渠道名称不能为空',
'data' => null
]);
}
// 验证渠道名称长度
if (mb_strlen($name) > 50) {
return json([
'code' => 400,
'success' => false,
'msg' => '渠道名称长度不能超过50个字符',
'data' => null
]);
}
// 验证手机号格式(如果提供)
if (!empty($phone)) {
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
return json([
'code' => 400,
'success' => false,
'msg' => '手机号格式不正确请输入11位数字且以1开头',
'data' => null
]);
}
// 检查手机号是否已存在(排除已删除的渠道)
$existChannel = Db::name('distribution_channel')
->where([
['companyId', '=', $companyId],
['phone', '=', $phone],
['deleteTime', '=', 0]
])
->find();
if ($existChannel) {
return json([
'code' => 400,
'success' => false,
'msg' => '手机号已被使用',
'data' => null
]);
}
}
// 验证微信号长度(如果提供)
if (!empty($wechatId) && mb_strlen($wechatId) > 50) {
return json([
'code' => 400,
'success' => false,
'msg' => '微信号长度不能超过50个字符',
'data' => null
]);
}
// 验证备注长度(如果提供)
if (!empty($remarks) && mb_strlen($remarks) > 200) {
return json([
'code' => 400,
'success' => false,
'msg' => '备注信息长度不能超过200个字符',
'data' => null
]);
}
// 生成渠道编码
$code = DistributionChannel::generateChannelCode();
2025-12-29 15:25:04 +08:00
// 准备插入数据从token中获取userId记录是哪个用户生成的二维码
2025-12-17 16:20:46 +08:00
$data = [
'companyId' => $companyId,
2025-12-29 15:25:04 +08:00
'userId' => $userId, // 从token中获取userId记录生成二维码的用户
2025-12-17 16:20:46 +08:00
'name' => $name,
'code' => $code,
'phone' => $phone ?: '',
'password' => md5('123456'), // 默认密码123456MD5加密
'wechatId' => $wechatId ?: '',
'remarks' => $remarks ?: '',
'createType' => DistributionChannel::CREATE_TYPE_AUTO, // 扫码创建
'status' => DistributionChannel::STATUS_ENABLED,
'totalCustomers' => 0,
'todayCustomers' => 0,
'totalFriends' => 0,
'todayFriends' => 0,
'withdrawableAmount' => 0, // 以分为单位存储
'createTime' => time(),
'updateTime' => time(),
];
// 插入数据库
$channelId = Db::name('distribution_channel')->insertGetId($data);
if (!$channelId) {
return json([
'code' => 500,
'success' => false,
'msg' => '创建渠道失败',
'data' => null
]);
}
// 获取创建的数据
$channel = Db::name('distribution_channel')->where('id', $channelId)->find();
// 格式化返回数据
$result = [
'id' => (string)$channel['id'],
'name' => $channel['name'],
'code' => $channel['code'],
'phone' => $channel['phone'] ?: '',
'wechatId' => $channel['wechatId'] ?: '',
2025-12-20 11:17:30 +08:00
'companyId' => (int)$companyId, // 返回companyId方便小程序自动跳转
2025-12-29 15:12:57 +08:00
'userId' => (int)($channel['userId'] ?? 0),
2025-12-17 16:20:46 +08:00
'createType' => $channel['createType'],
'status' => $channel['status'],
'totalCustomers' => (int)$channel['totalCustomers'],
'todayCustomers' => (int)$channel['todayCustomers'],
'totalFriends' => (int)$channel['totalFriends'],
'todayFriends' => (int)$channel['todayFriends'],
'withdrawableAmount' => round(($channel['withdrawableAmount'] ?? 0) / 100, 2), // 分转元保留2位小数
'createTime' => !empty($channel['createTime']) ? date('Y-m-d H:i:s', $channel['createTime']) : date('Y-m-d H:i:s'),
];
return json([
'code' => 200,
'success' => true,
'msg' => '渠道注册成功',
'data' => $result
]);
} catch (Exception $e) {
return json([
'code' => $e->getCode() ?: 500,
'success' => false,
'msg' => '渠道注册失败:' . $e->getMessage(),
'data' => null
]);
}
}
/**
* 创建scene和token映射表如果不存在
*/
protected function createSceneTokenTable()
{
try {
$sql = "CREATE TABLE IF NOT EXISTS `ck_distribution_channel_scene_token` (
`id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`scene` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '小程序scene参数MD5值',
`token` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '原始tokenbase64编码',
`companyId` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '公司ID',
`expireTime` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '过期时间戳',
`createTime` int(11) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建时间',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE KEY `uk_scene` (`scene`) USING BTREE,
INDEX `idx_companyId` (`companyId`) USING BTREE,
INDEX `idx_expireTime` (`expireTime`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分销渠道小程序码scene和token映射表';";
Db::execute($sql);
} catch (\Exception $e) {
2025-12-20 11:17:30 +08:00
// 静默失败
}
}
2025-12-17 16:20:46 +08:00
}