Files
CKB-Interface/application/store/controller/TokensController.php
2026-03-24 10:39:16 +08:00

523 lines
20 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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;
}
}