新版数智员工
This commit is contained in:
252
application/store/controller/AgentController.php
Normal file
252
application/store/controller/AgentController.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* Agent功能模块控制器 - V2版本
|
||||
*/
|
||||
class AgentController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取Agent模块列表
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getModules()
|
||||
{
|
||||
try {
|
||||
// 从BaseController获取设备ID(通过userInfo自动获取)
|
||||
$deviceId = $this->device['id'] ?? 0;
|
||||
|
||||
if (!$deviceId) {
|
||||
return json(['code' => 400, 'msg' => '设备不存在,请先绑定设备']);
|
||||
}
|
||||
|
||||
// 获取所有可用的模块定义
|
||||
$modules = $this->getModuleDefinitions();
|
||||
|
||||
// 从旧表获取配置
|
||||
$taskConfig = Db::name('device_taskconf')
|
||||
->where('deviceId', $deviceId)
|
||||
->where('deleteTime', 0)
|
||||
->find();
|
||||
|
||||
// 如果没有配置,返回默认关闭状态
|
||||
if (!$taskConfig) {
|
||||
$taskConfig = [
|
||||
'autoLike' => 0,
|
||||
'momentsSync' => 0,
|
||||
'autoCustomerDev' => 0,
|
||||
'groupMessageDeliver' => 0,
|
||||
'autoGroup' => 0
|
||||
];
|
||||
}
|
||||
|
||||
// 字段映射关系
|
||||
$fieldMap = [
|
||||
'auto_like' => 'autoLike',
|
||||
'moments_sync' => 'momentsSync',
|
||||
'auto_customer_dev' => 'autoCustomerDev',
|
||||
'group_message_deliver' => 'groupMessageDeliver',
|
||||
'auto_group' => 'autoGroup'
|
||||
];
|
||||
|
||||
// 组装返回数据
|
||||
$result = [];
|
||||
foreach ($modules as $module) {
|
||||
$moduleCode = $module['code'];
|
||||
$fieldName = $fieldMap[$moduleCode] ?? null;
|
||||
|
||||
$moduleData = array_merge($module, [
|
||||
'userEnabled' => $fieldName && isset($taskConfig[$fieldName]) ? (bool)$taskConfig[$fieldName] : false
|
||||
]);
|
||||
|
||||
$result[] = $moduleData;
|
||||
}
|
||||
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => 'success',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取Agent模块列表失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新模块状态
|
||||
* @param string $moduleCode 模块代码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateModuleStatus($moduleCode)
|
||||
{
|
||||
try {
|
||||
// 从BaseController获取设备ID(通过userInfo自动获取)
|
||||
$deviceId = $this->device['id'] ?? 0;
|
||||
$isEnabled = (bool)$this->request->param('isEnabled', false);
|
||||
|
||||
if (!$deviceId) {
|
||||
return json(['code' => 400, 'msg' => '设备不存在,请先绑定设备']);
|
||||
}
|
||||
|
||||
// 验证模块代码是否有效
|
||||
$validModules = array_column($this->getModuleDefinitions(), 'code');
|
||||
if (!in_array($moduleCode, $validModules)) {
|
||||
return json(['code' => 400, 'msg' => '无效的模块代码']);
|
||||
}
|
||||
|
||||
// 字段映射关系
|
||||
$fieldMap = [
|
||||
'auto_like' => 'autoLike',
|
||||
'moments_sync' => 'momentsSync',
|
||||
'auto_customer_dev' => 'autoCustomerDev',
|
||||
'group_message_deliver' => 'groupMessageDeliver',
|
||||
'auto_group' => 'autoGroup'
|
||||
];
|
||||
|
||||
$fieldName = $fieldMap[$moduleCode] ?? null;
|
||||
if (!$fieldName) {
|
||||
return json(['code' => 400, 'msg' => '不支持的模块']);
|
||||
}
|
||||
|
||||
// 查询现有配置
|
||||
$taskConfig = Db::name('device_taskconf')
|
||||
->where('deviceId', $deviceId)
|
||||
->where('deleteTime', 0)
|
||||
->find();
|
||||
|
||||
$now = time();
|
||||
|
||||
if ($taskConfig) {
|
||||
// 更新现有配置
|
||||
Db::name('device_taskconf')
|
||||
->where('id', $taskConfig['id'])
|
||||
->update([
|
||||
$fieldName => $isEnabled ? 1 : 0,
|
||||
'updateTime' => $now
|
||||
]);
|
||||
|
||||
// 清除设备缓存
|
||||
$this->clearDeviceCache();
|
||||
} else {
|
||||
// 创建新配置
|
||||
$insertData = [
|
||||
'deviceId' => $deviceId,
|
||||
'autoLike' => 0,
|
||||
'momentsSync' => 0,
|
||||
'autoCustomerDev' => 0,
|
||||
'groupMessageDeliver' => 0,
|
||||
'autoGroup' => 0,
|
||||
'companyId' => $this->device['companyId'] ?? $this->userInfo['companyId'] ?? 0,
|
||||
'createTime' => $now,
|
||||
'updateTime' => $now
|
||||
];
|
||||
$insertData[$fieldName] = $isEnabled ? 1 : 0;
|
||||
|
||||
Db::name('device_taskconf')->insert($insertData);
|
||||
|
||||
// 清除设备缓存
|
||||
$this->clearDeviceCache();
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '操作成功',
|
||||
'data' => [
|
||||
'moduleCode' => $moduleCode,
|
||||
'isEnabled' => $isEnabled
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新模块状态失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取模块定义
|
||||
* @return array
|
||||
*/
|
||||
protected function getModuleDefinitions()
|
||||
{
|
||||
return [
|
||||
[
|
||||
'code' => 'auto_like',
|
||||
'name' => '自动点赞',
|
||||
'icon' => 'icon-dianzan',
|
||||
'color' => '#ff6699',
|
||||
'description' => '自动为好友朋友圈点赞',
|
||||
'category' => 'social',
|
||||
'sort' => 1,
|
||||
'isEnabled' => true,
|
||||
'needAuth' => true
|
||||
],
|
||||
[
|
||||
'code' => 'moments_sync',
|
||||
'name' => '朋友圈同步',
|
||||
'icon' => 'icon-tupian',
|
||||
'color' => '#9966ff',
|
||||
'description' => '同步好友朋友圈内容',
|
||||
'category' => 'social',
|
||||
'sort' => 2,
|
||||
'isEnabled' => true,
|
||||
'needAuth' => true
|
||||
],
|
||||
[
|
||||
'code' => 'auto_customer_dev',
|
||||
'name' => '自动开发客户',
|
||||
'icon' => 'icon-yonghu',
|
||||
'color' => '#33cc99',
|
||||
'description' => '自动化客户开发流程',
|
||||
'category' => 'customer',
|
||||
'sort' => 3,
|
||||
'isEnabled' => true,
|
||||
'needAuth' => true
|
||||
],
|
||||
[
|
||||
'code' => 'group_message_deliver',
|
||||
'name' => '群消息群发',
|
||||
'icon' => 'icon-xiaoxi',
|
||||
'color' => '#ff9966',
|
||||
'description' => '批量推送消息到微信群',
|
||||
'category' => 'message',
|
||||
'sort' => 4,
|
||||
'isEnabled' => true,
|
||||
'needAuth' => false
|
||||
],
|
||||
[
|
||||
'code' => 'auto_group',
|
||||
'name' => '自动建群',
|
||||
'icon' => 'icon-yonghuqun',
|
||||
'color' => '#6699ff',
|
||||
'description' => '自动创建和管理微信群',
|
||||
'category' => 'group',
|
||||
'sort' => 5,
|
||||
'isEnabled' => true,
|
||||
'needAuth' => true
|
||||
],
|
||||
[
|
||||
'code' => 'video_distribute',
|
||||
'name' => '视频分发',
|
||||
'icon' => 'icon-video',
|
||||
'color' => '#ff66cc',
|
||||
'description' => '自动分发视频内容',
|
||||
'category' => 'content',
|
||||
'sort' => 6,
|
||||
'isEnabled' => false,
|
||||
'needAuth' => false
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
412
application/store/controller/AuthController.php
Normal file
412
application/store/controller/AuthController.php
Normal file
@@ -0,0 +1,412 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use app\store\service\SmsService;
|
||||
use app\common\util\JwtUtil;
|
||||
use app\common\service\UserApiKeyService;
|
||||
|
||||
/**
|
||||
* Store模块 - 认证控制器
|
||||
* Class AuthController
|
||||
* @package app\store\controller
|
||||
*/
|
||||
class AuthController extends Controller
|
||||
{
|
||||
/**
|
||||
* 账号密码登录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function passwordLogin()
|
||||
{
|
||||
// 获取参数
|
||||
$account = trim($this->request->param('account', ''));
|
||||
$password = trim($this->request->param('password', ''));
|
||||
$typeId = (int)$this->request->param('typeId', 2); // 类型ID,默认为2
|
||||
$deviceId = trim($this->request->param('deviceId', '')); // 设备ID(可选,仅APP端传递)
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($account) || empty($password)) {
|
||||
return json(['code' => 400, 'msg' => '账号和密码不能为空']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 查找账号(门店端使用 ck_users 表,typeId=2)
|
||||
$accountInfo = Db::name('users')
|
||||
->where(function($query) use ($account) {
|
||||
$query->where('account', $account)
|
||||
->whereOr('phone', $account);
|
||||
})
|
||||
->where('typeId', 2) // 门店端固定为2
|
||||
->where('deleteTime', 0)
|
||||
->find();
|
||||
|
||||
if (empty($accountInfo)) {
|
||||
return json(['code' => 404, 'msg' => '账号不存在']);
|
||||
}
|
||||
|
||||
// 验证密码(支持MD5和本地加密密码)
|
||||
$passwordMd5 = md5($password);
|
||||
$passwordMatch = false;
|
||||
|
||||
if (!empty($accountInfo['passwordMd5']) && $accountInfo['passwordMd5'] === $passwordMd5) {
|
||||
$passwordMatch = true;
|
||||
} elseif (!empty($accountInfo['passwordLocal'])) {
|
||||
// 验证本地加密密码(需要localDecrypt函数)
|
||||
if (function_exists('localDecrypt')) {
|
||||
$decryptedPassword = localDecrypt($accountInfo['passwordLocal']);
|
||||
if ($decryptedPassword === $password) {
|
||||
$passwordMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$passwordMatch) {
|
||||
return json(['code' => 401, 'msg' => '密码错误']);
|
||||
}
|
||||
|
||||
// 如果传了设备ID(APP端),验证设备是否存在
|
||||
if (!empty($deviceId)) {
|
||||
$device = Db::name('device')
|
||||
->where('deviceImei', $deviceId)
|
||||
->where('companyId', $accountInfo['companyId'])
|
||||
->where('deleteTime', 0)
|
||||
->find();
|
||||
|
||||
if (empty($device)) {
|
||||
return json(['code' => 404, 'msg' => '设备不存在或与账号不匹配']);
|
||||
}
|
||||
}
|
||||
|
||||
// 生成JWT令牌(与旧版一致)
|
||||
$token = JwtUtil::createToken($accountInfo, 86400 * 30); // 30天过期
|
||||
$tokenExpired = time() + 86400 * 30;
|
||||
|
||||
// 更新账号最后登录信息(ck_users表没有lastLoginTime和lastLoginIp字段,只更新密码和updateTime)
|
||||
Db::name('users')
|
||||
->where('id', $accountInfo['id'])
|
||||
->update([
|
||||
'passwordMd5' => $passwordMd5,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
// 准备返回的会员信息
|
||||
$memberInfo = [
|
||||
'id' => $accountInfo['id'],
|
||||
'account' => $accountInfo['account'] ?? '',
|
||||
'username' => $accountInfo['username'] ?? '',
|
||||
'phone' => $accountInfo['phone'] ?? '',
|
||||
'avatar' => $accountInfo['avatar'] ?? '',
|
||||
'companyId' => $accountInfo['companyId'] ?? 0,
|
||||
'typeId' => $accountInfo['typeId'] ?? 2,
|
||||
];
|
||||
|
||||
// 记录登录日志
|
||||
$this->recordLoginLog($accountInfo['id'], $deviceId, '账号密码登录成功');
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '登录成功',
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'token_expired' => $tokenExpired,
|
||||
'member' => $memberInfo
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志
|
||||
$this->recordLoginLog(0, $deviceId, '账号密码登录失败:' . $e->getMessage());
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '登录失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 免密登录(基于设备ID)
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function noPasswordLogin()
|
||||
{
|
||||
// 获取设备ID
|
||||
$deviceId = trim($this->request->param('deviceId', ''));
|
||||
|
||||
if (empty($deviceId)) {
|
||||
return json(['code' => 400, 'msg' => '设备ID不能为空']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 根据设备IMEI查找设备信息
|
||||
$device = Db::name('device')
|
||||
->where('deviceImei', $deviceId)
|
||||
->where('deleteTime', 0)
|
||||
->find();
|
||||
|
||||
if (empty($device)) {
|
||||
return json(['code' => 404, 'msg' => '设备不存在或已被删除']);
|
||||
}
|
||||
|
||||
// 检查设备是否在线
|
||||
if ($device['alive'] != 1) {
|
||||
return json(['code' => 403, 'msg' => '设备未在线,请确保设备已连接']);
|
||||
}
|
||||
|
||||
// 获取设备关联的公司ID
|
||||
$companyId = $device['companyId'];
|
||||
|
||||
// 查找公司账号信息(通过device_user关联查找用户)
|
||||
// 门店端使用 ck_users 表,通过 device_user 关联
|
||||
$account = Db::name('users')->alias('u')
|
||||
->join('device_user du', 'u.id = du.userId AND u.companyId = du.companyId')
|
||||
->where([
|
||||
'du.deviceId' => $device['id'],
|
||||
'u.companyId' => $companyId,
|
||||
'u.typeId' => 2, // 门店端固定为2
|
||||
'u.deleteTime' => 0,
|
||||
'du.deleteTime' => 0
|
||||
])
|
||||
->field('u.*')
|
||||
->find();
|
||||
|
||||
if (empty($account)) {
|
||||
return json(['code' => 404, 'msg' => '未找到关联的账号信息,请先绑定设备']);
|
||||
}
|
||||
|
||||
// 生成JWT令牌(与旧版一致)
|
||||
$token = JwtUtil::createToken($account, 86400 * 30); // 30天过期
|
||||
$tokenExpired = time() + 86400 * 30;
|
||||
|
||||
// 更新账号最后登录信息(ck_users表没有lastLoginTime和lastLoginIp字段)
|
||||
Db::name('users')
|
||||
->where('id', $account['id'])
|
||||
->update([
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
// 准备返回的会员信息
|
||||
$memberInfo = [
|
||||
'id' => $account['id'],
|
||||
'account' => $account['account'] ?? '',
|
||||
'username' => $account['username'] ?? '',
|
||||
'phone' => $account['phone'] ?? '',
|
||||
'avatar' => $account['avatar'] ?? '',
|
||||
'companyId' => $companyId,
|
||||
'typeId' => $account['typeId'] ?? 2,
|
||||
];
|
||||
|
||||
// 记录登录日志
|
||||
$this->recordLoginLog($account['id'], $deviceId, '免密登录成功');
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '登录成功',
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'token_expired' => $tokenExpired,
|
||||
'member' => $memberInfo
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志
|
||||
$this->recordLoginLog(0, $deviceId, '免密登录失败:' . $e->getMessage());
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '登录失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function sendVerificationCode()
|
||||
{
|
||||
// 获取参数
|
||||
$mobile = trim($this->request->param('mobile', ''));
|
||||
$type = trim($this->request->param('type', 'login')); // login/register/reset
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($mobile)) {
|
||||
return json(['code' => 400, 'msg' => '手机号不能为空']);
|
||||
}
|
||||
|
||||
try {
|
||||
$smsService = new SmsService();
|
||||
$result = $smsService->sendVerificationCode($mobile, $type);
|
||||
|
||||
if ($result['success']) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => $result['message'],
|
||||
'data' => $result['data'] ?? []
|
||||
]);
|
||||
} else {
|
||||
return json([
|
||||
'code' => 400,
|
||||
'msg' => $result['message']
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '发送失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号验证码登录
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function mobileLogin()
|
||||
{
|
||||
// 获取参数
|
||||
$mobile = trim($this->request->param('mobile', ''));
|
||||
$code = trim($this->request->param('code', ''));
|
||||
$isEncrypted = $this->request->param('is_encrypted', false);
|
||||
|
||||
// 验证必填参数
|
||||
if (empty($mobile) || empty($code)) {
|
||||
return json(['code' => 400, 'msg' => '手机号和验证码不能为空']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 验证短信验证码
|
||||
$smsService = new SmsService();
|
||||
$verifyResult = $smsService->verifyCode($mobile, $code, 'login');
|
||||
|
||||
if (!$verifyResult['success']) {
|
||||
return json(['code' => 400, 'msg' => $verifyResult['message']]);
|
||||
}
|
||||
|
||||
// 2. 查找或创建账号(根据手机号)
|
||||
// 门店端使用 ck_users 表,typeId=2
|
||||
$account = Db::name('users')
|
||||
->where('phone', $mobile)
|
||||
->where('typeId', 2) // 门店端固定为2
|
||||
->where('deleteTime', 0)
|
||||
->find();
|
||||
|
||||
// 如果账号不存在,自动创建(新用户注册)
|
||||
if (empty($account)) {
|
||||
// 注意:新用户注册需要companyId,这里暂时设为0,实际应该从设备或其他地方获取
|
||||
$accountId = Db::name('users')->insertGetId([
|
||||
'account' => $mobile, // 使用手机号作为账号
|
||||
'username' => '用户' . substr($mobile, -4), // 默认昵称
|
||||
'phone' => $mobile,
|
||||
'passwordMd5' => '', // 手机验证码登录不需要密码
|
||||
'avatar' => 'https://img.icons8.com/color/512/circled-user-male-skin-type-7.png',
|
||||
'isAdmin' => 0,
|
||||
'companyId' => 0, // 新用户默认companyId为0,后续需要绑定设备或公司
|
||||
'typeId' => 2, // 门店端固定为2
|
||||
'status' => 1, // 默认可用
|
||||
'balance' => 0,
|
||||
'tokens' => 0,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time(),
|
||||
'deleteTime' => 0
|
||||
]);
|
||||
|
||||
// 重新查询账号信息
|
||||
$account = Db::name('users')->where('id', $accountId)->find();
|
||||
|
||||
// 新用户自动生成对外 API Key
|
||||
try {
|
||||
UserApiKeyService::bindOrGet((int)$accountId);
|
||||
} catch (\Exception $e) {
|
||||
\think\facade\Log::error('新用户自动生成 apiKey 失败:' . $e->getMessage());
|
||||
}
|
||||
|
||||
// 记录注册日志
|
||||
$this->recordLoginLog($accountId, '', '手机验证码注册成功');
|
||||
}
|
||||
|
||||
// 3. 生成JWT令牌(与旧版一致)
|
||||
$token = JwtUtil::createToken($account, 86400 * 30); // 30天过期
|
||||
$tokenExpired = time() + 86400 * 30;
|
||||
|
||||
// 4. 更新账号最后登录信息(ck_users表没有lastLoginTime和lastLoginIp字段)
|
||||
Db::name('users')
|
||||
->where('id', $account['id'])
|
||||
->update([
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
// 5. 准备返回的用户信息
|
||||
$userInfo = [
|
||||
'id' => $account['id'],
|
||||
'account' => $account['account'] ?? '',
|
||||
'username' => $account['username'] ?? '',
|
||||
'phone' => $mobile,
|
||||
'avatar' => $account['avatar'] ?? '',
|
||||
'companyId' => $account['companyId'] ?? 0,
|
||||
'typeId' => $account['typeId'] ?? 2,
|
||||
];
|
||||
|
||||
// 记录登录日志
|
||||
$this->recordLoginLog($account['id'], '', '手机验证码登录成功');
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '登录成功',
|
||||
'data' => [
|
||||
'token' => $token,
|
||||
'token_expired' => $tokenExpired,
|
||||
'userInfo' => $userInfo
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
// 记录错误日志
|
||||
$this->recordLoginLog(0, '', '手机验证码登录失败:' . $e->getMessage());
|
||||
|
||||
return json([
|
||||
'code' => 500,
|
||||
'msg' => '登录失败:' . $e->getMessage()
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录登录日志
|
||||
* @param int $accountId 账号ID
|
||||
* @param string $deviceId 设备ID
|
||||
* @param string $message 日志信息
|
||||
*/
|
||||
private function recordLoginLog($accountId, $deviceId, $message)
|
||||
{
|
||||
try {
|
||||
// 使用ThinkPHP的日志记录功能,避免表不存在的问题
|
||||
\think\facade\Log::info('Store登录日志', [
|
||||
'accountId' => $accountId,
|
||||
'deviceId' => $deviceId,
|
||||
'action' => 'STORE_LOGIN',
|
||||
'message' => $message,
|
||||
'ip' => $this->request->ip(),
|
||||
'time' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
|
||||
// 如果存在operation_log表,也可以记录到数据库
|
||||
// Db::name('operation_log')->insert([
|
||||
// 'accountId' => $accountId,
|
||||
// 'deviceId' => $deviceId,
|
||||
// 'action' => 'STORE_LOGIN',
|
||||
// 'message' => $message,
|
||||
// 'ip' => $this->request->ip(),
|
||||
// 'createTime' => time()
|
||||
// ]);
|
||||
} catch (\Exception $e) {
|
||||
// 日志记录失败不影响主流程
|
||||
\think\facade\Log::error('登录日志记录失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,13 @@
|
||||
namespace app\store\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\facade\Config;
|
||||
use think\facade\Request;
|
||||
use think\facade\Response;
|
||||
use think\facade\Log;
|
||||
use app\common\controller\Api;
|
||||
use think\Db;
|
||||
use think\facade\Cache;
|
||||
|
||||
/**
|
||||
* 基础控制器
|
||||
* Store模块基础控制器 - V2版本
|
||||
*/
|
||||
class BaseController extends Api
|
||||
class BaseController extends Controller
|
||||
{
|
||||
protected $device = [];
|
||||
protected $userInfo = [];
|
||||
@@ -26,32 +21,39 @@ class BaseController extends Api
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->userInfo = request()->userInfo;
|
||||
|
||||
// 生成缓存key
|
||||
$cacheKey = 'device_info_' . $this->userInfo['id'] . '_' . $this->userInfo['companyId'];
|
||||
|
||||
// 尝试从缓存获取设备信息
|
||||
$device = Cache::get($cacheKey);
|
||||
// 如果缓存不存在,则从数据库获取
|
||||
if (!$device) {
|
||||
$device = Db::name('device_user')
|
||||
->alias('du')
|
||||
->join('device d', 'd.id = du.deviceId','left')
|
||||
->join('device_wechat_login dwl', 'dwl.deviceId = du.deviceId','left')
|
||||
->join('wechat_account wa', 'dwl.wechatId = wa.wechatId','left')
|
||||
->where([
|
||||
'du.userId' => $this->userInfo['id'],
|
||||
'du.companyId' => $this->userInfo['companyId']
|
||||
])
|
||||
->field('d.*,wa.wechatId,wa.alias,wa.s2_wechatAccountId as wechatAccountId')
|
||||
->find();
|
||||
// 将设备信息存入缓存
|
||||
if ($device) {
|
||||
Cache::set($cacheKey, $device, $this->cacheExpire);
|
||||
// 从请求中获取用户信息(通过JWT中间件设置)
|
||||
$this->userInfo = $this->request->userInfo ?? [];
|
||||
|
||||
// 如果用户信息存在,获取设备信息
|
||||
if (!empty($this->userInfo['id']) && !empty($this->userInfo['companyId'])) {
|
||||
// 生成缓存key
|
||||
$cacheKey = 'device_info_' . $this->userInfo['id'] . '_' . $this->userInfo['companyId'];
|
||||
|
||||
// 尝试从缓存获取设备信息
|
||||
$device = Cache::get($cacheKey);
|
||||
|
||||
// 如果缓存不存在,则从数据库获取
|
||||
if (!$device) {
|
||||
$device = Db::name('device_user')
|
||||
->alias('du')
|
||||
->join('device d', 'd.id = du.deviceId', 'left')
|
||||
->join('device_wechat_login dwl', 'dwl.deviceId = du.deviceId', 'left')
|
||||
->join('wechat_account wa', 'dwl.wechatId = wa.wechatId', 'left')
|
||||
->where([
|
||||
'du.userId' => $this->userInfo['id'],
|
||||
'du.companyId' => $this->userInfo['companyId']
|
||||
])
|
||||
->field('d.*,wa.wechatId,wa.alias,wa.s2_wechatAccountId as wechatAccountId')
|
||||
->find();
|
||||
|
||||
// 将设备信息存入缓存
|
||||
if ($device) {
|
||||
Cache::set($cacheKey, $device, $this->cacheExpire);
|
||||
}
|
||||
}
|
||||
$this->device = $device ?: [];
|
||||
}
|
||||
$this->device = $device;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +61,10 @@ class BaseController extends Api
|
||||
*/
|
||||
protected function clearDeviceCache()
|
||||
{
|
||||
$cacheKey = 'device_info_' . $this->userInfo['id'] . '_' . $this->userInfo['companyId'];
|
||||
Cache::rm($cacheKey);
|
||||
if (!empty($this->userInfo['id']) && !empty($this->userInfo['companyId'])) {
|
||||
$cacheKey = 'device_info_' . $this->userInfo['id'] . '_' . $this->userInfo['companyId'];
|
||||
Cache::rm($cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,92 +2,879 @@
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\common\controller\Api;
|
||||
use app\common\model\TrafficPoolCompany;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 客户管理控制器
|
||||
*/
|
||||
class CustomerController extends Api
|
||||
class CustomerController extends BaseController
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedRight = ['*'];
|
||||
|
||||
/**
|
||||
* 获取客户列表
|
||||
* GET /v2/store/customers
|
||||
*
|
||||
* @return \think\Response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
|
||||
// 获取分页参数
|
||||
$page = isset($params['page']) ? intval($params['page']) : 1;
|
||||
$pageSize = isset($params['pageSize']) ? intval($params['pageSize']) : 10;
|
||||
$userInfo = request()->userInfo;
|
||||
|
||||
$where = [];
|
||||
// 必要的查询条件
|
||||
$userId = $userInfo['id'];
|
||||
$companyId = $userInfo['companyId'];
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return errorJson('缺少必要参数');
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId');
|
||||
if (empty($deviceIds)) {
|
||||
return errorJson('设备不存在');
|
||||
}
|
||||
$wechatIds = [];
|
||||
foreach ($deviceIds as $deviceId) {
|
||||
$wechatIds[] = Db::name('device_wechat_login')
|
||||
->where(['deviceId' => $deviceId])
|
||||
->order('id DESC')
|
||||
->value('wechatId');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 搜索条件
|
||||
if (!empty($params['keyword'])) {
|
||||
$where['alias|nickname|wechatId'] = ['like', '%' . $params['keyword'] . '%'];
|
||||
}
|
||||
// if (!empty($params['email'])) {
|
||||
// $where['wa.bindEmail'] = ['like', '%' . $params['email'] . '%'];
|
||||
// }
|
||||
// if (!empty($params['name'])) {
|
||||
// $where['wa.accountRealName|wa.accountUserName|wa.nickname'] = ['like', '%' . $params['name'] . '%'];
|
||||
// }
|
||||
|
||||
// 构建查询
|
||||
$query = Db::table('s2_wechat_friend')
|
||||
->where($where)
|
||||
->whereIn('ownerWechatId',$wechatIds)
|
||||
->group('wechatId'); // 防止重复数据
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
// 克隆查询对象,用于计算总数
|
||||
$countQuery = clone $query;
|
||||
$total = $countQuery->count();
|
||||
|
||||
// 获取分页数据
|
||||
$list = $query->page($page, $pageSize)
|
||||
->order('id DESC')
|
||||
->select();
|
||||
|
||||
|
||||
// 格式化数据
|
||||
foreach ($list as &$item) {
|
||||
$item['labels'] = json_decode($item['labels'], true);
|
||||
$item['createTime'] = date('Y-m-d H:i:s', $item['createTime']);
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 分页参数
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', 10));
|
||||
$pageSize = intval($this->request->param('pageSize', 10));
|
||||
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = $pageSize > 0 ? $pageSize : 10;
|
||||
if ($limit > 100) $limit = 100;
|
||||
|
||||
// 搜索关键词
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
// 筛选条件
|
||||
$status = $this->request->param('status', ''); // 状态:潜在、活跃、沉默、流失
|
||||
$value = $this->request->param('value', ''); // 价值:高、中、低
|
||||
$lifecycle = $this->request->param('lifecycle', ''); // 生命周期
|
||||
|
||||
// 构建查询条件
|
||||
// 从流量池公司表查询,关联流量池总表和微信好友表
|
||||
// 注意:s2_wechat_friend 表没有 ck_ 前缀,使用数组形式 join 可以避免自动添加前缀
|
||||
$query = Db::name('traffic_pool_company')
|
||||
->alias('tpc')
|
||||
->join('traffic_pool tp', 'tp.id = tpc.poolId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId AND wf.ownerWechatId = \'' . $wechatId . '\'', 'left')
|
||||
->where([
|
||||
['tpc.companyId', '=', $companyId],
|
||||
['tpc.ownerAccountId', '=', $accountId], // 归属当前微信账号
|
||||
['tpc.status', '=', TrafficPoolCompany::STATUS_NORMAL], // 正常状态
|
||||
]);
|
||||
|
||||
// 关键词搜索(昵称、微信号、手机号)
|
||||
if (!empty($keyword)) {
|
||||
$query->where(function($query) use ($keyword) {
|
||||
$query->where('tp.nickname', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tp.wechatAlias', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tp.mobile', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tpc.realName', 'like', '%' . $keyword . '%')
|
||||
->whereOr('tpc.phone', 'like', '%' . $keyword . '%');
|
||||
});
|
||||
}
|
||||
|
||||
// 状态筛选(根据生命周期)
|
||||
if (!empty($lifecycle)) {
|
||||
$lifecycleMap = [
|
||||
'潜在' => TrafficPoolCompany::LIFECYCLE_NEW,
|
||||
'活跃' => TrafficPoolCompany::LIFECYCLE_FOLLOWING,
|
||||
'沉默' => TrafficPoolCompany::LIFECYCLE_SILENT,
|
||||
'流失' => TrafficPoolCompany::LIFECYCLE_LOST,
|
||||
];
|
||||
if (isset($lifecycleMap[$lifecycle])) {
|
||||
$query->where('tpc.lifecycle', '=', $lifecycleMap[$lifecycle]);
|
||||
}
|
||||
}
|
||||
|
||||
// 价值筛选(根据意向度或等级)
|
||||
if (!empty($value)) {
|
||||
$valueMap = [
|
||||
'高' => TrafficPoolCompany::INTENTION_HIGH,
|
||||
'中' => TrafficPoolCompany::INTENTION_MEDIUM,
|
||||
'低' => TrafficPoolCompany::INTENTION_LOW,
|
||||
];
|
||||
if (isset($valueMap[$value])) {
|
||||
$query->where('tpc.intentionLevel', '=', $valueMap[$value]);
|
||||
}
|
||||
}
|
||||
|
||||
// 统计总数
|
||||
$total = $query->count();
|
||||
|
||||
// 获取列表数据
|
||||
$list = $query->field('tpc.id,tpc.poolId,tpc.companyId,tpc.ownerAccountId,tpc.realName,tpc.phone,tpc.email,tpc.lifecycle,tpc.intentionLevel,tpc.level,tpc.remark,tpc.createTime,tp.nickname,tp.avatar,tp.wechatId,tp.wechatAlias,tp.mobile,tp.gender,tp.region,tp.signature,wf.id as friendId,wf.alias as friendAlias,wf.nickname as friendNickname')
|
||||
->order('tpc.id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 格式化数据
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
// 获取标签
|
||||
$tags = Db::name('traffic_pool_tag')
|
||||
->where([
|
||||
['poolCompanyId', '=', $item['id']],
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->column('tagName');
|
||||
|
||||
// 获取最后互动时间(从行为记录表)
|
||||
$lastBehavior = Db::name('traffic_pool_behavior')
|
||||
->where('poolCompanyId', $item['id'])
|
||||
->order('behaviorTime desc')
|
||||
->find();
|
||||
|
||||
$lastContact = '';
|
||||
if (!empty($lastBehavior) && !empty($lastBehavior['behaviorTime'])) {
|
||||
$lastContact = date('Y-m-d H:i:s', intval($lastBehavior['behaviorTime']));
|
||||
}
|
||||
|
||||
// 获取价值评估(从RFM或估值相关表,这里先使用模拟数据)
|
||||
$valuation = $this->calculateCustomerValuation($item['id']);
|
||||
|
||||
// 状态映射
|
||||
$lifecycleMap = [
|
||||
TrafficPoolCompany::LIFECYCLE_NEW => '潜在',
|
||||
TrafficPoolCompany::LIFECYCLE_FOLLOWING => '活跃',
|
||||
TrafficPoolCompany::LIFECYCLE_CONVERTED => '已成交',
|
||||
TrafficPoolCompany::LIFECYCLE_SILENT => '沉默',
|
||||
TrafficPoolCompany::LIFECYCLE_LOST => '流失',
|
||||
];
|
||||
|
||||
// 价值映射
|
||||
$intentionMap = [
|
||||
TrafficPoolCompany::INTENTION_HIGH => '高',
|
||||
TrafficPoolCompany::INTENTION_MEDIUM => '中',
|
||||
TrafficPoolCompany::INTENTION_LOW => '低',
|
||||
TrafficPoolCompany::INTENTION_UNKNOWN => '低',
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'id' => intval($item['id']),
|
||||
'poolCompanyId' => intval($item['id']),
|
||||
'name' => $item['realName'] ?? $item['nickname'] ?? '未知',
|
||||
'nickname' => $item['nickname'] ?? '',
|
||||
'wechatId' => $item['wechatAlias'] ?? $item['wechatId'] ?? '',
|
||||
'avatar' => $item['avatar'] ?? '',
|
||||
'phone' => $item['phone'] ?? $item['mobile'] ?? '',
|
||||
'email' => $item['email'] ?? '',
|
||||
'status' => $lifecycleMap[$item['lifecycle'] ?? TrafficPoolCompany::LIFECYCLE_NEW] ?? '潜在',
|
||||
'value' => $intentionMap[$item['intentionLevel'] ?? TrafficPoolCompany::INTENTION_UNKNOWN] ?? '低',
|
||||
'tags' => $tags ?: [],
|
||||
'lastContact' => $lastContact,
|
||||
'nextFollow' => !empty($item['nextFollowTime']) && is_numeric($item['nextFollowTime'])
|
||||
? date('Y-m-d', intval($item['nextFollowTime']))
|
||||
: '',
|
||||
'notes' => $item['remark'] ?? '',
|
||||
'addedDate' => !empty($item['createTime']) && is_numeric($item['createTime'])
|
||||
? date('Y-m-d', intval($item['createTime']))
|
||||
: '',
|
||||
'valuation' => $valuation,
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $result,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取客户列表失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return successJson([
|
||||
'list' => $list,
|
||||
'total' => $total
|
||||
], '获取成功');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户详情
|
||||
* GET /v2/store/customers/:id
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 获取客户ID
|
||||
$customerId = intval($this->request->param('id', 0));
|
||||
if (empty($customerId)) {
|
||||
return json(['code' => 400, 'msg' => '客户ID不能为空']);
|
||||
}
|
||||
|
||||
// 查询客户详情
|
||||
// 注意:s2_wechat_friend 表没有 ck_ 前缀,使用数组形式 join 可以避免自动添加前缀
|
||||
$customer = Db::name('traffic_pool_company')
|
||||
->alias('tpc')
|
||||
->join('traffic_pool tp', 'tp.id = tpc.poolId', 'left')
|
||||
->join(['s2_wechat_friend' => 'wf'], 'wf.wechatId = tp.wechatId AND wf.ownerWechatId = \'' . $wechatId . '\'', 'left')
|
||||
->where([
|
||||
['tpc.id', '=', $customerId],
|
||||
['tpc.companyId', '=', $companyId],
|
||||
['tpc.ownerAccountId', '=', $accountId],
|
||||
])
|
||||
->field('tpc.*,tp.*,wf.id as friendId,wf.alias as friendAlias,wf.nickname as friendNickname')
|
||||
->find();
|
||||
|
||||
if (empty($customer)) {
|
||||
return json(['code' => 404, 'msg' => '客户不存在']);
|
||||
}
|
||||
|
||||
// 获取标签
|
||||
$tags = Db::name('traffic_pool_tag')
|
||||
->where([
|
||||
['poolCompanyId', '=', $customerId],
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->column('tagName');
|
||||
|
||||
// 获取流量池标签(系统标签或微信标签)
|
||||
// 注意:从表结构看,isSystem字段在tagDefineId关联的标签定义表中
|
||||
// 这里先获取所有标签,后续可以根据tagType区分
|
||||
$allTags = Db::name('traffic_pool_tag')
|
||||
->alias('tpt')
|
||||
->join('traffic_pool_tag_define tptd', 'tpt.tagDefineId = tptd.id', 'left')
|
||||
->where([
|
||||
['tpt.poolCompanyId', '=', $customerId],
|
||||
['tpt.isDel', '=', 0]
|
||||
])
|
||||
->field('tpt.tagName,tptd.isSystem')
|
||||
->select();
|
||||
|
||||
$trafficPoolTags = [];
|
||||
foreach ($allTags as $tag) {
|
||||
// 系统标签或微信标签(tagType=1)作为流量池标签
|
||||
if (!empty($tag['isSystem']) || (!empty($tag['tagType']) && $tag['tagType'] == 1)) {
|
||||
$trafficPoolTags[] = $tag['tagName'];
|
||||
}
|
||||
}
|
||||
|
||||
// 获取来源信息
|
||||
$sources = Db::name('traffic_pool_source')
|
||||
->where('poolCompanyId', $customerId)
|
||||
->order('createTime desc')
|
||||
->select();
|
||||
|
||||
$sourceChannel = '未知';
|
||||
$addTime = '';
|
||||
if (!empty($sources)) {
|
||||
$firstSource = $sources[0];
|
||||
$sourceChannel = $firstSource['sourceName'] ?? '未知';
|
||||
$addTime = !empty($firstSource['createTime']) && is_numeric($firstSource['createTime'])
|
||||
? date('Y-m-d', intval($firstSource['createTime']))
|
||||
: '';
|
||||
}
|
||||
|
||||
// 获取互动统计
|
||||
$interactionStats = $this->getInteractionStats($customerId);
|
||||
|
||||
// 获取价值评估
|
||||
$valueEvaluation = $this->getValueEvaluation($customerId);
|
||||
|
||||
// 获取用户旅程(最近记录)
|
||||
$journey = $this->getCustomerJourney($customerId, 10);
|
||||
|
||||
// 获取消费偏好(从行为记录分析)
|
||||
$preferences = $this->getCustomerPreferences($customerId);
|
||||
|
||||
// 状态映射
|
||||
$lifecycleMap = [
|
||||
TrafficPoolCompany::LIFECYCLE_NEW => '潜在',
|
||||
TrafficPoolCompany::LIFECYCLE_FOLLOWING => '活跃',
|
||||
TrafficPoolCompany::LIFECYCLE_CONVERTED => '已成交',
|
||||
TrafficPoolCompany::LIFECYCLE_SILENT => '沉默',
|
||||
TrafficPoolCompany::LIFECYCLE_LOST => '流失',
|
||||
];
|
||||
|
||||
$conversionStatus = $lifecycleMap[$customer['lifecycle'] ?? TrafficPoolCompany::LIFECYCLE_NEW] ?? '潜在';
|
||||
|
||||
// 生成首字母
|
||||
$name = $customer['realName'] ?? $customer['nickname'] ?? '未知';
|
||||
$initials = mb_substr($name, 0, 1, 'UTF-8');
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
// 基础信息
|
||||
'id' => intval($customer['id']),
|
||||
'poolCompanyId' => intval($customer['id']),
|
||||
'initials' => $initials,
|
||||
|
||||
// 好友概览
|
||||
'nickname' => $customer['nickname'] ?? '',
|
||||
'remarkName' => $customer['realName'] ?? '',
|
||||
'wechatId' => $customer['wechatAlias'] ?? $customer['wechatId'] ?? '',
|
||||
'wechatPhone' => $customer['mobile'] ?? '',
|
||||
'wechatLocation' => $customer['region'] ?? '',
|
||||
'avatar' => $customer['avatar'] ?? '',
|
||||
'conversionStatus' => $conversionStatus,
|
||||
'sourceChannel' => $sourceChannel,
|
||||
'addTime' => $addTime,
|
||||
|
||||
// 基础信息
|
||||
'realName' => $customer['realName'] ?? '',
|
||||
'sex' => $this->getGenderText($customer['gender'] ?? 0),
|
||||
'age' => $this->calculateAge($customer['birthday'] ?? ''),
|
||||
'personalPhone' => $customer['phone'] ?? '',
|
||||
'email' => $customer['email'] ?? '',
|
||||
'idNumber' => $this->maskIdNumber($customer['idCard'] ?? ''),
|
||||
'address' => $customer['address'] ?? '',
|
||||
|
||||
// 标签
|
||||
'tags' => $tags ?: [],
|
||||
'trafficPoolTags' => $trafficPoolTags ?: [],
|
||||
|
||||
// 互动统计
|
||||
'interactionStats' => $interactionStats,
|
||||
|
||||
// 价值评估
|
||||
'valueEvaluation' => $valueEvaluation,
|
||||
'valuationRank' => 'TOP 8%', // 需要计算
|
||||
'valuationTrend' => '+12%', // 需要计算
|
||||
|
||||
// 用户旅程
|
||||
'journey' => $journey,
|
||||
|
||||
// 消费偏好
|
||||
'preferences' => $preferences,
|
||||
|
||||
// AI预测(需要实现)
|
||||
'aiProfile' => [
|
||||
'summary' => '该用户为典型的高净值客户,消费频率高且偏好高端产品。',
|
||||
'predictions' => [
|
||||
'预计未来7天内有85%概率下单',
|
||||
'流失风险极低(5%),建议通过会员活动维持粘性',
|
||||
'最佳触达时间:工作日12:00-14:00或周末下午'
|
||||
]
|
||||
],
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取客户详情失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新客户信息
|
||||
* PUT /v2/store/customers/:id
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function update()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 获取客户ID
|
||||
$customerId = intval($this->request->param('id', 0));
|
||||
if (empty($customerId)) {
|
||||
return json(['code' => 400, 'msg' => '客户ID不能为空']);
|
||||
}
|
||||
|
||||
// 验证客户是否存在且归属当前账号
|
||||
$customer = Db::name('traffic_pool_company')
|
||||
->where([
|
||||
['id', '=', $customerId],
|
||||
['companyId', '=', $companyId],
|
||||
['ownerAccountId', '=', $accountId],
|
||||
])
|
||||
->find();
|
||||
|
||||
if (empty($customer)) {
|
||||
return json(['code' => 404, 'msg' => '客户不存在']);
|
||||
}
|
||||
|
||||
// 获取更新参数
|
||||
$updateType = $this->request->param('updateType', ''); // wechat, personal, tags
|
||||
|
||||
$updateData = [];
|
||||
$updateFields = [];
|
||||
|
||||
// 更新微信资料
|
||||
if ($updateType === 'wechat' || $this->request->has('remarkName')) {
|
||||
$remarkName = $this->request->param('remarkName', '');
|
||||
if ($remarkName !== '') {
|
||||
$updateData['realName'] = $remarkName; // 备注名存储在realName字段
|
||||
$updateFields[] = '备注名';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新基础信息
|
||||
if ($updateType === 'personal') {
|
||||
$realName = $this->request->param('realName', '');
|
||||
$sex = $this->request->param('sex', '');
|
||||
$age = $this->request->param('age', '');
|
||||
$phone = $this->request->param('phone', '');
|
||||
$email = $this->request->param('email', '');
|
||||
$idNumber = $this->request->param('idNumber', '');
|
||||
$address = $this->request->param('address', '');
|
||||
|
||||
if ($realName !== '') {
|
||||
$updateData['realName'] = $realName;
|
||||
$updateFields[] = '姓名';
|
||||
}
|
||||
if ($sex !== '') {
|
||||
$updateData['gender'] = $sex === '男' ? 1 : ($sex === '女' ? 2 : 0);
|
||||
$updateFields[] = '性别';
|
||||
}
|
||||
if ($age !== '') {
|
||||
// 根据年龄计算生日(简化处理)
|
||||
$birthYear = date('Y') - intval($age);
|
||||
$updateData['birthday'] = $birthYear . '-01-01';
|
||||
$updateFields[] = '年龄';
|
||||
}
|
||||
if ($phone !== '') {
|
||||
$updateData['phone'] = $phone;
|
||||
$updateFields[] = '手机号';
|
||||
}
|
||||
if ($email !== '') {
|
||||
$updateData['email'] = $email;
|
||||
$updateFields[] = '邮箱';
|
||||
}
|
||||
if ($idNumber !== '') {
|
||||
$updateData['idCard'] = $idNumber;
|
||||
$updateFields[] = '身份证号';
|
||||
}
|
||||
if ($address !== '') {
|
||||
$updateData['address'] = $address;
|
||||
$updateFields[] = '住址';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新标签
|
||||
if ($updateType === 'tags') {
|
||||
$tags = $this->request->param('tags', []);
|
||||
if (is_array($tags)) {
|
||||
// 获取客户信息(用于获取identifier和companyId)
|
||||
$customerInfo = Db::name('traffic_pool_company')
|
||||
->where('id', $customerId)
|
||||
->field('identifier,companyId')
|
||||
->find();
|
||||
|
||||
if (!empty($customerInfo)) {
|
||||
// 软删除旧标签(只删除站内标签,保留微信标签和系统标签)
|
||||
// 通过关联标签定义表判断是否为站内标签
|
||||
Db::name('traffic_pool_tag')
|
||||
->alias('tpt')
|
||||
->join('traffic_pool_tag_define tptd', 'tpt.tagDefineId = tptd.id', 'left')
|
||||
->where([
|
||||
['tpt.poolCompanyId', '=', $customerId],
|
||||
['tptd.tagType', '=', 2], // 站内标签
|
||||
['tpt.isDel', '=', 0]
|
||||
])
|
||||
->update([
|
||||
'tpt.isDel' => 1,
|
||||
'tpt.deleteTime' => time()
|
||||
]);
|
||||
|
||||
// 添加新标签(站内标签)
|
||||
foreach ($tags as $tag) {
|
||||
if (!empty($tag)) {
|
||||
// 查找或创建标签定义
|
||||
$tagDefine = Db::name('traffic_pool_tag_define')
|
||||
->where([
|
||||
['companyId', 'in', [$companyId, 0]],
|
||||
['tagName', '=', $tag],
|
||||
['tagType', '=', 2], // 站内标签
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->order('companyId desc') // 优先使用公司自定义标签
|
||||
->find();
|
||||
|
||||
if (empty($tagDefine)) {
|
||||
// 创建标签定义
|
||||
$tagDefineId = Db::name('traffic_pool_tag_define')->insertGetId([
|
||||
'companyId' => $companyId,
|
||||
'tagType' => 2, // 站内标签
|
||||
'tagCode' => 'custom_' . time() . '_' . rand(1000, 9999),
|
||||
'tagName' => $tag,
|
||||
'isSystem' => 0,
|
||||
'status' => 1,
|
||||
'createTime' => time(),
|
||||
]);
|
||||
} else {
|
||||
$tagDefineId = $tagDefine['id'];
|
||||
}
|
||||
|
||||
// 检查标签是否已存在
|
||||
$existTag = Db::name('traffic_pool_tag')
|
||||
->where([
|
||||
['poolCompanyId', '=', $customerId],
|
||||
['tagDefineId', '=', $tagDefineId],
|
||||
['isDel', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (empty($existTag)) {
|
||||
Db::name('traffic_pool_tag')->insert([
|
||||
'poolCompanyId' => $customerId,
|
||||
'identifier' => $customerInfo['identifier'],
|
||||
'companyId' => $customerInfo['companyId'],
|
||||
'tagDefineId' => $tagDefineId,
|
||||
'tagType' => 2, // 站内标签
|
||||
'tagName' => $tag,
|
||||
'source' => 1, // 手动
|
||||
'operatorId' => $userId,
|
||||
'createTime' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$updateFields[] = '标签';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新客户信息
|
||||
if (!empty($updateData)) {
|
||||
$updateData['updateTime'] = time();
|
||||
Db::name('traffic_pool_company')
|
||||
->where('id', $customerId)
|
||||
->update($updateData);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功',
|
||||
'data' => [
|
||||
'updatedFields' => $updateFields
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新客户信息失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算客户估值
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return int
|
||||
*/
|
||||
private function calculateCustomerValuation($poolCompanyId)
|
||||
{
|
||||
// TODO: 实现真实的估值计算逻辑
|
||||
// 可以从订单表、行为记录表等计算
|
||||
return 50000; // 模拟数据
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取互动统计
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return array
|
||||
*/
|
||||
private function getInteractionStats($poolCompanyId)
|
||||
{
|
||||
// 统计聊天消息数
|
||||
$chatCount = Db::name('traffic_pool_behavior')
|
||||
->where([
|
||||
['poolCompanyId', '=', $poolCompanyId],
|
||||
['behaviorType', '=', 1] // 发送消息
|
||||
])
|
||||
->count();
|
||||
|
||||
// 统计朋友圈互动数
|
||||
$momentsCount = Db::name('traffic_pool_behavior')
|
||||
->where([
|
||||
['poolCompanyId', '=', $poolCompanyId],
|
||||
['behaviorType', 'in', [9, 10]] // 点赞朋友圈、评论朋友圈
|
||||
])
|
||||
->count();
|
||||
|
||||
// 统计红包转账总额(从行为记录中获取)
|
||||
$redPacketTotal = Db::name('traffic_pool_behavior')
|
||||
->where([
|
||||
['poolCompanyId', '=', $poolCompanyId],
|
||||
['behaviorType', '=', 7] // 支付
|
||||
])
|
||||
->sum('amount');
|
||||
$redPacketTotal = round(floatval($redPacketTotal ?? 0), 2);
|
||||
|
||||
// 计算活跃度评分(简化计算)
|
||||
$activeScore = min(100, ($chatCount * 2 + $momentsCount * 3 + $redPacketTotal / 10));
|
||||
|
||||
// 获取最后互动时间
|
||||
$lastBehavior = Db::name('traffic_pool_behavior')
|
||||
->where('poolCompanyId', $poolCompanyId)
|
||||
->order('behaviorTime desc')
|
||||
->find();
|
||||
|
||||
$lastInteraction = '从未互动';
|
||||
if (!empty($lastBehavior) && !empty($lastBehavior['behaviorTime'])) {
|
||||
$time = intval($lastBehavior['behaviorTime']);
|
||||
$diff = time() - $time;
|
||||
if ($diff < 3600) {
|
||||
$lastInteraction = '刚刚';
|
||||
} elseif ($diff < 86400) {
|
||||
$lastInteraction = '今天 ' . date('H:i', $time);
|
||||
} elseif ($diff < 172800) {
|
||||
$lastInteraction = '昨天 ' . date('H:i', $time);
|
||||
} else {
|
||||
$lastInteraction = date('Y-m-d H:i', $time);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'lastInteraction' => $lastInteraction,
|
||||
'chatCount' => intval($chatCount),
|
||||
'momentsCount' => intval($momentsCount),
|
||||
'redPacketTotal' => number_format($redPacketTotal, 2),
|
||||
'activeScore' => intval($activeScore)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取价值评估
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return array
|
||||
*/
|
||||
private function getValueEvaluation($poolCompanyId)
|
||||
{
|
||||
// TODO: 实现真实的价值评估计算
|
||||
// 可以从RFM模型、CLV模型、社交裂变模型等计算
|
||||
|
||||
return [
|
||||
'totalValuation' => 58600,
|
||||
'models' => [
|
||||
[
|
||||
'name' => 'RFM 贡献模型',
|
||||
'value' => 52000,
|
||||
'weight' => 0.5,
|
||||
'score' => 92
|
||||
],
|
||||
[
|
||||
'name' => 'CLV 终身价值模型',
|
||||
'value' => 78000,
|
||||
'weight' => 0.3,
|
||||
'score' => 88
|
||||
],
|
||||
[
|
||||
'name' => '社交/裂变模型',
|
||||
'value' => 15000,
|
||||
'weight' => 0.2,
|
||||
'score' => 75
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户旅程
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @param int $limit 限制数量
|
||||
* @return array
|
||||
*/
|
||||
private function getCustomerJourney($poolCompanyId, $limit = 10)
|
||||
{
|
||||
// 从行为记录表获取
|
||||
$behaviors = Db::name('traffic_pool_behavior')
|
||||
->where('poolCompanyId', $poolCompanyId)
|
||||
->order('behaviorTime desc')
|
||||
->limit($limit)
|
||||
->select();
|
||||
|
||||
$journey = [];
|
||||
$typeMap = [
|
||||
1 => '发送消息',
|
||||
2 => '接收消息',
|
||||
3 => '浏览',
|
||||
4 => '点击',
|
||||
5 => '咨询',
|
||||
6 => '下单',
|
||||
7 => '支付',
|
||||
8 => '退款',
|
||||
9 => '点赞朋友圈',
|
||||
10 => '评论朋友圈',
|
||||
];
|
||||
|
||||
foreach ($behaviors as $behavior) {
|
||||
$type = $typeMap[$behavior['behaviorType']] ?? '未知行为';
|
||||
$content = $behavior['behaviorName'] ?? $type;
|
||||
if (!empty($behavior['targetName'])) {
|
||||
$content .= ': ' . $behavior['targetName'];
|
||||
}
|
||||
|
||||
$journey[] = [
|
||||
'type' => $type,
|
||||
'content' => $content,
|
||||
'time' => !empty($behavior['behaviorTime']) && is_numeric($behavior['behaviorTime'])
|
||||
? date('Y-m-d H:i:s', intval($behavior['behaviorTime']))
|
||||
: '',
|
||||
'source' => '存客宝',
|
||||
'actionType' => $this->getActionType($behavior['behaviorType']),
|
||||
'amount' => !empty($behavior['amount']) && floatval($behavior['amount']) > 0
|
||||
? '¥' . number_format(floatval($behavior['amount']), 2)
|
||||
: '',
|
||||
];
|
||||
}
|
||||
|
||||
return $journey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取行为类型
|
||||
*
|
||||
* @param int $behaviorType 行为类型
|
||||
* @return string
|
||||
*/
|
||||
private function getActionType($behaviorType)
|
||||
{
|
||||
if (in_array($behaviorType, [6, 7, 8])) {
|
||||
return 'transaction'; // 交易
|
||||
} elseif (in_array($behaviorType, [1, 2, 9, 10])) {
|
||||
return 'social'; // 社交
|
||||
} elseif (in_array($behaviorType, [3, 4, 5])) {
|
||||
return 'footprint'; // 轨迹
|
||||
} else {
|
||||
return 'flow'; // 流量
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取消费偏好
|
||||
*
|
||||
* @param int $poolCompanyId 客户ID
|
||||
* @return array
|
||||
*/
|
||||
private function getCustomerPreferences($poolCompanyId)
|
||||
{
|
||||
// TODO: 从行为记录和订单记录分析消费偏好
|
||||
return [
|
||||
'categories' => ['智能数码', '精品咖啡', '商务休闲'],
|
||||
'recentItems' => ['iPhone 16 Pro', 'iPad Air'],
|
||||
'coreInterest' => '数码发烧友 & 品质生活追求者'
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取性别文本
|
||||
*
|
||||
* @param int $gender 性别代码
|
||||
* @return string
|
||||
*/
|
||||
private function getGenderText($gender)
|
||||
{
|
||||
$map = [
|
||||
0 => '保密',
|
||||
1 => '男',
|
||||
2 => '女',
|
||||
];
|
||||
return $map[$gender] ?? '未知';
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算年龄
|
||||
*
|
||||
* @param string $birthday 生日
|
||||
* @return int
|
||||
*/
|
||||
private function calculateAge($birthday)
|
||||
{
|
||||
if (empty($birthday)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$birthTimestamp = strtotime($birthday);
|
||||
if ($birthTimestamp === false) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$age = date('Y') - date('Y', $birthTimestamp);
|
||||
if (date('md', $birthTimestamp) > date('md')) {
|
||||
$age--;
|
||||
}
|
||||
|
||||
return $age;
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏身份证号
|
||||
*
|
||||
* @param string $idNumber 身份证号
|
||||
* @return string
|
||||
*/
|
||||
private function maskIdNumber($idNumber)
|
||||
{
|
||||
if (empty($idNumber) || strlen($idNumber) < 8) {
|
||||
return $idNumber;
|
||||
}
|
||||
|
||||
return substr($idNumber, 0, 4) . str_repeat('*', strlen($idNumber) - 8) . substr($idNumber, -4);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
436
application/store/controller/DeviceWechatController.php
Normal file
436
application/store/controller/DeviceWechatController.php
Normal file
@@ -0,0 +1,436 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\common\service\WechatAccountHealthScoreService;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 设备和微信控制器
|
||||
*/
|
||||
class DeviceWechatController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取设备和微信信息
|
||||
* GET /v2/store/device-wechat/info
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getInfo()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['id'])) {
|
||||
return json(['code' => 404, 'msg' => '设备不存在']);
|
||||
}
|
||||
|
||||
$deviceId = $device['id'];
|
||||
$wechatId = $device['wechatId'] ?? '';
|
||||
|
||||
if (empty($wechatId)) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
// 1. 获取微信账号信息
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id,wechatId,alias,nickname,avatar,totalFriend')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 2. 获取设备持有人信息
|
||||
$deviceOwner = Db::name('device_user')
|
||||
->alias('du')
|
||||
->join('users u', 'u.id = du.userId', 'left')
|
||||
->where([
|
||||
['du.deviceId', '=', $deviceId],
|
||||
['du.companyId', '=', $companyId],
|
||||
['du.deleteTime', '=', 0]
|
||||
])
|
||||
->field('u.username,u.account')
|
||||
->find();
|
||||
|
||||
$deviceOwnerName = $deviceOwner['username'] ?? $deviceOwner['account'] ?? '未知';
|
||||
|
||||
// 3. 获取设备在线状态和微信状态
|
||||
$deviceWechatLogin = Db::name('device_wechat_login')
|
||||
->where([
|
||||
['deviceId', '=', $deviceId],
|
||||
['wechatId', '=', $wechatId],
|
||||
['companyId', '=', $companyId]
|
||||
])
|
||||
->order('id desc')
|
||||
->find();
|
||||
|
||||
$deviceOnline = !empty($device['alive']) && $device['alive'] == 1;
|
||||
$wechatNormal = !empty($deviceWechatLogin['alive']) && $deviceWechatLogin['alive'] == 1;
|
||||
|
||||
// 4. 获取健康分信息
|
||||
$healthScoreService = new WechatAccountHealthScoreService();
|
||||
$healthScoreInfo = $healthScoreService->getHealthScore($accountId);
|
||||
|
||||
$healthScore = $healthScoreInfo['healthScore'] ?? 0;
|
||||
$maxAddFriendPerDay = $healthScoreInfo['maxAddFriendPerDay'] ?? 0;
|
||||
|
||||
// 5. 获取今日加粉统计
|
||||
$todayStats = $this->getTodayAddFriendStats($wechatId);
|
||||
|
||||
// 6. 获取基础构成
|
||||
$baseComposition = $this->getBaseComposition($healthScoreInfo);
|
||||
|
||||
// 7. 判断健康状态
|
||||
$healthStatus = $this->getHealthStatus($healthScore);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
// 用户资料
|
||||
'user' => [
|
||||
'nickname' => $wechatAccount['nickname'] ?? '',
|
||||
'wechatId' => $wechatAccount['alias'] ?? $wechatId,
|
||||
'avatar' => $wechatAccount['avatar'] ?? '',
|
||||
],
|
||||
// 设备信息
|
||||
'device' => [
|
||||
'owner' => $deviceOwnerName,
|
||||
'imei' => $device['imei'] ?? $device['deviceImei'] ?? '',
|
||||
],
|
||||
// 设备状态
|
||||
'status' => [
|
||||
'deviceOnline' => $deviceOnline,
|
||||
'wechatNormal' => $wechatNormal,
|
||||
],
|
||||
// 微信健康分
|
||||
'healthScore' => [
|
||||
'score' => intval($healthScore),
|
||||
'status' => $healthStatus,
|
||||
'maxAddFriendPerDay' => intval($maxAddFriendPerDay),
|
||||
'todayAdded' => intval($todayStats['todayAdded']),
|
||||
'todayRemaining' => max(0, intval($maxAddFriendPerDay) - intval($todayStats['todayAdded'])),
|
||||
'progress' => $maxAddFriendPerDay > 0 ? round((intval($todayStats['todayAdded']) / intval($maxAddFriendPerDay)) * 100, 2) : 0,
|
||||
],
|
||||
// 加粉统计
|
||||
'addFriendStats' => [
|
||||
'success' => intval($todayStats['success']),
|
||||
'failed' => intval($todayStats['failed']),
|
||||
'pending' => intval($todayStats['pending']),
|
||||
],
|
||||
// 基础构成
|
||||
'baseComposition' => $baseComposition,
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取设备和微信信息失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取动态记录(分页)
|
||||
* GET /v2/store/device-wechat/dynamic-records
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getDynamicRecords()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId) || empty($companyId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取设备信息
|
||||
$device = $this->device;
|
||||
if (empty($device) || empty($device['wechatId'])) {
|
||||
return json(['code' => 404, 'msg' => '设备未绑定微信']);
|
||||
}
|
||||
|
||||
$wechatId = $device['wechatId'];
|
||||
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (empty($wechatAccount)) {
|
||||
return json(['code' => 404, 'msg' => '微信账号不存在']);
|
||||
}
|
||||
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 分页参数
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', 10));
|
||||
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = 10;
|
||||
if ($limit > 100) $limit = 100; // 限制最大每页数量
|
||||
|
||||
// 获取近7天的开始时间
|
||||
$sevenDaysAgo = strtotime('-7 days');
|
||||
|
||||
// 查询动态记录(从健康分日志表)
|
||||
$query = Db::table('s2_wechat_account_score_log')
|
||||
->where([
|
||||
['accountId', '=', $accountId],
|
||||
['createTime', '>=', $sevenDaysAgo]
|
||||
])
|
||||
->order('createTime desc');
|
||||
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->select();
|
||||
|
||||
// 格式化数据
|
||||
$records = [];
|
||||
foreach ($list as $item) {
|
||||
// 使用changeValue字段(变动值)或计算valueAfter - valueBefore
|
||||
$score = intval($item['changeValue'] ?? 0);
|
||||
if ($score == 0) {
|
||||
$score = intval($item['valueAfter'] ?? 0) - intval($item['valueBefore'] ?? 0);
|
||||
}
|
||||
|
||||
$formatted = $score > 0 ? '+' . $score : (string)$score;
|
||||
|
||||
// 生成描述文本
|
||||
$field = $item['field'] ?? '';
|
||||
$description = $this->formatFieldDescription($field, $item);
|
||||
|
||||
$records[] = [
|
||||
'name' => $description,
|
||||
'score' => $score,
|
||||
'formatted' => $formatted,
|
||||
'type' => $score > 0 ? 'bonus' : ($score < 0 ? 'penalty' : 'neutral'),
|
||||
'time' => !empty($item['createTime']) && is_numeric($item['createTime'])
|
||||
? date('Y-m-d H:i:s', intval($item['createTime']))
|
||||
: '',
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $records,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit,
|
||||
'note' => '仅显示近7天记录'
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取动态记录失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取今日加粉统计
|
||||
*
|
||||
* @param string $wechatId 微信ID
|
||||
* @return array
|
||||
*/
|
||||
private function getTodayAddFriendStats($wechatId)
|
||||
{
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($companyId)) {
|
||||
return [
|
||||
'todayAdded' => 0,
|
||||
'success' => 0,
|
||||
'failed' => 0,
|
||||
'pending' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 1. 查询今日加粉任务(成功和失败)
|
||||
$todayTasks = Db::table('s2_friend_task')
|
||||
->where('wechatId', $wechatId)
|
||||
->whereBetween('createTime', [$todayStart, $todayEnd])
|
||||
->field('status')
|
||||
->select();
|
||||
|
||||
$stats = [
|
||||
'todayAdded' => 0,
|
||||
'success' => 0,
|
||||
'failed' => 0,
|
||||
'pending' => 0,
|
||||
];
|
||||
|
||||
// 统计成功和失败
|
||||
foreach ($todayTasks as $task) {
|
||||
$status = intval($task['status'] ?? 0);
|
||||
|
||||
// 状态:0=执行中,1=成功,2=失败
|
||||
if ($status == 1) {
|
||||
$stats['success']++;
|
||||
$stats['todayAdded']++;
|
||||
} elseif ($status == 2) {
|
||||
$stats['failed']++;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 查询场景获客中的待添加数量(friendStatus = 0 且来源是场景获客)
|
||||
// 获取微信账号ID
|
||||
$wechatAccount = Db::table('s2_wechat_account')
|
||||
->where('wechatId', $wechatId)
|
||||
->field('id')
|
||||
->find();
|
||||
|
||||
if (!empty($wechatAccount)) {
|
||||
$accountId = $wechatAccount['id'];
|
||||
|
||||
// 查询场景获客中未添加的好友数量
|
||||
// 关联流量池公司表和流量来源表,筛选:
|
||||
// - friendStatus = 0(未加)
|
||||
// - sourceName 包含 "场景获客"
|
||||
// - ownerAccountId = 当前微信账号ID(或根据业务需求调整)
|
||||
$pendingCount = Db::name('traffic_pool_company')
|
||||
->alias('tpc')
|
||||
->join('traffic_pool_source tps', 'tps.poolCompanyId = tpc.id', 'left')
|
||||
->where([
|
||||
['tpc.companyId', '=', $companyId],
|
||||
['tpc.friendStatus', '=', 0], // 未加
|
||||
['tpc.ownerAccountId', '=', $accountId], // 归属当前微信账号
|
||||
['tps.sourceName', 'like', '场景获客%'], // 来源是场景获客
|
||||
])
|
||||
->count();
|
||||
|
||||
$stats['pending'] = intval($pendingCount);
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取基础构成
|
||||
*
|
||||
* @param array $healthScoreInfo 健康分信息
|
||||
* @return array
|
||||
*/
|
||||
private function getBaseComposition($healthScoreInfo)
|
||||
{
|
||||
$baseScore = intval($healthScoreInfo['baseScore'] ?? 0);
|
||||
$baseInfoScore = intval($healthScoreInfo['baseInfoScore'] ?? 0);
|
||||
$friendCountScore = intval($healthScoreInfo['friendCountScore'] ?? 0);
|
||||
$friendCount = intval($healthScoreInfo['friendCount'] ?? 0);
|
||||
|
||||
$composition = [];
|
||||
|
||||
// 账号基础分(默认60分)
|
||||
$accountBaseScore = 60;
|
||||
$composition[] = [
|
||||
'name' => '账号基础分',
|
||||
'description' => '系统分配默认初始分值',
|
||||
'score' => $accountBaseScore,
|
||||
'formatted' => '+' . $accountBaseScore,
|
||||
];
|
||||
|
||||
// 基础信息分(已修改微信号)
|
||||
if ($baseInfoScore > 0) {
|
||||
$composition[] = [
|
||||
'name' => '基础信息',
|
||||
'description' => '已修改微信号(权重0.2)',
|
||||
'score' => $baseInfoScore,
|
||||
'formatted' => '+' . $baseInfoScore,
|
||||
];
|
||||
}
|
||||
|
||||
// 好友数量加成
|
||||
if ($friendCountScore > 0) {
|
||||
$composition[] = [
|
||||
'name' => '好友数量加成',
|
||||
'description' => '当前好友' . number_format($friendCount) . '人(权重0.3)',
|
||||
'score' => $friendCountScore,
|
||||
'formatted' => '+' . $friendCountScore,
|
||||
];
|
||||
}
|
||||
|
||||
return $composition;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取健康状态
|
||||
*
|
||||
* @param int $healthScore 健康分
|
||||
* @return string
|
||||
*/
|
||||
private function getHealthStatus($healthScore)
|
||||
{
|
||||
if ($healthScore >= 80) {
|
||||
return '健康';
|
||||
} elseif ($healthScore >= 60) {
|
||||
return '良好';
|
||||
} elseif ($healthScore >= 40) {
|
||||
return '一般';
|
||||
} else {
|
||||
return '较差';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化字段描述
|
||||
*
|
||||
* @param string $field 字段名
|
||||
* @param array $item 记录项
|
||||
* @return string
|
||||
*/
|
||||
private function formatFieldDescription($field, $item)
|
||||
{
|
||||
$descriptions = [
|
||||
'frequentPenalty' => '触发限额',
|
||||
'noFrequentBonus' => '不触发频繁',
|
||||
'banPenalty' => '封号',
|
||||
'healthScore' => '健康分变动',
|
||||
'baseScore' => '基础分',
|
||||
'baseInfoScore' => '基础信息',
|
||||
'friendCountScore' => '好友数量加成',
|
||||
];
|
||||
|
||||
$baseDesc = $descriptions[$field] ?? $field;
|
||||
|
||||
// 特殊处理:连续N天不触发频繁
|
||||
if ($field == 'noFrequentBonus') {
|
||||
$extra = !empty($item['extra']) ? json_decode($item['extra'], true) : [];
|
||||
$days = $extra['consecutiveDays'] ?? 0;
|
||||
if ($days >= 3) {
|
||||
return "连续{$days}天不触发频繁";
|
||||
}
|
||||
}
|
||||
|
||||
// 特殊处理:首次/再次触发限额
|
||||
if ($field == 'frequentPenalty') {
|
||||
$extra = !empty($item['extra']) ? json_decode($item['extra'], true) : [];
|
||||
$count = $extra['frequentCount'] ?? 0;
|
||||
if ($count == 1) {
|
||||
return '首次触发限额';
|
||||
} elseif ($count > 1) {
|
||||
return '再次触发限额';
|
||||
}
|
||||
}
|
||||
|
||||
return $baseDesc;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,253 +2,372 @@
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\common\controller\Api;
|
||||
use app\store\model\FlowPackageModel;
|
||||
use app\store\model\UserFlowPackageModel;
|
||||
use app\store\model\FlowPackageOrderModel;
|
||||
use think\facade\Config;
|
||||
use app\store\model\UserFlowPackageModel;
|
||||
use think\facade\Log;
|
||||
|
||||
/**
|
||||
* 流量套餐控制器
|
||||
* 流量套餐控制器 - V2版本
|
||||
* 门店端流量采购功能
|
||||
*/
|
||||
class FlowPackageController extends Api
|
||||
class FlowPackageController extends BaseController
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedRight = ['*'];
|
||||
|
||||
/**
|
||||
* 获取流量套餐列表
|
||||
* GET /v2/store/flow-packages
|
||||
*
|
||||
* @return \think\Response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
|
||||
// 查询条件
|
||||
$where = [];
|
||||
|
||||
// 只获取未删除的数据
|
||||
$where[] = ['isDel', '=', 0];
|
||||
|
||||
// 套餐模型
|
||||
$model = new FlowPackageModel();
|
||||
|
||||
// 查询数据
|
||||
$list = $model->where($where)
|
||||
->field('id, name, tag, originalPrice, price, monthlyFlow, duration, privileges')
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
|
||||
// 格式化返回数据,添加计算字段
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
$result[] = [
|
||||
'id' => $item['id'],
|
||||
'name' => $item['name'],
|
||||
'tag' => $item['tag'],
|
||||
'originalPrice' => $item['originalPrice'],
|
||||
'price' => $item['price'],
|
||||
'monthlyFlow' => $item['monthlyFlow'],
|
||||
'duration' => $item['duration'],
|
||||
'discount' => $item->discount,
|
||||
'totalFlow' => $item->totalFlow,
|
||||
'privileges' => $item['privileges'],
|
||||
try {
|
||||
// 查询条件
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['status', '=', 1], // 只获取启用的套餐
|
||||
['companyId', '=', $this->userInfo['companyId']]
|
||||
];
|
||||
|
||||
// 查询数据(包含公司ID和创建用户ID)
|
||||
$list = FlowPackageModel::where($where)
|
||||
->field('id, name, tag, originalPrice, price, monthlyFlow, duration, privileges, companyId, userId, createTime')
|
||||
->order('sort', 'asc')
|
||||
->select();
|
||||
|
||||
// 格式化返回数据,添加计算字段
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
$result[] = [
|
||||
'id' => $item['id'],
|
||||
'name' => $item['name'],
|
||||
'tag' => $item['tag'],
|
||||
'originalPrice' => $item['originalPrice'],
|
||||
'price' => $item['price'],
|
||||
'monthlyFlow' => $item['monthlyFlow'],
|
||||
'duration' => $item['duration'],
|
||||
'discount' => $item->discount,
|
||||
'totalFlow' => $item->totalFlow,
|
||||
'privileges' => $item['privileges'],
|
||||
'createTime' => !empty($item['createTime']) ? date('Y-m-d H:i:s', $item['createTime']) : '', // 创建时间
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取流量套餐列表失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
return successJson($result, '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取流量套餐详情
|
||||
* GET /v2/store/flow-packages/:id
|
||||
*
|
||||
* @param int $id 套餐ID
|
||||
* @return \think\Response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail($id)
|
||||
{
|
||||
if (empty($id)) {
|
||||
return errorJson('参数错误');
|
||||
try {
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 查询数据
|
||||
$info = FlowPackageModel::where('id', $id)
|
||||
->where('isDel', 0)
|
||||
->find();
|
||||
|
||||
if (empty($info)) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在']);
|
||||
}
|
||||
|
||||
// 格式化返回数据,添加计算字段
|
||||
$result = [
|
||||
'id' => $info['id'],
|
||||
'name' => $info['name'],
|
||||
'tag' => $info['tag'],
|
||||
'originalPrice' => $info['originalPrice'],
|
||||
'price' => $info['price'],
|
||||
'monthlyFlow' => $info['monthlyFlow'],
|
||||
'duration' => $info['duration'],
|
||||
'discount' => $info->discount,
|
||||
'totalFlow' => $info->totalFlow,
|
||||
'privileges' => $info['privileges'],
|
||||
'companyId' => $info['companyId'] ?? 0, // 公司ID
|
||||
'userId' => $info['userId'] ?? 0, // 创建用户ID
|
||||
'createTime' => !empty($info['createTime']) ? date('Y-m-d H:i:s', $info['createTime']) : '', // 创建时间
|
||||
];
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取流量套餐详情失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
// 套餐模型
|
||||
$model = new FlowPackageModel();
|
||||
|
||||
// 查询数据
|
||||
$info = $model->where('id', $id)->where('isDel', 0)->find();
|
||||
|
||||
if (empty($info)) {
|
||||
return errorJson('套餐不存在');
|
||||
}
|
||||
|
||||
// 格式化返回数据,添加计算字段
|
||||
$result = [
|
||||
'id' => $info['id'],
|
||||
'name' => $info['name'],
|
||||
'tag' => $info['tag'],
|
||||
'originalPrice' => $info['originalPrice'],
|
||||
'price' => $info['price'],
|
||||
'monthlyFlow' => $info['monthlyFlow'],
|
||||
'duration' => $info['duration'],
|
||||
'discount' => $info->discount,
|
||||
'totalFlow' => $info->totalFlow,
|
||||
'privileges' => $info['privileges'],
|
||||
];
|
||||
|
||||
return successJson($result, '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 展示用户流量套餐使用情况
|
||||
* 获取剩余流量
|
||||
* GET /v2/store/flow-packages/remaining-flow
|
||||
*
|
||||
* @return \think\Response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function remainingFlow()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
|
||||
$userInfo = request()->userInfo;
|
||||
// 获取用户ID,通常应该从会话或令牌中获取
|
||||
$userId = $userInfo['id'];
|
||||
|
||||
if (empty($userId)) {
|
||||
return errorJson('请先登录');
|
||||
}
|
||||
|
||||
// 获取用户当前有效的流量套餐
|
||||
$userPackage = UserFlowPackageModel::getUserActivePackage($userId);
|
||||
try {
|
||||
// 从认证中间件获取用户信息
|
||||
$userInfo = $this->request->userInfo ?? [];
|
||||
$userId = $userInfo['id'] ?? 0;
|
||||
|
||||
if (empty($userPackage)) {
|
||||
return errorJson('您没有有效的流量套餐');
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取用户当前有效的流量套餐
|
||||
$userPackage = UserFlowPackageModel::getUserActivePackage($userId);
|
||||
|
||||
if (empty($userPackage)) {
|
||||
return json(['code' => 404, 'msg' => '您没有有效的流量套餐']);
|
||||
}
|
||||
|
||||
// 获取套餐详情
|
||||
$packageId = $userPackage['packageId'];
|
||||
$flowPackage = FlowPackageModel::where('id', $packageId)
|
||||
->where('isDel', 0)
|
||||
->find();
|
||||
|
||||
if (empty($flowPackage)) {
|
||||
return json(['code' => 404, 'msg' => '套餐信息不存在']);
|
||||
}
|
||||
|
||||
// 计算剩余流量
|
||||
$totalFlow = intval($userPackage['totalFlow'] ?? $flowPackage->totalFlow ?? 0); // 总流量
|
||||
$usedFlow = intval($userPackage['usedFlow'] ?? 0); // 已使用流量
|
||||
$remainingFlow = $totalFlow - $usedFlow; // 剩余流量
|
||||
$remainingFlow = $remainingFlow > 0 ? $remainingFlow : 0; // 确保不为负数
|
||||
|
||||
// 计算剩余天数
|
||||
$now = time();
|
||||
$expireTime = intval($userPackage['expireTime'] ?? 0);
|
||||
$duration = intval($userPackage['duration'] ?? 0);
|
||||
|
||||
if ($expireTime <= 0) {
|
||||
return json(['code' => 400, 'msg' => '套餐数据异常,到期时间无效']);
|
||||
}
|
||||
|
||||
$remainingDays = ceil(($expireTime - $now) / 86400); // 向上取整,剩余天数
|
||||
$remainingDays = $remainingDays > 0 ? $remainingDays : 0; // 确保不为负数
|
||||
|
||||
// 剩余百分比
|
||||
$flowPercentage = $totalFlow > 0 ? round(($remainingFlow / $totalFlow) * 100, 1) : 0;
|
||||
$timePercentage = $duration > 0 ?
|
||||
round(($remainingDays / ($duration * 30)) * 100, 1) : 0;
|
||||
|
||||
// 返回数据
|
||||
$result = [
|
||||
'packageName' => $flowPackage['name'], // 套餐名称
|
||||
'remainingFlow' => $remainingFlow, // 剩余流量(人)
|
||||
'totalFlow' => $totalFlow, // 总流量(人)
|
||||
'flowPercentage' => $flowPercentage, // 剩余流量百分比
|
||||
'remainingDays' => $remainingDays, // 剩余天数
|
||||
'totalDays' => $duration * 30, // 总天数(按30天/月计算)
|
||||
'timePercentage' => $timePercentage, // 剩余时间百分比
|
||||
'expireTime' => date('Y-m-d', $expireTime), // 到期日期
|
||||
'startTime' => date('Y-m-d', $userPackage['startTime']), // 开始日期
|
||||
];
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => $result
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取剩余流量失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
|
||||
// 获取套餐详情
|
||||
$packageId = $userPackage['packageId'];
|
||||
$flowPackage = FlowPackageModel::where('id', $packageId)->where('isDel', 0)->find();
|
||||
|
||||
if (empty($flowPackage)) {
|
||||
return errorJson('套餐信息不存在');
|
||||
}
|
||||
|
||||
// 计算剩余流量
|
||||
$totalFlow = $userPackage['totalFlow'] ?? $flowPackage->totalFlow; // 总流量
|
||||
$usedFlow = $userPackage['usedFlow'] ?? 0; // 已使用流量
|
||||
$remainingFlow = $totalFlow - $usedFlow; // 剩余流量
|
||||
$remainingFlow = $remainingFlow > 0 ? $remainingFlow : 0; // 确保不为负数
|
||||
|
||||
// 计算剩余天数
|
||||
$now = time();
|
||||
$expireTime = $userPackage['expireTime'];
|
||||
$remainingDays = ceil(($expireTime - $now) / 86400); // 向上取整,剩余天数
|
||||
$remainingDays = $remainingDays > 0 ? $remainingDays : 0; // 确保不为负数
|
||||
|
||||
// 剩余百分比
|
||||
$flowPercentage = $totalFlow > 0 ? round(($remainingFlow / $totalFlow) * 100, 1) : 0;
|
||||
$timePercentage = $userPackage['duration'] > 0 ?
|
||||
round(($remainingDays / ($userPackage['duration'] * 30)) * 100, 1) : 0;
|
||||
|
||||
// 返回数据
|
||||
$result = [
|
||||
'packageName' => $flowPackage['name'], // 套餐名称
|
||||
'remainingFlow' => $remainingFlow, // 剩余流量(人)
|
||||
'totalFlow' => $totalFlow, // 总流量(人)
|
||||
'flowPercentage' => $flowPercentage, // 剩余流量百分比
|
||||
'remainingDays' => $remainingDays, // 剩余天数
|
||||
'totalDays' => $userPackage['duration'] * 30, // 总天数(按30天/月计算)
|
||||
'timePercentage' => $timePercentage, // 剩余时间百分比
|
||||
'expireTime' => date('Y-m-d', $expireTime), // 到期日期
|
||||
'startTime' => date('Y-m-d', $userPackage['startTime']), // 开始日期
|
||||
];
|
||||
|
||||
return successJson($result, '获取成功');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建流量采购订单
|
||||
* POST /v2/store/flow-packages/order
|
||||
*
|
||||
* @return \think\Response
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createOrder()
|
||||
{
|
||||
$params = $this->request->param();
|
||||
|
||||
$userInfo = request()->userInfo;
|
||||
// 获取用户ID,通常应该从会话或令牌中获取
|
||||
$userId = $userInfo['id'];
|
||||
|
||||
if (empty($userId)) {
|
||||
return errorJson('请先登录');
|
||||
}
|
||||
|
||||
// 获取套餐ID
|
||||
$packageId = isset($params['packageId']) ? intval($params['packageId']) : 0;
|
||||
|
||||
if (empty($packageId)) {
|
||||
return errorJson('请选择套餐');
|
||||
}
|
||||
|
||||
// 查询套餐信息
|
||||
$flowPackage = FlowPackageModel::where('id', $packageId)->where('isDel', 0)->find();
|
||||
|
||||
if (empty($flowPackage)) {
|
||||
return errorJson('套餐不存在');
|
||||
}
|
||||
|
||||
// 获取支付方式(可选)
|
||||
$payType = isset($params['payType']) ? $params['payType'] : 'wechat';
|
||||
|
||||
// 套餐价格和信息
|
||||
$amount = floatval($flowPackage['price']);
|
||||
$packageName = $flowPackage['name'];
|
||||
$duration = intval($flowPackage['duration']);
|
||||
$remark = isset($params['remark']) ? $params['remark'] : '';
|
||||
|
||||
// 处理金额为0的特殊情况
|
||||
if ($amount <= 0) {
|
||||
// 金额为0,无需支付,直接创建订单并设置为已支付
|
||||
$order = FlowPackageOrderModel::createOrder(
|
||||
$userId,
|
||||
$packageId,
|
||||
$packageName,
|
||||
0,
|
||||
$duration,
|
||||
'nopay',
|
||||
$remark
|
||||
);
|
||||
try {
|
||||
// 从BaseController获取用户信息
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (!$order) {
|
||||
return errorJson('订单创建失败');
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 创建用户流量套餐记录
|
||||
$this->createUserFlowPackage($userId, $packageId, $order['id']);
|
||||
|
||||
// 返回成功信息
|
||||
return successJson(['orderNo' => $order['orderNo'],'status' => 'success'], '购买成功');
|
||||
} else {
|
||||
// 创建正常需要支付的订单
|
||||
$order = FlowPackageOrderModel::createOrder(
|
||||
$userId,
|
||||
$packageId,
|
||||
$packageName,
|
||||
$amount,
|
||||
$duration,
|
||||
$payType,
|
||||
$remark
|
||||
);
|
||||
|
||||
if (!$order) {
|
||||
return errorJson('订单创建失败');
|
||||
if (empty($companyId)) {
|
||||
return json(['code' => 400, 'msg' => '公司信息不存在']);
|
||||
}
|
||||
|
||||
// 返回订单信息,前端需要跳转到支付页面
|
||||
return successJson([
|
||||
'orderNo' => $order['orderNo'],
|
||||
'amount' => $amount,
|
||||
'payType' => $payType,
|
||||
'status' => 'pending'
|
||||
], '订单创建成功');
|
||||
// 获取套餐ID
|
||||
$packageId = $this->request->param('packageId', 0);
|
||||
|
||||
if (empty($packageId)) {
|
||||
return json(['code' => 400, 'msg' => '请选择套餐']);
|
||||
}
|
||||
|
||||
// 查询套餐信息
|
||||
$flowPackage = FlowPackageModel::where('id', $packageId)
|
||||
->where('isDel', 0)
|
||||
->find();
|
||||
|
||||
if (empty($flowPackage)) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在']);
|
||||
}
|
||||
|
||||
// 获取支付方式(可选)
|
||||
$payType = $this->request->param('payType', 'wechat');
|
||||
|
||||
// 套餐价格和信息
|
||||
$amount = floatval($flowPackage['price']);
|
||||
$packageName = $flowPackage['name'];
|
||||
$duration = intval($flowPackage['duration']);
|
||||
$remark = $this->request->param('remark', '');
|
||||
|
||||
// 处理金额为0的特殊情况
|
||||
if ($amount <= 0) {
|
||||
// 金额为0,无需支付,直接创建订单并设置为已支付
|
||||
$order = FlowPackageOrderModel::createOrder(
|
||||
$userId,
|
||||
$companyId,
|
||||
$packageId,
|
||||
$packageName,
|
||||
0,
|
||||
$duration,
|
||||
'nopay',
|
||||
$remark
|
||||
);
|
||||
|
||||
if (!$order) {
|
||||
return json(['code' => 500, 'msg' => '订单创建失败']);
|
||||
}
|
||||
|
||||
// 创建用户流量套餐记录
|
||||
$this->createUserFlowPackage($userId, $packageId, $order['id']);
|
||||
|
||||
// 返回成功信息
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '购买成功',
|
||||
'data' => [
|
||||
'orderNo' => $order['orderNo'],
|
||||
'status' => 'success'
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
// 创建正常需要支付的订单
|
||||
$order = FlowPackageOrderModel::createOrder(
|
||||
$userId,
|
||||
$companyId,
|
||||
$packageId,
|
||||
$packageName,
|
||||
$amount,
|
||||
$duration,
|
||||
$payType,
|
||||
$remark
|
||||
);
|
||||
|
||||
if (!$order) {
|
||||
return json(['code' => 500, 'msg' => '订单创建失败']);
|
||||
}
|
||||
|
||||
// 返回订单信息,前端需要跳转到支付页面
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '订单创建成功',
|
||||
'data' => [
|
||||
'orderNo' => $order['orderNo'],
|
||||
'amount' => $amount,
|
||||
'payType' => $payType,
|
||||
'status' => 'pending'
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('创建订单失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '创建订单失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
* GET /v2/store/flow-packages/orders
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getOrderList()
|
||||
{
|
||||
try {
|
||||
// 从认证中间件获取用户信息
|
||||
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', 10));
|
||||
$status = $this->request->param('status', ''); // 订单状态筛选
|
||||
|
||||
// 确保分页参数有效
|
||||
$page = $page > 0 ? $page : 1;
|
||||
$limit = $limit > 0 ? $limit : 10;
|
||||
|
||||
$where = [
|
||||
['userId', '=', $this->userInfo['id']],
|
||||
['companyId', '=', $this->userInfo['companyId']], // 按公司ID查询
|
||||
['isDel', '=', 0]
|
||||
];
|
||||
|
||||
if ($status !== '' && $status !== null) {
|
||||
$status = intval($status);
|
||||
$where[] = ['status', '=', $status];
|
||||
}
|
||||
|
||||
$query = FlowPackageOrderModel::where($where)
|
||||
->order('id', 'desc');
|
||||
|
||||
$list = $query->page($page, $limit)->select();
|
||||
$total = $query->count();
|
||||
|
||||
// 格式化数据
|
||||
foreach ($list as &$item) {
|
||||
$item['createTime'] = !empty($item['createTime']) && is_numeric($item['createTime']) ? date('Y-m-d H:i:s', intval($item['createTime'])) : '';
|
||||
$item['payTime'] = !empty($item['payTime']) && is_numeric($item['payTime']) ? date('Y-m-d H:i:s', intval($item['payTime'])) : '';
|
||||
}
|
||||
unset($item);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取订单列表失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +382,9 @@ class FlowPackageController extends Api
|
||||
private function createUserFlowPackage($userId, $packageId, $orderId)
|
||||
{
|
||||
// 获取套餐信息
|
||||
$flowPackage = FlowPackageModel::where('id', $packageId)->where('isDel', 0)->find();
|
||||
$flowPackage = FlowPackageModel::where('id', $packageId)
|
||||
->where('isDel', 0)
|
||||
->find();
|
||||
|
||||
if (empty($flowPackage)) {
|
||||
return false;
|
||||
@@ -273,23 +394,21 @@ class FlowPackageController extends Api
|
||||
$now = time();
|
||||
$expireTime = $now + (intval($flowPackage['duration']) * 30 * 86400);
|
||||
|
||||
// 用户流量套餐数据
|
||||
// 用户流量套餐数据(注意:ck_user_flow_package表没有packageName和monthlyFlow字段)
|
||||
$data = [
|
||||
'userId' => $userId,
|
||||
'packageId' => $packageId,
|
||||
'orderId' => $orderId,
|
||||
'packageName' => $flowPackage['name'],
|
||||
'monthlyFlow' => $flowPackage['monthlyFlow'],
|
||||
'duration' => $flowPackage['duration'],
|
||||
'totalFlow' => $flowPackage->totalFlow, // 使用计算属性获取总流量
|
||||
'usedFlow' => 0,
|
||||
'startTime' => $now,
|
||||
'expireTime' => $expireTime,
|
||||
'status' => 1, // 1:有效 0:无效
|
||||
'isDel' => 0
|
||||
];
|
||||
|
||||
// 创建用户流量套餐记录
|
||||
return UserFlowPackageModel::create($data) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\common\util\JwtUtil;
|
||||
use think\Db;
|
||||
use think\Controller;
|
||||
|
||||
class LoginController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$deviceId = $this->request->param('deviceId', '');
|
||||
if (empty($deviceId)) {
|
||||
return errorJson('缺少必要参数');
|
||||
}
|
||||
|
||||
$user = Db::name('users')->alias('u')
|
||||
->field('u.*')
|
||||
->join('device_user du', 'u.id = du.userId and u.companyId = du.companyId')
|
||||
->join('device d', 'du.deviceId = d.id and u.companyId = du.companyId')
|
||||
->where(['d.deviceImei' => $deviceId, 'u.deleteTime' => 0, 'du.deleteTime' => 0, 'd.deleteTime' => 0])
|
||||
->find();
|
||||
if (empty($user)) {
|
||||
return errorJson('用户不存在');
|
||||
}
|
||||
$member = array_merge($user, [
|
||||
'lastLoginIp' => $this->request->ip(),
|
||||
'lastLoginTime' => time()
|
||||
]);
|
||||
|
||||
// 生成JWT令牌
|
||||
$token = JwtUtil::createToken($user, 86400 * 30);
|
||||
$token_expired = time() + 86400 * 30;
|
||||
|
||||
$data = [
|
||||
'member' => $member,
|
||||
'token' => $token,
|
||||
'token_expired' => $token_expired
|
||||
];
|
||||
return successJson($data, '登录成功');
|
||||
}
|
||||
}
|
||||
@@ -1,482 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\store\model\WechatFriendModel;
|
||||
use app\store\model\WechatMessageModel;
|
||||
use app\store\model\TrafficOrderModel;
|
||||
use think\Db;
|
||||
|
||||
|
||||
/**
|
||||
* 数据统计控制器
|
||||
*/
|
||||
class StatisticsController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取数据概览
|
||||
*/
|
||||
public function getOverview()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->userInfo['companyId'];
|
||||
$userId = $this->userInfo['id'];
|
||||
|
||||
// 构建查询条件
|
||||
$deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId');
|
||||
if (empty($deviceIds)) {
|
||||
return errorJson('设备不存在');
|
||||
}
|
||||
$ownerWechatIds = [];
|
||||
foreach ($deviceIds as $deviceId) {
|
||||
$ownerWechatIds[] = Db::name('device_wechat_login')
|
||||
->where(['deviceId' => $deviceId])
|
||||
->order('id DESC')
|
||||
->value('wechatId');
|
||||
}
|
||||
|
||||
$wechatAccountIds = Db::table('s2_wechat_account')->whereIn('wechatId', $ownerWechatIds)->column('id');
|
||||
|
||||
|
||||
// 获取时间范围
|
||||
$timeRange = $this->getTimeRange();
|
||||
$startTime = $timeRange['start_time'];
|
||||
$endTime = $timeRange['end_time'];
|
||||
$lastStartTime = $timeRange['last_start_time'];
|
||||
$lastEndTime = $timeRange['last_end_time'];
|
||||
|
||||
|
||||
// 1. 总客户数
|
||||
$totalCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('isDeleted', 0)
|
||||
->whereTime('createTime', '>=', $startTime)
|
||||
->whereTime('createTime', '<', $endTime)
|
||||
->count();
|
||||
|
||||
// 上期总客户数
|
||||
$lastTotalCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->whereTime('createTime', '>=', $lastStartTime)
|
||||
->whereTime('createTime', '<', $lastEndTime)
|
||||
->count();
|
||||
|
||||
// 2. 新增客户数
|
||||
$newCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->whereTime('createTime', '>=', $startTime)
|
||||
->whereTime('createTime', '<', $endTime)
|
||||
->count();
|
||||
|
||||
// 上期新增客户数
|
||||
$lastNewCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->whereTime('createTime', '>=', $lastStartTime)
|
||||
->whereTime('createTime', '<', $lastEndTime)
|
||||
->count();
|
||||
|
||||
//3. 互动次数
|
||||
$interactionCount = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->count();
|
||||
|
||||
// 上期互动次数
|
||||
$lastInteractionCount = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $lastStartTime)
|
||||
->where('createTime', '<', $lastEndTime)
|
||||
->count();
|
||||
|
||||
// 4. RFM 平均值计算(不查询上期数据)
|
||||
$rfmStats = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('isDeleted', 0)
|
||||
->field('AVG(`R`) as avgR, AVG(`F`) as avgF, AVG(`M`) as avgM')
|
||||
->find();
|
||||
|
||||
// 处理查询结果,如果字段为null则默认为0
|
||||
$avgR = isset($rfmStats['avgR']) && $rfmStats['avgR'] !== null ? round((float)$rfmStats['avgR'], 2) : 0;
|
||||
$avgF = isset($rfmStats['avgF']) && $rfmStats['avgF'] !== null ? round((float)$rfmStats['avgF'], 2) : 0;
|
||||
$avgM = isset($rfmStats['avgM']) && $rfmStats['avgM'] !== null ? round((float)$rfmStats['avgM'], 2) : 0;
|
||||
|
||||
// 计算三者的平均值
|
||||
$avgRFM = ($avgR + $avgF + $avgM) / 3;
|
||||
$avgRFM = round($avgRFM, 2);
|
||||
|
||||
// 计算环比增长率
|
||||
$customerGrowth = $this->calculateGrowth($totalCustomers, $lastTotalCustomers);
|
||||
$newCustomerGrowth = $this->calculateGrowth($newCustomers, $lastNewCustomers);
|
||||
$interactionGrowth = $this->calculateGrowth($interactionCount, $lastInteractionCount);
|
||||
$data = [
|
||||
'total_customers' => [
|
||||
'value' => $totalCustomers,
|
||||
'growth' => $customerGrowth
|
||||
],
|
||||
'new_customers' => [
|
||||
'value' => $newCustomers,
|
||||
'growth' => $newCustomerGrowth
|
||||
],
|
||||
'interaction_count' => [
|
||||
'value' => $interactionCount,
|
||||
'growth' => $interactionGrowth
|
||||
],
|
||||
'conversion_rate' => [
|
||||
'value' => 10,
|
||||
'growth' => 15
|
||||
],
|
||||
'account_value' => [
|
||||
'avg_r' => $avgR,
|
||||
'avg_f' => $avgF,
|
||||
'avg_m' => $avgM,
|
||||
'avg_rfm' => $avgRFM
|
||||
]
|
||||
];
|
||||
|
||||
return successJson($data);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取数据概览失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取综合分析数据
|
||||
*/
|
||||
public function getComprehensiveAnalysis()
|
||||
{
|
||||
try {
|
||||
$companyId = $this->userInfo['companyId'];
|
||||
$userId = $this->userInfo['id'];
|
||||
|
||||
// 构建查询条件
|
||||
$deviceIds = Db::name('device_user')->where(['userId' => $userId, 'companyId' => $companyId])->order('id DESC')->column('deviceId');
|
||||
if (empty($deviceIds)) {
|
||||
return errorJson('设备不存在');
|
||||
}
|
||||
$ownerWechatIds = [];
|
||||
foreach ($deviceIds as $deviceId) {
|
||||
$ownerWechatIds[] = Db::name('device_wechat_login')
|
||||
->where(['deviceId' => $deviceId])
|
||||
->order('id DESC')
|
||||
->value('wechatId');
|
||||
}
|
||||
$wechatAccountIds = Db::table('s2_wechat_account')->whereIn('wechatId', $ownerWechatIds)->column('id');
|
||||
|
||||
// 获取时间范围
|
||||
$timeRange = $this->getTimeRange();
|
||||
$startTime = $timeRange['start_time'];
|
||||
$endTime = $timeRange['end_time'];
|
||||
$lastStartTime = $timeRange['last_start_time'];
|
||||
$lastEndTime = $timeRange['last_end_time'];
|
||||
|
||||
// ========== 1. 客户平均转化金额 ==========
|
||||
// 获取有订单的客户数(去重)
|
||||
$convertedCustomers = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->group('identifier')
|
||||
->column('identifier');
|
||||
$convertedCustomerCount = count($convertedCustomers);
|
||||
|
||||
// 总销售额
|
||||
$totalSales = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->sum('actualPay');
|
||||
$totalSales = $totalSales ?: 0;
|
||||
|
||||
// 客户平均转化金额
|
||||
$avgConversionAmount = $convertedCustomerCount > 0 ? round($totalSales / $convertedCustomerCount, 2) : 0;
|
||||
|
||||
// ========== 2. 价值指标 ==========
|
||||
// 销售总额(已计算)
|
||||
|
||||
// 平均订单金额(总订单数)
|
||||
$totalOrderCount = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->count();
|
||||
$avgOrderAmount = $totalOrderCount > 0 ? round($totalSales / $totalOrderCount, 2) : 0;
|
||||
|
||||
// 高价值客户(消费超过平均订单金额的客户)
|
||||
// 先获取每个客户的消费总额
|
||||
$customerTotalSpend = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->field('identifier, SUM(actualPay) as totalSpend')
|
||||
->group('identifier')
|
||||
->select();
|
||||
|
||||
$highValueCustomerCount = 0;
|
||||
$avgCustomerSpend = $convertedCustomerCount > 0 ? ($totalSales / $convertedCustomerCount) : 0;
|
||||
foreach ($customerTotalSpend as $customer) {
|
||||
if ($customer['totalSpend'] > $avgCustomerSpend) {
|
||||
$highValueCustomerCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// 高价值客户百分比
|
||||
$totalCustomersForCalc = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('isDeleted', 0)
|
||||
->count();
|
||||
$highValueCustomerPercent = $totalCustomersForCalc > 0 ? round(($highValueCustomerCount / $totalCustomersForCalc) * 100, 1) : 0;
|
||||
|
||||
// ========== 3. 增长趋势 ==========
|
||||
// 上期销售额
|
||||
$lastTotalSales = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $lastStartTime)
|
||||
->where('createTime', '<', $lastEndTime)
|
||||
->sum('actualPay');
|
||||
$lastTotalSales = $lastTotalSales ?: 0;
|
||||
|
||||
// 周收益增长(金额差值)
|
||||
$weeklyRevenueGrowth = round($totalSales - $lastTotalSales, 2);
|
||||
|
||||
// 新客转化(新客户中有订单的人数)
|
||||
$newCustomers = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->column('wechatId');
|
||||
|
||||
// 获取新客户中有订单的(identifier 对应 wechatId)
|
||||
$newConvertedCustomers = 0;
|
||||
if (!empty($newCustomers)) {
|
||||
$newConvertedCustomers = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->whereIn('identifier', $newCustomers)
|
||||
->group('identifier')
|
||||
->count();
|
||||
}
|
||||
|
||||
// 活跃客户增长(有互动的客户)
|
||||
$activeCustomers = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->group('wechatFriendId')
|
||||
->count();
|
||||
|
||||
$lastActiveCustomers = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $lastStartTime)
|
||||
->where('createTime', '<', $lastEndTime)
|
||||
->group('wechatFriendId')
|
||||
->count();
|
||||
|
||||
// 活跃客户增长(人数差值)
|
||||
$activeCustomerGrowth = $activeCustomers - $lastActiveCustomers;
|
||||
|
||||
// ========== 4. 客户活跃度 ==========
|
||||
// 按天统计每个客户的互动次数,然后分类
|
||||
// 高频互动用户数(平均每天3次以上)
|
||||
$days = max(1, ($endTime - $startTime) / 86400); // 计算天数
|
||||
$highFrequencyThreshold = $days * 3; // 高频阈值
|
||||
|
||||
$highFrequencyUsers = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->field('wechatFriendId, COUNT(*) as count')
|
||||
->group('wechatFriendId')
|
||||
->having('count > ' . $highFrequencyThreshold)
|
||||
->count();
|
||||
|
||||
// 中频互动用户数(平均每天1-3次)
|
||||
$midFrequencyThreshold = $days * 1;
|
||||
$midFrequencyUsers = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->field('wechatFriendId, COUNT(*) as count')
|
||||
->group('wechatFriendId')
|
||||
->having('count >= ' . $midFrequencyThreshold . ' AND count <= ' . $highFrequencyThreshold)
|
||||
->count();
|
||||
|
||||
// 低频互动用户数(少于平均每天1次)
|
||||
$lowFrequencyUsers = WechatMessageModel::whereIn('wechatAccountId', $wechatAccountIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->field('wechatFriendId, COUNT(*) as count')
|
||||
->group('wechatFriendId')
|
||||
->having('count < ' . $midFrequencyThreshold)
|
||||
->count();
|
||||
|
||||
$frequency_analysis = [
|
||||
['name' => '高频', 'value' => $highFrequencyUsers],
|
||||
['name' => '中频', 'value' => $midFrequencyUsers],
|
||||
['name' => '低频', 'value' => $lowFrequencyUsers]
|
||||
];
|
||||
|
||||
// ========== 5. 转化客户来源 ==========
|
||||
// 只统计有订单的客户来源(identifier 对应 wechatId)
|
||||
$convertedFriendIds = TrafficOrderModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->where('createTime', '>=', $startTime)
|
||||
->where('createTime', '<', $endTime)
|
||||
->group('identifier')
|
||||
->column('identifier');
|
||||
|
||||
$friendRecommend = 0;
|
||||
$wechatSearch = 0;
|
||||
$wechatGroup = 0;
|
||||
|
||||
if (!empty($convertedFriendIds)) {
|
||||
// 朋友推荐(有订单的)
|
||||
$friendRecommend = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->whereIn('wechatId', $convertedFriendIds)
|
||||
->whereIn('addFrom', [17, 1000017])
|
||||
->count();
|
||||
|
||||
// 微信搜索(有订单的)
|
||||
$wechatSearch = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->whereIn('wechatId', $convertedFriendIds)
|
||||
->whereIn('addFrom', [3, 15, 1000003, 1000015])
|
||||
->count();
|
||||
|
||||
// 微信群(有订单的)
|
||||
$wechatGroup = WechatFriendModel::whereIn('ownerWechatId', $ownerWechatIds)
|
||||
->whereIn('wechatId', $convertedFriendIds)
|
||||
->whereIn('addFrom', [14, 1000014])
|
||||
->count();
|
||||
}
|
||||
|
||||
$totalConvertedCustomers = $convertedCustomerCount;
|
||||
$otherSource = max(0, $totalConvertedCustomers - $friendRecommend - $wechatSearch - $wechatGroup);
|
||||
|
||||
// 计算百分比
|
||||
$calculatePercentage = function ($value) use ($totalConvertedCustomers) {
|
||||
if ($totalConvertedCustomers <= 0) return 0;
|
||||
return round(($value / $totalConvertedCustomers) * 100, 2);
|
||||
};
|
||||
|
||||
$sourceDistribution = [
|
||||
[
|
||||
'name' => '朋友推荐',
|
||||
'value' => $calculatePercentage($friendRecommend) . '%',
|
||||
'count' => $friendRecommend
|
||||
],
|
||||
[
|
||||
'name' => '微信搜索',
|
||||
'value' => $calculatePercentage($wechatSearch) . '%',
|
||||
'count' => $wechatSearch
|
||||
],
|
||||
[
|
||||
'name' => '微信群',
|
||||
'value' => $calculatePercentage($wechatGroup) . '%',
|
||||
'count' => $wechatGroup
|
||||
]
|
||||
];
|
||||
|
||||
// 构建返回数据
|
||||
$data = [
|
||||
'avg_conversion_amount' => $avgConversionAmount, // 客户平均转化金额
|
||||
'value_indicators' => [
|
||||
'total_sales' => round($totalSales, 2), // 销售总额
|
||||
'avg_order_amount' => $avgOrderAmount, // 平均订单金额
|
||||
'high_value_customers' => $highValueCustomerPercent . '%' // 高价值客户
|
||||
],
|
||||
'growth_trend' => [
|
||||
'weekly_revenue_growth' => $weeklyRevenueGrowth, // 周收益增长(金额)
|
||||
'new_customer_conversion' => $newConvertedCustomers, // 新客转化(人数)
|
||||
'active_customer_growth' => $activeCustomerGrowth // 活跃客户增长(人数差值)
|
||||
],
|
||||
'frequency_analysis' => $frequency_analysis, // 客户活跃度
|
||||
'source_distribution' => $sourceDistribution // 转化客户来源
|
||||
];
|
||||
|
||||
return successJson($data);
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('获取互动分析数据失败:' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取时间范围
|
||||
*
|
||||
* @param bool $toTimestamp 是否将日期转为时间戳,默认为true
|
||||
* @return array 时间范围数组
|
||||
*/
|
||||
private function getTimeRange($toTimestamp = true)
|
||||
{
|
||||
// 可选:today, yesterday, this_week, last_week, this_month, this_quarter, this_year
|
||||
$timeType = input('time_type', 'this_week');
|
||||
|
||||
switch ($timeType) {
|
||||
case 'today': // 今日
|
||||
$startTime = date('Y-m-d');
|
||||
$endTime = date('Y-m-d', strtotime('+1 day'));
|
||||
$lastStartTime = date('Y-m-d', strtotime('-1 day')); // 昨日
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
case 'yesterday': // 昨日
|
||||
$startTime = date('Y-m-d', strtotime('-1 day'));
|
||||
$endTime = date('Y-m-d');
|
||||
$lastStartTime = date('Y-m-d', strtotime('-2 day')); // 前日
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
case 'this_week': // 本周
|
||||
$startTime = date('Y-m-d', strtotime('monday this week'));
|
||||
$endTime = date('Y-m-d', strtotime('monday next week'));
|
||||
$lastStartTime = date('Y-m-d', strtotime('monday last week')); // 上周一
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
case 'last_week': // 上周
|
||||
$startTime = date('Y-m-d', strtotime('monday last week'));
|
||||
$endTime = date('Y-m-d', strtotime('monday this week'));
|
||||
$lastStartTime = date('Y-m-d', strtotime('monday last week', strtotime('last week'))); // 上上周一
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
case 'this_month': // 本月
|
||||
$startTime = date('Y-m-01');
|
||||
$endTime = date('Y-m-d', strtotime(date('Y-m-01') . ' +1 month'));
|
||||
$lastStartTime = date('Y-m-01', strtotime('-1 month')); // 上月初
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
case 'this_quarter': // 本季度
|
||||
$month = date('n');
|
||||
$quarter = ceil($month / 3);
|
||||
$startMonth = ($quarter - 1) * 3 + 1;
|
||||
$startTime = date('Y-') . str_pad($startMonth, 2, '0', STR_PAD_LEFT) . '-01';
|
||||
$endTime = date('Y-m-d', strtotime($startTime . ' +3 month'));
|
||||
// 上季度
|
||||
$lastStartTime = date('Y-m-d', strtotime($startTime . ' -3 month'));
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
case 'this_year': // 本年度
|
||||
$startTime = date('Y-01-01');
|
||||
$endTime = (date('Y') + 1) . '-01-01';
|
||||
$lastStartTime = (date('Y') - 1) . '-01-01'; // 去年初
|
||||
$lastEndTime = $startTime;
|
||||
break;
|
||||
|
||||
default:
|
||||
$startTime = date('Y-m-d', strtotime('monday this week'));
|
||||
$endTime = date('Y-m-d', strtotime('monday next week'));
|
||||
$lastStartTime = date('Y-m-d', strtotime('monday last week'));
|
||||
$lastEndTime = $startTime;
|
||||
}
|
||||
|
||||
// 如果需要转换为时间戳
|
||||
if ($toTimestamp) {
|
||||
$startTime = strtotime($startTime);
|
||||
$endTime = strtotime($endTime);
|
||||
$lastStartTime = strtotime($lastStartTime);
|
||||
$lastEndTime = strtotime($lastEndTime);
|
||||
}
|
||||
|
||||
return [
|
||||
'start_time' => $startTime,
|
||||
'end_time' => $endTime,
|
||||
'last_start_time' => $lastStartTime,
|
||||
'last_end_time' => $lastEndTime
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算环比增长率
|
||||
*/
|
||||
private function calculateGrowth($current, $last)
|
||||
{
|
||||
if ($last == 0) {
|
||||
return $current > 0 ? 100 : 0;
|
||||
}
|
||||
return round((($current - $last) / $last) * 100, 1);
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\store\controller\BaseController;
|
||||
|
||||
|
||||
/**
|
||||
* 系统设置控制器
|
||||
*/
|
||||
class SystemConfigController extends BaseController
|
||||
{
|
||||
protected $noNeedLogin = [];
|
||||
protected $noNeedRight = ['*'];
|
||||
|
||||
/**
|
||||
* 获取系统开关状态
|
||||
*
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function getSwitchStatus()
|
||||
{
|
||||
try {
|
||||
// 获取设备ID
|
||||
$deviceId = $this->device['id'] ?? 0;
|
||||
if (!$deviceId) {
|
||||
return $this->error('设备不存在');
|
||||
}
|
||||
|
||||
// 从新表中获取配置
|
||||
$config = Db::name('device_taskconf')
|
||||
->where('deviceId', $deviceId)
|
||||
->field('id,autoLike,autoCustomerDev,groupMessageDeliver,autoGroup,contentSync,aiChat,autoReply,momentsSync')
|
||||
->find();
|
||||
|
||||
// 如果没有找到配置,创建默认配置
|
||||
if (empty($config)) {
|
||||
$taskConfig = [
|
||||
'deviceId' => $deviceId,
|
||||
'autoLike' => 0,
|
||||
'autoCustomerDev' => 0,
|
||||
'groupMessageDeliver' => 0,
|
||||
'autoGroup' => 0,
|
||||
'contentSync' => 0,
|
||||
'aiChat' => 0,
|
||||
'autoReply' => 0,
|
||||
'momentsSync' => 0,
|
||||
'companyId' => $this->device['companyId'] ?? 0,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 添加到数据库
|
||||
Db::name('device_taskconf')->insert($taskConfig);
|
||||
|
||||
// 返回默认配置
|
||||
return successJson($taskConfig);
|
||||
}
|
||||
|
||||
// 返回开关状态
|
||||
return successJson($config);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取开关状态异常:' . $e->getMessage());
|
||||
return $this->error('获取开关状态失败');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统开关状态
|
||||
*
|
||||
* @return \think\Response
|
||||
*/
|
||||
public function updateSwitchStatus()
|
||||
{
|
||||
try {
|
||||
// 获取参数
|
||||
if (empty($this->device)) {
|
||||
return errorJson('设备不存在');
|
||||
}
|
||||
|
||||
$switchName = $this->request->param('switchName');
|
||||
$deviceId = $this->device['id'];
|
||||
|
||||
if (empty($switchName)) {
|
||||
return errorJson('开关名称不能为空');
|
||||
}
|
||||
|
||||
// 验证开关名称是否有效
|
||||
$validSwitches = ['autoLike', 'autoCustomerDev', 'groupMessageDeliver', 'autoGroup', 'contentSync', 'aiChat', 'autoReply', 'momentsSync'];
|
||||
if (!in_array($switchName, $validSwitches)) {
|
||||
return errorJson('无效的开关名称');
|
||||
}
|
||||
|
||||
// 获取当前配置
|
||||
$taskConfig = Db::name('device_taskconf')
|
||||
->where('deviceId', $deviceId)
|
||||
->find();
|
||||
|
||||
// 如果没有找到配置,创建默认配置
|
||||
if (empty($taskConfig)) {
|
||||
$taskConfig = [
|
||||
'deviceId' => $deviceId,
|
||||
'autoLike' => 0,
|
||||
'autoCustomerDev' => 0,
|
||||
'groupMessageDeliver' => 0,
|
||||
'autoGroup' => 0,
|
||||
'contentSync' => 0,
|
||||
'aiChat' => 0,
|
||||
'autoReply' => 0,
|
||||
'momentsSync' => 0,
|
||||
'companyId' => $this->device['companyId'] ?? 0,
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 设置要更新的开关
|
||||
$taskConfig[$switchName] = 1;
|
||||
|
||||
// 添加到数据库
|
||||
Db::name('device_taskconf')->insert($taskConfig);
|
||||
} else {
|
||||
// 更新指定开关状态
|
||||
$updateData = [
|
||||
$switchName => !$taskConfig[$switchName],
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 更新数据库
|
||||
$result = Db::name('device_taskconf')
|
||||
->where('deviceId', $deviceId)
|
||||
->update($updateData);
|
||||
|
||||
if ($result === false) {
|
||||
Log::error("更新设备{$switchName}开关状态失败,设备ID:{$deviceId}");
|
||||
return errorJson('更新失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 清除缓存
|
||||
$this->clearDeviceCache();
|
||||
|
||||
return successJson([], '更新成功');
|
||||
|
||||
} catch (\Exception $e) {
|
||||
return errorJson('系统错误'. $e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
522
application/store/controller/TokensController.php
Normal file
522
application/store/controller/TokensController.php
Normal file
@@ -0,0 +1,522 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\store\model\TokensPackageModel;
|
||||
use app\store\model\TokensCompanyModel;
|
||||
use app\store\model\TokensRecordModel;
|
||||
use app\common\controller\PaymentService;
|
||||
use app\common\model\Order;
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use think\facade\Env;
|
||||
|
||||
/**
|
||||
* 算力中心控制器
|
||||
*/
|
||||
class TokensController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取算力套餐列表
|
||||
* GET /v2/store/tokens/packages
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', 10));
|
||||
|
||||
// 确保分页参数有效
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = 10;
|
||||
|
||||
$where = [
|
||||
['isDel', '=', 0],
|
||||
['status', '=', 1],
|
||||
];
|
||||
|
||||
$query = TokensPackageModel::where($where);
|
||||
$total = $query->count();
|
||||
$list = $query->page($page, $limit)->order('sort ASC,id desc')->select();
|
||||
|
||||
// 格式化数据
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
$originalPrice = floatval($item['originalPrice'] ?? 0) / 100; // 分转元
|
||||
$price = floatval($item['price'] ?? 0) / 100; // 分转元
|
||||
$tokens = intval($item['tokens'] ?? 0);
|
||||
|
||||
// 计算折扣
|
||||
$discount = 0;
|
||||
if ($originalPrice > 0) {
|
||||
$discount = round((($originalPrice - $price) / $originalPrice) * 100, 2);
|
||||
}
|
||||
|
||||
// 计算单价
|
||||
$unitPrice = $tokens > 0 ? round($price / $tokens, 6) : 0;
|
||||
|
||||
$result[] = [
|
||||
'id' => intval($item['id']),
|
||||
'name' => $item['name'] ?? '',
|
||||
'tokens' => number_format($tokens),
|
||||
'price' => round($price, 2),
|
||||
'originalPrice' => round($originalPrice, 2),
|
||||
'discount' => $discount,
|
||||
'unitPrice' => $unitPrice,
|
||||
'description' => $item->description,
|
||||
'sort' => intval($item['sort'] ?? 50),
|
||||
'isTrial' => intval($item['isTrial'] ?? 0),
|
||||
'isRecommend' => intval($item['isRecommend'] ?? 0),
|
||||
'isHot' => intval($item['isHot'] ?? 0),
|
||||
'isVip' => intval($item['isVip'] ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $result,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取算力套餐列表失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 购买算力
|
||||
* POST /v2/store/tokens/pay
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function pay()
|
||||
{
|
||||
try {
|
||||
$id = intval($this->request->param('id', 0));
|
||||
$price = $this->request->param('price', '');
|
||||
$payType = $this->request->param('payType', 'qrCode');
|
||||
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
if (!in_array($payType, ['wechat', 'alipay', 'qrCode'])) {
|
||||
return json(['code' => 400, 'msg' => '付款类型不正确']);
|
||||
}
|
||||
|
||||
if (empty($id) && empty($price)) {
|
||||
return json(['code' => 400, 'msg' => '套餐和自定义购买金额必须选一个']);
|
||||
}
|
||||
|
||||
// 处理套餐或自定义购买
|
||||
if (!empty($id)) {
|
||||
$package = TokensPackageModel::where(['id' => $id, 'status' => 1, 'isDel' => 0])->find();
|
||||
if (empty($package)) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在或者已禁用']);
|
||||
}
|
||||
|
||||
if ($package['price'] <= 0) {
|
||||
return json(['code' => 400, 'msg' => '套餐金额异常']);
|
||||
}
|
||||
|
||||
$specs = [
|
||||
'id' => intval($package['id']),
|
||||
'name' => $package['name'],
|
||||
'price' => intval($package['price']), // 单位:分
|
||||
'tokens' => intval($package['tokens']),
|
||||
];
|
||||
} else {
|
||||
// 获取配置的tokens比例
|
||||
$tokens_multiple = Env::get('payment.tokens_multiple', 20);
|
||||
$specs = [
|
||||
'id' => 0,
|
||||
'name' => '自定义购买算力',
|
||||
'price' => intval(floatval($price) * 100), // 元转分
|
||||
'tokens' => intval(floatval($price) * $tokens_multiple),
|
||||
];
|
||||
}
|
||||
|
||||
// 生成订单号
|
||||
$orderNo = date('YmdHis') . rand(100000, 999999);
|
||||
$order = [
|
||||
'companyId' => $companyId,
|
||||
'userId' => $userId,
|
||||
'orderNo' => $orderNo,
|
||||
'goodsId' => $specs['id'],
|
||||
'goodsName' => $specs['name'],
|
||||
'goodsSpecs' => $specs,
|
||||
'orderType' => 1, // 1=购买算力
|
||||
'money' => $specs['price'],
|
||||
'service' => $payType
|
||||
];
|
||||
|
||||
$paymentService = new PaymentService();
|
||||
$res = $paymentService->createOrder($order);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] == 200) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '订单创建成功',
|
||||
'data' => [
|
||||
'orderNo' => $orderNo,
|
||||
'code_url' => $res['data'] ?? ''
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
return json(['code' => 500, 'msg' => $res['msg'] ?? '订单创建失败']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('购买算力失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '购买失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单状态
|
||||
* GET /v2/store/tokens/order
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function queryOrder()
|
||||
{
|
||||
try {
|
||||
$orderNo = $this->request->param('orderNo', '');
|
||||
|
||||
if (empty($orderNo)) {
|
||||
return json(['code' => 400, 'msg' => '订单号不能为空']);
|
||||
}
|
||||
|
||||
$order = Order::where('orderNo', $orderNo)->find();
|
||||
if (!$order) {
|
||||
return json(['code' => 404, 'msg' => '该订单不存在']);
|
||||
}
|
||||
|
||||
// 如果订单已支付,直接返回
|
||||
if ($order->status == 1) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '订单已支付',
|
||||
'data' => [
|
||||
'orderNo' => $order->orderNo,
|
||||
'status' => $order->status,
|
||||
'payTime' => !empty($order->payTime) && is_numeric($order->payTime) ? date('Y-m-d H:i:s', intval($order->payTime)) : '',
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
// 查询支付状态
|
||||
$paymentService = new PaymentService();
|
||||
$res = $paymentService->queryOrder($orderNo);
|
||||
$res = json_decode($res, true);
|
||||
|
||||
if ($res['code'] == 200) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '订单已支付',
|
||||
'data' => [
|
||||
'orderNo' => $order->orderNo,
|
||||
'status' => 1,
|
||||
'payTime' => !empty($order->payTime) && is_numeric($order->payTime) ? date('Y-m-d H:i:s', intval($order->payTime)) : '',
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
$errorMsg = !empty($order['payInfo']) ? $order['payInfo'] : '订单未支付';
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => $errorMsg,
|
||||
'data' => [
|
||||
'orderNo' => $order->orderNo,
|
||||
'status' => $order->status,
|
||||
]
|
||||
]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('查询订单失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '查询失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取订单列表
|
||||
* GET /v2/store/tokens/orders
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getOrderList()
|
||||
{
|
||||
try {
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', 10));
|
||||
$status = $this->request->param('status', '');
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$orderType = $this->request->param('orderType', '');
|
||||
$payType = $this->request->param('payType', '');
|
||||
$startTime = $this->request->param('startTime', '');
|
||||
$endTime = $this->request->param('endTime', '');
|
||||
|
||||
// 确保分页参数有效
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = 10;
|
||||
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 构建查询条件
|
||||
$where = [
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId]
|
||||
];
|
||||
|
||||
// 关键词搜索(订单号、商品名称)
|
||||
if (!empty($keyword)) {
|
||||
$where[] = ['orderNo|goodsName', 'like', '%' . $keyword . '%'];
|
||||
}
|
||||
|
||||
// 状态筛选 (0-待支付 1-已付款 2-已退款 3-付款失败)
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
// 订单类型筛选
|
||||
if ($orderType !== '') {
|
||||
$where[] = ['orderType', '=', intval($orderType)];
|
||||
}
|
||||
|
||||
// 支付类型筛选
|
||||
if ($payType !== '') {
|
||||
$where[] = ['payType', '=', intval($payType)];
|
||||
}
|
||||
|
||||
// 时间范围筛选
|
||||
if (!empty($startTime)) {
|
||||
$where[] = ['createTime', '>=', strtotime($startTime)];
|
||||
}
|
||||
if (!empty($endTime)) {
|
||||
$where[] = ['createTime', '<=', strtotime($endTime . ' 23:59:59')];
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
$query = Order::where($where)
|
||||
->where(function ($query) {
|
||||
$query->whereNull('deleteTime')->whereOr('deleteTime', 0);
|
||||
});
|
||||
$total = $query->count();
|
||||
|
||||
$list = $query->field('id,orderNo,goodsId,goodsName,goodsSpecs,orderType,money,status,payType,payTime,createTime')
|
||||
->order('id desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
// 格式化数据
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
// 金额转换(分转元)
|
||||
$money = round(floatval($item['money'] ?? 0) / 100, 2);
|
||||
|
||||
// 解析商品规格
|
||||
$specs = [];
|
||||
if (!empty($item['goodsSpecs'])) {
|
||||
$specs = is_string($item['goodsSpecs']) ? json_decode($item['goodsSpecs'], true) : $item['goodsSpecs'];
|
||||
}
|
||||
|
||||
// 状态文本
|
||||
$statusText = [
|
||||
0 => '待支付',
|
||||
1 => '已付款',
|
||||
2 => '已退款',
|
||||
3 => '付款失败'
|
||||
];
|
||||
|
||||
// 订单类型文本
|
||||
$orderTypeText = [
|
||||
1 => '购买算力'
|
||||
];
|
||||
|
||||
// 支付类型文本
|
||||
$payTypeText = [
|
||||
1 => '微信支付',
|
||||
2 => '支付宝'
|
||||
];
|
||||
|
||||
$result[] = [
|
||||
'id' => intval($item['id']),
|
||||
'orderNo' => $item['orderNo'],
|
||||
'goodsId' => intval($item['goodsId'] ?? 0),
|
||||
'goodsName' => $item['goodsName'] ?? '',
|
||||
'goodsSpecs' => $specs,
|
||||
'tokens' => isset($specs['tokens']) ? number_format(intval($specs['tokens'])) : '0',
|
||||
'orderType' => intval($item['orderType'] ?? 0),
|
||||
'orderTypeText' => $orderTypeText[$item['orderType'] ?? 0] ?? '其他',
|
||||
'money' => $money,
|
||||
'status' => intval($item['status'] ?? 0),
|
||||
'statusText' => $statusText[$item['status'] ?? 0] ?? '未知',
|
||||
'payType' => intval($item['payType'] ?? 0),
|
||||
'payTypeText' => !empty($item['payType']) ? ($payTypeText[$item['payType']] ?? '未知') : '',
|
||||
'payTime' => !empty($item['payTime']) && is_numeric($item['payTime']) ? date('Y-m-d H:i:s', intval($item['payTime'])) : '',
|
||||
'createTime' => !empty($item['createTime']) && is_numeric($item['createTime']) ? date('Y-m-d H:i:s', intval($item['createTime'])) : '',
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $result,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取订单列表失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取算力统计信息
|
||||
* GET /v2/store/tokens/statistics
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getTokensStatistics()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($companyId)) {
|
||||
return json(['code' => 400, 'msg' => '公司信息获取失败']);
|
||||
}
|
||||
|
||||
// 获取公司算力余额
|
||||
$tokensCompany = TokensCompanyModel::where(['companyId' => $companyId, 'userId' => $userId])->find();
|
||||
$remainingTokens = $tokensCompany ? intval($tokensCompany->tokens ?? 0) : 0;
|
||||
|
||||
// 获取今日开始和结束时间戳
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
|
||||
// 获取本月开始和结束时间戳
|
||||
$monthStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$monthEnd = strtotime(date('Y-m-t 23:59:59'));
|
||||
|
||||
// 统计今日消费(type=0表示消费)
|
||||
$todayUsed = TokensRecordModel::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 0为减少(消费)
|
||||
['createTime', '>=', $todayStart],
|
||||
['createTime', '<=', $todayEnd]
|
||||
])->sum('tokens');
|
||||
$todayUsed = intval($todayUsed);
|
||||
|
||||
// 统计本月消费
|
||||
$monthUsed = TokensRecordModel::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 0为减少(消费)
|
||||
['createTime', '>=', $monthStart],
|
||||
['createTime', '<=', $monthEnd]
|
||||
])->sum('tokens');
|
||||
$monthUsed = intval($monthUsed);
|
||||
|
||||
// 计算总算力(当前剩余 + 历史总消费)
|
||||
$totalConsumed = TokensRecordModel::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0]
|
||||
])->sum('tokens');
|
||||
$totalConsumed = intval($totalConsumed);
|
||||
|
||||
// 总充值算力
|
||||
$totalRecharged = TokensRecordModel::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 1] // 1为增加(充值)
|
||||
])->sum('tokens');
|
||||
$totalRecharged = intval($totalRecharged);
|
||||
|
||||
// 计算预计可用天数(基于过去一个月的平均消耗)
|
||||
$estimatedDays = $this->calculateEstimatedDays($userId, $companyId, $remainingTokens);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'totalTokens' => $totalRecharged, // 总算力(累计充值)
|
||||
'todayUsed' => $todayUsed, // 今日使用
|
||||
'monthUsed' => $monthUsed, // 本月使用
|
||||
'remainingTokens' => $remainingTokens, // 剩余算力
|
||||
'totalConsumed' => $totalConsumed, // 累计消费
|
||||
'estimatedDays' => $estimatedDays, // 预计可用天数
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取算力统计失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算预计可用天数(基于过去一个月的平均消耗)
|
||||
*
|
||||
* @param int $userId 用户ID
|
||||
* @param int $companyId 公司ID
|
||||
* @param int $remainingTokens 当前剩余算力
|
||||
* @return int 预计可用天数,-1表示无法计算(无消耗记录或余额为0)
|
||||
*/
|
||||
private function calculateEstimatedDays($userId, $companyId, $remainingTokens)
|
||||
{
|
||||
// 如果余额为0或负数,无法计算
|
||||
if ($remainingTokens <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 计算过去30天的消耗总量(只统计减少的记录,type=0)
|
||||
$oneMonthAgo = time() - (30 * 24 * 60 * 60); // 30天前的时间戳
|
||||
|
||||
$totalConsumed = TokensRecordModel::where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 只统计减少的记录
|
||||
['createTime', '>=', $oneMonthAgo]
|
||||
])->sum('tokens');
|
||||
|
||||
$totalConsumed = intval($totalConsumed);
|
||||
|
||||
// 如果过去30天没有消耗记录,无法计算
|
||||
if ($totalConsumed <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 计算平均每天消耗量
|
||||
$avgDailyConsumption = $totalConsumed / 30;
|
||||
|
||||
// 如果平均每天消耗为0,无法计算
|
||||
if ($avgDailyConsumption <= 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 计算预计可用天数 = 当前余额 / 平均每天消耗量
|
||||
$estimatedDays = floor($remainingTokens / $avgDailyConsumption);
|
||||
|
||||
return $estimatedDays;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\store\model\TrafficPackage as TrafficPackageModel;
|
||||
use think\Controller;
|
||||
|
||||
class TrafficPackage extends Controller
|
||||
{
|
||||
/**
|
||||
* 获取流量套餐列表
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
$model = new TrafficPackageModel;
|
||||
|
||||
// 获取列表数据
|
||||
$list = $model->field([
|
||||
'id',
|
||||
'name',
|
||||
'tags',
|
||||
'originalPrice',
|
||||
'price',
|
||||
'monthlyTraffic',
|
||||
'duration',
|
||||
'privileges',
|
||||
'createTime'
|
||||
])->select();
|
||||
|
||||
// 处理数据
|
||||
$list = collection($list)->each(function($item) {
|
||||
// 添加计算字段
|
||||
$item['discount'] = $item->discount; // 折扣
|
||||
$item['totalTraffic'] = $item->totalTraffic; // 总流量
|
||||
// 确保特权是数组格式
|
||||
$item['privileges'] = $item->privileges; // 使用模型的获取器处理特权
|
||||
// 格式化时间
|
||||
$item['createTime'] = date('Y-m-d H:i:s', strtotime($item['createTime']));
|
||||
return $item;
|
||||
});
|
||||
return successJson($list,'获取成功');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取当前套餐使用情况
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getUsage()
|
||||
{
|
||||
// 获取用户ID,可以从session或token中获取
|
||||
$userId = input('userId', 0, 'intval');
|
||||
if (empty($userId)) {
|
||||
return errorJson('请先登录');
|
||||
}
|
||||
|
||||
// 获取用户当前生效的套餐订单
|
||||
$order = model('TrafficPackageOrder')
|
||||
->where('userId', $userId)
|
||||
->where('status', 1) // 1表示生效中
|
||||
->where('expireTime', '>', time()) // 未过期
|
||||
->order('expireTime', 'desc') // 取最晚过期的
|
||||
->find();
|
||||
|
||||
if (empty($order)) {
|
||||
return errorJson('未找到有效的套餐');
|
||||
}
|
||||
|
||||
// 获取套餐详情
|
||||
$package = TrafficPackageModel::get($order['packageId']);
|
||||
if (empty($package)) {
|
||||
return errorJson('套餐信息不存在');
|
||||
}
|
||||
|
||||
// 计算套餐使用情况
|
||||
$totalUsers = $package['monthlyTraffic'] * $package['duration']; // 总人数
|
||||
$usedUsers = model('TrafficUsageLog')
|
||||
->where('orderId', $order['id'])
|
||||
->count(); // 已使用人数
|
||||
|
||||
// 计算剩余有效期(天数)
|
||||
$remainDays = ceil(($order['expireTime'] - time()) / (60 * 60 * 24));
|
||||
$remainDays = max(0, $remainDays); // 确保不会出现负数
|
||||
|
||||
$data = [
|
||||
'packageName' => $package['name'], // 套餐名称
|
||||
'totalUsers' => $totalUsers, // 总人数
|
||||
'usedUsers' => $usedUsers, // 已使用人数
|
||||
'remainUsers' => $totalUsers - $usedUsers, // 剩余可用人数
|
||||
'remainDays' => $remainDays, // 剩余有效期(天)
|
||||
'expireTime' => date('Y-m-d', $order['expireTime']), // 过期时间
|
||||
'usagePercent' => $totalUsers > 0 ? round(($usedUsers / $totalUsers) * 100, 1) : 0, // 使用百分比
|
||||
];
|
||||
|
||||
return successJson($data, '获取成功');
|
||||
}
|
||||
|
||||
}
|
||||
267
application/store/controller/UserController.php
Normal file
267
application/store/controller/UserController.php
Normal file
@@ -0,0 +1,267 @@
|
||||
<?php
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use think\Db;
|
||||
use think\facade\Log;
|
||||
use app\common\service\UserApiKeyService;
|
||||
|
||||
/**
|
||||
* 用户管理控制器
|
||||
*/
|
||||
class UserController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取用户资料
|
||||
* GET /v2/store/user/profile
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getProfile()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取用户基本信息
|
||||
$user = Db::name('users')
|
||||
->where([
|
||||
['id', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['typeId', '=', 2], // 门店端用户
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->field('id, account, username, phone, avatar, companyId, typeId, status, createTime')
|
||||
->find();
|
||||
|
||||
if (empty($user)) {
|
||||
return json(['code' => 404, 'msg' => '用户不存在']);
|
||||
}
|
||||
|
||||
// 获取算力信息
|
||||
$tokensCompany = Db::name('tokens_company')
|
||||
->where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId]
|
||||
])
|
||||
->find();
|
||||
|
||||
$remainingTokens = $tokensCompany ? intval($tokensCompany['tokens'] ?? 0) : 0;
|
||||
|
||||
// 统计今日消费
|
||||
$todayStart = strtotime(date('Y-m-d 00:00:00'));
|
||||
$todayEnd = strtotime(date('Y-m-d 23:59:59'));
|
||||
$todayUsed = Db::name('tokens_record')
|
||||
->where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 0为减少(消费)
|
||||
['createTime', '>=', $todayStart],
|
||||
['createTime', '<=', $todayEnd]
|
||||
])
|
||||
->sum('tokens');
|
||||
$todayUsed = intval($todayUsed);
|
||||
|
||||
// 统计本月消费
|
||||
$monthStart = strtotime(date('Y-m-01 00:00:00'));
|
||||
$monthEnd = strtotime(date('Y-m-t 23:59:59'));
|
||||
$monthUsed = Db::name('tokens_record')
|
||||
->where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 0], // 0为减少(消费)
|
||||
['createTime', '>=', $monthStart],
|
||||
['createTime', '<=', $monthEnd]
|
||||
])
|
||||
->sum('tokens');
|
||||
$monthUsed = intval($monthUsed);
|
||||
|
||||
// 总充值算力
|
||||
$totalRecharged = Db::name('tokens_record')
|
||||
->where([
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['type', '=', 1] // 1为增加(充值)
|
||||
])
|
||||
->sum('tokens');
|
||||
$totalRecharged = intval($totalRecharged);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'id' => intval($user['id']),
|
||||
'account' => $user['account'] ?? '',
|
||||
'username' => $user['username'] ?? '',
|
||||
'phone' => $user['phone'] ?? '',
|
||||
'avatar' => $user['avatar'] ?? 'https://img.icons8.com/color/512/circled-user-male-skin-type-7.png',
|
||||
'companyId' => intval($user['companyId']),
|
||||
'typeId' => intval($user['typeId']),
|
||||
'status' => intval($user['status']),
|
||||
'createTime' => !empty($user['createTime']) && is_numeric($user['createTime']) ? date('Y-m-d H:i:s', intval($user['createTime'])) : '',
|
||||
// 算力信息
|
||||
'tokens' => [
|
||||
'remainingTokens' => $remainingTokens, // 剩余算力
|
||||
'totalRecharged' => $totalRecharged, // 总算力(累计充值)
|
||||
'todayUsed' => $todayUsed, // 今日使用
|
||||
'monthUsed' => $monthUsed, // 本月使用
|
||||
]
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取用户资料失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新用户资料
|
||||
* PUT /v2/store/user/profile
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateProfile()
|
||||
{
|
||||
try {
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 获取更新参数
|
||||
$username = $this->request->param('username', '');
|
||||
$avatar = $this->request->param('avatar', '');
|
||||
$oldPassword = $this->request->param('oldPassword', '');
|
||||
$newPassword = $this->request->param('newPassword', '');
|
||||
|
||||
// 检查用户是否存在
|
||||
$user = Db::name('users')
|
||||
->where([
|
||||
['id', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['typeId', '=', 2],
|
||||
['deleteTime', '=', 0]
|
||||
])
|
||||
->find();
|
||||
|
||||
if (empty($user)) {
|
||||
return json(['code' => 404, 'msg' => '用户不存在']);
|
||||
}
|
||||
|
||||
$updateData = [];
|
||||
$updateFields = [];
|
||||
|
||||
// 更新昵称
|
||||
if ($username !== '') {
|
||||
$updateData['username'] = $username;
|
||||
$updateFields[] = '昵称';
|
||||
}
|
||||
|
||||
// 更新头像
|
||||
if ($avatar !== '') {
|
||||
$updateData['avatar'] = $avatar;
|
||||
$updateFields[] = '头像';
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
if (!empty($oldPassword) && !empty($newPassword)) {
|
||||
// 验证旧密码
|
||||
$oldPasswordMd5 = md5($oldPassword);
|
||||
if ($user['passwordMd5'] !== $oldPasswordMd5) {
|
||||
return json(['code' => 400, 'msg' => '旧密码不正确']);
|
||||
}
|
||||
|
||||
// 验证新密码长度
|
||||
if (strlen($newPassword) < 6) {
|
||||
return json(['code' => 400, 'msg' => '新密码长度不能少于6位']);
|
||||
}
|
||||
|
||||
$updateData['passwordMd5'] = md5($newPassword);
|
||||
$updateFields[] = '密码';
|
||||
}
|
||||
|
||||
// 如果没有需要更新的字段
|
||||
if (empty($updateData)) {
|
||||
return json(['code' => 400, 'msg' => '没有需要更新的字段']);
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
$updateData['updateTime'] = time();
|
||||
$result = Db::name('users')
|
||||
->where('id', $userId)
|
||||
->update($updateData);
|
||||
|
||||
if ($result !== false) {
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '更新成功',
|
||||
'data' => [
|
||||
'updatedFields' => $updateFields
|
||||
]
|
||||
]);
|
||||
} else {
|
||||
return json(['code' => 500, 'msg' => '更新失败']);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新用户资料失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户的对外 API Key(没有则自动生成)
|
||||
* GET /v2/store/user/api-key
|
||||
*/
|
||||
public function getApiKey()
|
||||
{
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
try {
|
||||
$apiKey = UserApiKeyService::bindOrGet((int)$userId);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => 'success',
|
||||
'data' => ['apiKey' => $apiKey],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取 apiKey 失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新生成当前用户的对外 API Key(会覆盖旧 Key)
|
||||
* POST /v2/store/user/api-key/regenerate
|
||||
*/
|
||||
public function regenerateApiKey()
|
||||
{
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
try {
|
||||
$apiKey = UserApiKeyService::forceGenerate((int)$userId);
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '重新生成成功,请妥善保存新 Key,旧 Key 已失效',
|
||||
'data' => ['apiKey' => $apiKey],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('重新生成 apiKey 失败: ' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '重新生成失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,26 +6,30 @@ use app\store\model\VendorPackageModel;
|
||||
use app\store\model\VendorProjectModel;
|
||||
use app\store\model\VendorOrderModel;
|
||||
use think\facade\Log;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 套餐控制器
|
||||
* 供应商套餐控制器
|
||||
*/
|
||||
class VendorController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取套餐列表
|
||||
*
|
||||
* 获取供应商套餐列表
|
||||
* GET /v2/store/vendor/list
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', $this->request->param('pageSize', 10))); // 兼容 pageSize 参数
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
$status = $this->request->param('status', '');
|
||||
|
||||
// 确保分页参数有效
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = 10;
|
||||
|
||||
$where = [
|
||||
['isDel', '=', 0]
|
||||
];
|
||||
@@ -35,41 +39,69 @@ class VendorController extends BaseController
|
||||
$where[] = ['name', 'like', "%{$keyword}%"];
|
||||
}
|
||||
|
||||
// 状态筛选
|
||||
// 状态筛选(1=上架,0=下架)
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', $status];
|
||||
$where[] = ['status', '=', intval($status)];
|
||||
} else {
|
||||
// 默认只显示上架的套餐
|
||||
$where[] = ['status', '=', 1];
|
||||
}
|
||||
|
||||
$list = VendorPackageModel::where($where)
|
||||
->field('id, userId, companyId, name, originalPrice, price, discount, advancePayment, tags, description, cover, status, createTime, updateTime')
|
||||
->order('id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
$total = VendorPackageModel::where($where)->count();
|
||||
|
||||
// 格式化返回数据
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
$result[] = [
|
||||
'id' => intval($item['id']),
|
||||
'userId' => intval($item['userId'] ?? 0),
|
||||
'companyId' => intval($item['companyId'] ?? 0),
|
||||
'name' => $item['name'],
|
||||
'originalPrice' => floatval($item['originalPrice']),
|
||||
'price' => floatval($item['price']),
|
||||
'discount' => $item->discount,
|
||||
'advancePayment' => floatval($item['advancePayment'] ?? 0),
|
||||
'tags' => $item->tags,
|
||||
'description' => $item['description'] ?? '',
|
||||
'cover' => $item['cover'] ?? '',
|
||||
'status' => intval($item['status']),
|
||||
'createTime' => !empty($item['createTime']) && is_numeric($item['createTime']) ? date('Y-m-d H:i:s', intval($item['createTime'])) : '',
|
||||
'updateTime' => !empty($item['updateTime']) && is_numeric($item['updateTime']) ? date('Y-m-d H:i:s', intval($item['updateTime'])) : '',
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'list' => $result,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取套餐列表失败:' . $e->getMessage());
|
||||
Log::error('获取供应商套餐列表失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取套餐详情
|
||||
*
|
||||
* 获取供应商套餐详情
|
||||
* GET /v2/store/vendor/detail
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id', 0);
|
||||
$id = intval($this->request->param('id', 0));
|
||||
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
@@ -91,444 +123,116 @@ class VendorController extends BaseController
|
||||
['isDel', '=', 0]
|
||||
])->select();
|
||||
|
||||
$package['projects'] = $projects;
|
||||
// 格式化套餐信息
|
||||
$packageData = [
|
||||
'id' => intval($package['id']),
|
||||
'userId' => intval($package['userId'] ?? 0),
|
||||
'companyId' => intval($package['companyId'] ?? 0),
|
||||
'name' => $package['name'],
|
||||
'originalPrice' => floatval($package['originalPrice']),
|
||||
'price' => floatval($package['price']),
|
||||
'discount' => $package->discount,
|
||||
'advancePayment' => floatval($package['advancePayment'] ?? 0),
|
||||
'tags' => $package->tags,
|
||||
'description' => $package['description'] ?? '',
|
||||
'cover' => $package['cover'] ?? '',
|
||||
'status' => intval($package['status']),
|
||||
'createTime' => !empty($package['createTime']) && is_numeric($package['createTime']) ? date('Y-m-d H:i:s', intval($package['createTime'])) : '',
|
||||
'updateTime' => !empty($package['updateTime']) && is_numeric($package['updateTime']) ? date('Y-m-d H:i:s', intval($package['updateTime'])) : '',
|
||||
];
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $package]);
|
||||
// 格式化项目信息
|
||||
$projectList = [];
|
||||
foreach ($projects as $project) {
|
||||
$projectList[] = [
|
||||
'id' => intval($project['id']),
|
||||
'packageId' => intval($project['packageId']),
|
||||
'name' => $project['name'],
|
||||
'originalPrice' => floatval($project['originalPrice']),
|
||||
'price' => floatval($project['price']),
|
||||
'duration' => intval($project['duration'] ?? 0),
|
||||
'image' => $project['image'] ?? '',
|
||||
'detail' => $project['detail'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
$packageData['projects'] = $projectList;
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $packageData]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取套餐详情失败:' . $e->getMessage());
|
||||
Log::error('获取供应商套餐详情失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加套餐
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
|
||||
$param = $this->request->post();
|
||||
|
||||
// 参数验证
|
||||
if (empty($param['name'])) {
|
||||
return json(['code' => 400, 'msg' => '套餐名称不能为空']);
|
||||
}
|
||||
|
||||
// 检查名称是否已存在
|
||||
$exists = VendorPackageModel::where([
|
||||
['name', '=', $param['name']],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if ($exists) {
|
||||
return json(['code' => 400, 'msg' => '该套餐名称已存在']);
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 创建套餐
|
||||
$package = new VendorPackageModel;
|
||||
$package->name = $param['name'];
|
||||
$package->originalPrice = $param['originalPrice'] ?? 0;
|
||||
$package->price = $param['price'] ?? 0;
|
||||
$package->discount = $param['discount'] ?? 0;
|
||||
$package->advancePayment = $param['advancePayment'] ?? 0;
|
||||
$package->tags = $param['tags'] ?? '';
|
||||
$package->description = $param['description'] ?? '';
|
||||
$package->cover = $param['cover'] ?? '';
|
||||
$package->status = $param['status'] ?? 1;
|
||||
$package->createTime = time();
|
||||
$package->updateTime = time();
|
||||
$package->save();
|
||||
|
||||
// 处理项目信息
|
||||
if (!empty($param['projects']) && is_array($param['projects'])) {
|
||||
foreach ($param['projects'] as $projectData) {
|
||||
if (empty($projectData['name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 创建项目
|
||||
$project = new VendorProjectModel;
|
||||
$project->packageId = $package->id;
|
||||
$project->name = $projectData['name'];
|
||||
$project->originalPrice = $projectData['originalPrice'] ?? 0;
|
||||
$project->price = $projectData['price'] ?? 0;
|
||||
$project->duration = $projectData['duration'] ?? 0;
|
||||
$project->image = $projectData['image'] ?? '';
|
||||
$project->detail = $projectData['detail'] ?? '';
|
||||
$project->createTime = time();
|
||||
$project->updateTime = time();
|
||||
$project->save();
|
||||
}
|
||||
}
|
||||
|
||||
Db::commit();
|
||||
return json(['code' => 200, 'msg' => '添加成功', 'data' => ['id' => $package->id]]);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
Log::error('添加套餐失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '添加失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('添加套餐异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '添加异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑套餐
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function edit()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
|
||||
$param = $this->request->post();
|
||||
|
||||
// 参数验证
|
||||
if (empty($param['id'])) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
if (empty($param['name'])) {
|
||||
return json(['code' => 400, 'msg' => '套餐名称不能为空']);
|
||||
}
|
||||
|
||||
// 检查套餐是否存在
|
||||
$package = VendorPackageModel::where([
|
||||
['id', '=', $param['id']],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$package) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在']);
|
||||
}
|
||||
|
||||
// 检查名称是否已存在
|
||||
$exists = VendorPackageModel::where([
|
||||
['name', '=', $param['name']],
|
||||
['id', '<>', $param['id']],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if ($exists) {
|
||||
return json(['code' => 400, 'msg' => '该套餐名称已存在']);
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 更新套餐
|
||||
$package->name = $param['name'];
|
||||
$package->originalPrice = $param['originalPrice'] ?? $package->originalPrice;
|
||||
$package->price = $param['price'] ?? $package->price;
|
||||
$package->discount = $param['discount'] ?? $package->discount;
|
||||
$package->advancePayment = $param['advancePayment'] ?? $package->advancePayment;
|
||||
$package->tags = $param['tags'] ?? $package->tags;
|
||||
$package->description = $param['description'] ?? $package->description;
|
||||
$package->cover = $param['cover'] ?? $package->cover;
|
||||
$package->status = $param['status'] ?? $package->status;
|
||||
$package->updateTime = time();
|
||||
$package->save();
|
||||
|
||||
Db::commit();
|
||||
return json(['code' => 200, 'msg' => '更新成功']);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
Log::error('更新套餐失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('编辑套餐异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '编辑异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除套餐
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 检查套餐是否存在
|
||||
$package = VendorPackageModel::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$package) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在']);
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 软删除套餐
|
||||
$package->isDel = 1;
|
||||
$package->updateTime = time();
|
||||
$package->save();
|
||||
|
||||
// 软删除关联的项目
|
||||
VendorProjectModel::where('packageId', $id)
|
||||
->update([
|
||||
'isDel' => 1,
|
||||
'updateTime' => time()
|
||||
]);
|
||||
|
||||
Db::commit();
|
||||
return json(['code' => 200, 'msg' => '删除成功']);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
Log::error('删除套餐失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '删除失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('删除套餐异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '删除异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加项目
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function addProject()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
|
||||
$param = $this->request->post();
|
||||
|
||||
// 参数验证
|
||||
if (empty($param['packageId'])) {
|
||||
return json(['code' => 400, 'msg' => '套餐ID不能为空']);
|
||||
}
|
||||
|
||||
if (empty($param['name'])) {
|
||||
return json(['code' => 400, 'msg' => '项目名称不能为空']);
|
||||
}
|
||||
|
||||
// 检查套餐是否存在
|
||||
$package = VendorPackageModel::where([
|
||||
['id', '=', $param['packageId']],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$package) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建项目
|
||||
$project = new VendorProjectModel;
|
||||
$project->packageId = $param['packageId'];
|
||||
$project->name = $param['name'];
|
||||
$project->originalPrice = $param['originalPrice'] ?? 0;
|
||||
$project->price = $param['price'] ?? 0;
|
||||
$project->duration = $param['duration'] ?? 0;
|
||||
$project->image = $param['image'] ?? '';
|
||||
$project->detail = $param['detail'] ?? '';
|
||||
$project->createTime = time();
|
||||
$project->updateTime = time();
|
||||
$project->save();
|
||||
|
||||
return json(['code' => 200, 'msg' => '添加成功', 'data' => ['id' => $project->id]]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('添加项目失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '添加失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('添加项目异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '添加异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑项目
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function editProject()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
|
||||
$param = $this->request->post();
|
||||
|
||||
// 参数验证
|
||||
if (empty($param['id'])) {
|
||||
return json(['code' => 400, 'msg' => '项目ID不能为空']);
|
||||
}
|
||||
|
||||
if (empty($param['name'])) {
|
||||
return json(['code' => 400, 'msg' => '项目名称不能为空']);
|
||||
}
|
||||
|
||||
// 检查项目是否存在
|
||||
$project = VendorProjectModel::where([
|
||||
['id', '=', $param['id']],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$project) {
|
||||
return json(['code' => 404, 'msg' => '项目不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 更新项目
|
||||
$project->name = $param['name'];
|
||||
$project->originalPrice = $param['originalPrice'] ?? $project->originalPrice;
|
||||
$project->price = $param['price'] ?? $project->price;
|
||||
$project->duration = $param['duration'] ?? $project->duration;
|
||||
$project->image = $param['image'] ?? $project->image;
|
||||
$project->detail = $param['detail'] ?? $project->detail;
|
||||
$project->updateTime = time();
|
||||
$project->save();
|
||||
|
||||
return json(['code' => 200, 'msg' => '更新成功']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新项目失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('编辑项目异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '编辑异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除项目
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function deleteProject()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id', 0);
|
||||
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 检查项目是否存在
|
||||
$project = VendorProjectModel::where([
|
||||
['id', '=', $id],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
if (!$project) {
|
||||
return json(['code' => 404, 'msg' => '项目不存在']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 软删除项目
|
||||
$project->isDel = 1;
|
||||
$project->updateTime = time();
|
||||
$project->save();
|
||||
|
||||
return json(['code' => 200, 'msg' => '删除成功']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('删除项目失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '删除失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('删除项目异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '删除异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建订单
|
||||
*
|
||||
* 创建供应商订单
|
||||
* POST /v2/store/vendor/order
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function createOrder()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
$packageId = intval($this->request->param('packageId', 0));
|
||||
$remark = $this->request->param('remark', '');
|
||||
|
||||
$param = $this->request->post();
|
||||
|
||||
// 参数验证
|
||||
if (empty($param['packageId'])) {
|
||||
if (empty($packageId)) {
|
||||
return json(['code' => 400, 'msg' => '套餐ID不能为空']);
|
||||
}
|
||||
|
||||
// 检查套餐是否存在
|
||||
$package = VendorPackageModel::where([
|
||||
['id', '=', $param['packageId']],
|
||||
['isDel', '=', 0],
|
||||
['status', '=', 1]
|
||||
])->find();
|
||||
|
||||
if (!$package) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在或已下架']);
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
$userId = $this->request->userInfo['id'];
|
||||
// 获取用户信息
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
Db::startTrans();
|
||||
try {
|
||||
// 生成订单
|
||||
$order = new VendorOrderModel;
|
||||
$order->orderNo = VendorOrderModel::generateOrderNo();
|
||||
$order->userId = $userId;
|
||||
$order->packageId = $package->id;
|
||||
$order->packageName = $package->name;
|
||||
$order->totalAmount = $package->price;
|
||||
$order->payAmount = $package->price;
|
||||
$order->advancePayment = $package->advancePayment;
|
||||
$order->status = VendorOrderModel::STATUS_UNPAID;
|
||||
$order->remark = $param['remark'] ?? '';
|
||||
$order->createTime = time();
|
||||
$order->updateTime = time();
|
||||
$order->save();
|
||||
|
||||
Db::commit();
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '订单创建成功',
|
||||
'data' => [
|
||||
'orderId' => $order->id,
|
||||
'orderNo' => $order->orderNo
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Db::rollback();
|
||||
Log::error('创建订单失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '创建订单失败:' . $e->getMessage()]);
|
||||
if (empty($companyId)) {
|
||||
return json(['code' => 400, 'msg' => '公司信息不存在']);
|
||||
}
|
||||
|
||||
// 检查套餐是否存在且上架
|
||||
$package = VendorPackageModel::where([
|
||||
['id', '=', $packageId],
|
||||
['isDel', '=', 0],
|
||||
['status', '=', 1]
|
||||
])->find();
|
||||
|
||||
if (empty($package)) {
|
||||
return json(['code' => 404, 'msg' => '套餐不存在或已下架']);
|
||||
}
|
||||
|
||||
// 创建订单
|
||||
$order = VendorOrderModel::createOrder(
|
||||
$userId,
|
||||
$companyId,
|
||||
$package->id,
|
||||
$package->name,
|
||||
floatval($package->price),
|
||||
floatval($package->price),
|
||||
floatval($package->advancePayment ?? 0),
|
||||
$remark
|
||||
);
|
||||
|
||||
if (!$order) {
|
||||
return json(['code' => 500, 'msg' => '订单创建失败']);
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '订单创建成功',
|
||||
'data' => [
|
||||
'orderId' => intval($order['id']),
|
||||
'orderNo' => $order['orderNo']
|
||||
]
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('创建订单异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '创建订单异常:' . $e->getMessage()]);
|
||||
Log::error('创建供应商订单失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '创建订单失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,35 +2,44 @@
|
||||
|
||||
namespace app\store\controller;
|
||||
|
||||
use app\store\model\VendorOrderModel;
|
||||
use app\store\model\VendorPackageModel;
|
||||
use app\store\model\VendorProjectModel;
|
||||
use app\store\model\VendorOrderModel;
|
||||
use think\facade\Log;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 订单控制器
|
||||
* 供应商订单控制器
|
||||
*/
|
||||
class VendorOrderController extends BaseController
|
||||
{
|
||||
/**
|
||||
* 获取订单列表
|
||||
*
|
||||
* GET /v2/store/vendor/orders
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
$page = $this->request->param('page', 1);
|
||||
$limit = $this->request->param('limit', 10);
|
||||
$page = intval($this->request->param('page', 1));
|
||||
$limit = intval($this->request->param('limit', $this->request->param('pageSize', 10))); // 兼容 pageSize 参数
|
||||
$status = $this->request->param('status', '');
|
||||
$keyword = $this->request->param('keyword', '');
|
||||
|
||||
// 获取当前用户信息
|
||||
$userId = $this->request->userInfo['id'];
|
||||
// 确保分页参数有效
|
||||
if ($page <= 0) $page = 1;
|
||||
if ($limit <= 0) $limit = 10;
|
||||
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
$where = [
|
||||
['userId', '=', $userId]
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId], // 按公司ID查询
|
||||
];
|
||||
|
||||
// 关键词搜索
|
||||
@@ -40,22 +49,42 @@ class VendorOrderController extends BaseController
|
||||
|
||||
// 状态筛选
|
||||
if ($status !== '') {
|
||||
$where[] = ['status', '=', $status];
|
||||
$where[] = ['status', '=', intval($status)];
|
||||
}
|
||||
|
||||
$list = VendorOrderModel::with(['package'])
|
||||
->where($where)
|
||||
$list = VendorOrderModel::where($where)
|
||||
->order('id', 'desc')
|
||||
->page($page, $limit)
|
||||
->select();
|
||||
|
||||
$total = VendorOrderModel::where($where)->count();
|
||||
|
||||
// 格式化数据
|
||||
$result = [];
|
||||
foreach ($list as $item) {
|
||||
$result[] = [
|
||||
'id' => intval($item['id']),
|
||||
'orderNo' => $item['orderNo'],
|
||||
'userId' => intval($item['userId']),
|
||||
'companyId' => intval($item['companyId'] ?? 0),
|
||||
'packageId' => intval($item['packageId']),
|
||||
'packageName' => $item['packageName'],
|
||||
'totalAmount' => floatval($item['totalAmount']),
|
||||
'payAmount' => floatval($item['payAmount']),
|
||||
'advancePayment' => floatval($item['advancePayment'] ?? 0),
|
||||
'status' => intval($item['status']),
|
||||
'payTime' => !empty($item['payTime']) && is_numeric($item['payTime']) ? date('Y-m-d H:i:s', intval($item['payTime'])) : '',
|
||||
'remark' => $item['remark'] ?? '',
|
||||
'createTime' => !empty($item['createTime']) && is_numeric($item['createTime']) ? date('Y-m-d H:i:s', intval($item['createTime'])) : '',
|
||||
'updateTime' => !empty($item['updateTime']) && is_numeric($item['updateTime']) ? date('Y-m-d H:i:s', intval($item['updateTime'])) : '',
|
||||
];
|
||||
}
|
||||
|
||||
return json([
|
||||
'code' => 200,
|
||||
'msg' => '获取成功',
|
||||
'data' => [
|
||||
'list' => $list,
|
||||
'list' => $result,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'limit' => $limit
|
||||
@@ -69,161 +98,148 @@ class VendorOrderController extends BaseController
|
||||
|
||||
/**
|
||||
* 获取订单详情
|
||||
*
|
||||
* GET /v2/store/vendor/orders/:id
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function detail()
|
||||
{
|
||||
try {
|
||||
$id = $this->request->param('id', 0);
|
||||
$id = intval($this->request->param('id', 0));
|
||||
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 查询订单
|
||||
$order = VendorOrderModel::with(['package'])
|
||||
->where([
|
||||
['id', '=', $id],
|
||||
['userId', '=', $userId]
|
||||
])->find();
|
||||
$order = VendorOrderModel::where([
|
||||
['id', '=', $id],
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId]
|
||||
])->find();
|
||||
|
||||
if (empty($order)) {
|
||||
return json(['code' => 404, 'msg' => '订单不存在']);
|
||||
}
|
||||
|
||||
// 查询套餐信息
|
||||
$package = VendorPackageModel::where([
|
||||
['id', '=', $order['packageId']],
|
||||
['isDel', '=', 0]
|
||||
])->find();
|
||||
|
||||
// 查询套餐项目
|
||||
if (!empty($order['package'])) {
|
||||
$projects = [];
|
||||
if ($package) {
|
||||
$projects = VendorProjectModel::where([
|
||||
['packageId', '=', $order['packageId']],
|
||||
['isDel', '=', 0]
|
||||
])->select();
|
||||
|
||||
$order['package']['projects'] = $projects;
|
||||
}
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $order]);
|
||||
// 格式化订单信息
|
||||
$orderData = [
|
||||
'id' => intval($order['id']),
|
||||
'orderNo' => $order['orderNo'],
|
||||
'userId' => intval($order['userId']),
|
||||
'companyId' => intval($order['companyId'] ?? 0),
|
||||
'packageId' => intval($order['packageId']),
|
||||
'packageName' => $order['packageName'],
|
||||
'totalAmount' => floatval($order['totalAmount']),
|
||||
'payAmount' => floatval($order['payAmount']),
|
||||
'advancePayment' => floatval($order['advancePayment'] ?? 0),
|
||||
'status' => intval($order['status']),
|
||||
'payTime' => !empty($order['payTime']) && is_numeric($order['payTime']) ? date('Y-m-d H:i:s', intval($order['payTime'])) : '',
|
||||
'remark' => $order['remark'] ?? '',
|
||||
'createTime' => !empty($order['createTime']) && is_numeric($order['createTime']) ? date('Y-m-d H:i:s', intval($order['createTime'])) : '',
|
||||
'updateTime' => !empty($order['updateTime']) && is_numeric($order['updateTime']) ? date('Y-m-d H:i:s', intval($order['updateTime'])) : '',
|
||||
];
|
||||
|
||||
// 添加套餐信息
|
||||
if ($package) {
|
||||
$orderData['package'] = [
|
||||
'id' => intval($package['id']),
|
||||
'name' => $package['name'],
|
||||
'originalPrice' => floatval($package['originalPrice']),
|
||||
'price' => floatval($package['price']),
|
||||
'description' => $package['description'] ?? '',
|
||||
'cover' => $package['cover'] ?? '',
|
||||
];
|
||||
}
|
||||
|
||||
// 添加项目列表
|
||||
$projectList = [];
|
||||
foreach ($projects as $project) {
|
||||
$projectList[] = [
|
||||
'id' => intval($project['id']),
|
||||
'name' => $project['name'],
|
||||
'originalPrice' => floatval($project['originalPrice']),
|
||||
'price' => floatval($project['price']),
|
||||
'duration' => intval($project['duration'] ?? 0),
|
||||
'image' => $project['image'] ?? '',
|
||||
'detail' => $project['detail'] ?? '',
|
||||
];
|
||||
}
|
||||
$orderData['package']['projects'] = $projectList;
|
||||
|
||||
return json(['code' => 200, 'msg' => '获取成功', 'data' => $orderData]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('获取订单详情失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '获取失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新订单状态
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function updateStatus()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
|
||||
$param = $this->request->post();
|
||||
|
||||
// 参数验证
|
||||
if (empty($param['id'])) {
|
||||
return json(['code' => 400, 'msg' => '订单ID不能为空']);
|
||||
}
|
||||
|
||||
if (!isset($param['status'])) {
|
||||
return json(['code' => 400, 'msg' => '订单状态不能为空']);
|
||||
}
|
||||
|
||||
// 检查订单是否存在
|
||||
$order = VendorOrderModel::where('id', $param['id'])->find();
|
||||
|
||||
if (!$order) {
|
||||
return json(['code' => 404, 'msg' => '订单不存在']);
|
||||
}
|
||||
|
||||
// 检查状态是否有效
|
||||
$validStatus = [
|
||||
VendorOrderModel::STATUS_UNPAID,
|
||||
VendorOrderModel::STATUS_PAID,
|
||||
VendorOrderModel::STATUS_COMPLETED,
|
||||
VendorOrderModel::STATUS_CANCELED
|
||||
];
|
||||
|
||||
if (!in_array($param['status'], $validStatus)) {
|
||||
return json(['code' => 400, 'msg' => '无效的订单状态']);
|
||||
}
|
||||
|
||||
// 更新订单状态
|
||||
$updateData = [
|
||||
'status' => $param['status'],
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 如果订单状态为已支付,记录支付时间
|
||||
if ($param['status'] == VendorOrderModel::STATUS_PAID) {
|
||||
$updateData['payTime'] = time();
|
||||
}
|
||||
|
||||
try {
|
||||
$order->save($updateData);
|
||||
return json(['code' => 200, 'msg' => '更新成功']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新订单状态失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新失败:' . $e->getMessage()]);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('更新订单状态异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '更新异常:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消订单
|
||||
*
|
||||
* POST /v2/store/vendor/orders/:id/cancel
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function cancel()
|
||||
{
|
||||
try {
|
||||
if (!$this->request->isPost()) {
|
||||
return json(['code' => 400, 'msg' => '请求方式错误']);
|
||||
}
|
||||
|
||||
$id = $this->request->param('id', 0);
|
||||
$id = intval($this->request->param('id', 0));
|
||||
|
||||
if (empty($id)) {
|
||||
return json(['code' => 400, 'msg' => '参数错误']);
|
||||
}
|
||||
|
||||
// 获取当前用户信息
|
||||
$userId = $this->request->userInfo['id'];
|
||||
$userId = $this->userInfo['id'] ?? 0;
|
||||
$companyId = $this->userInfo['companyId'] ?? 0;
|
||||
|
||||
// 检查订单是否存在
|
||||
if (empty($userId)) {
|
||||
return json(['code' => 401, 'msg' => '请先登录']);
|
||||
}
|
||||
|
||||
// 检查订单是否存在且为待支付状态
|
||||
$order = VendorOrderModel::where([
|
||||
['id', '=', $id],
|
||||
['userId', '=', $userId],
|
||||
['companyId', '=', $companyId],
|
||||
['status', '=', VendorOrderModel::STATUS_UNPAID]
|
||||
])->find();
|
||||
|
||||
if (!$order) {
|
||||
if (empty($order)) {
|
||||
return json(['code' => 404, 'msg' => '订单不存在或状态不允许取消']);
|
||||
}
|
||||
|
||||
try {
|
||||
// 更新订单状态为已取消
|
||||
$order->status = VendorOrderModel::STATUS_CANCELED;
|
||||
$order->updateTime = time();
|
||||
$order->save();
|
||||
|
||||
return json(['code' => 200, 'msg' => '取消成功']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('取消订单失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '取消失败:' . $e->getMessage()]);
|
||||
}
|
||||
// 更新订单状态为已取消
|
||||
$order->status = VendorOrderModel::STATUS_CANCELED;
|
||||
$order->updateTime = time();
|
||||
$order->save();
|
||||
|
||||
return json(['code' => 200, 'msg' => '取消成功']);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('取消订单异常:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '取消异常:' . $e->getMessage()]);
|
||||
Log::error('取消订单失败:' . $e->getMessage());
|
||||
return json(['code' => 500, 'msg' => '取消失败:' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user