fix(fsgx): 积分释放定时任务、佣金轮巡与 UniApp 体验
- PointsReleaseServices: 使用 Db::table 查询与更新,构造函数注入 UserDao;日志与账单独立 try/catch\n- SystemTimer: implement_timer 捕获后重新抛出异常,便于 run_now 返回错误\n- SystemTimerServices / 控制器: runNow 返回任务结果并在 API 中带回 result\n- StoreOrderCreateServices: 报单佣金位次修正与多件轮巡\n- UniApp: 佣金记录跳转 type=2、余额提现免手续费展示、状态页与资产页头部渐变与提现页一致\n- docs: 增加 fsgx-issues-0328-1 问题跟踪 Made-with: Cursor
This commit is contained in:
35
docs/fsgx-issues-0328-1.md
Normal file
35
docs/fsgx-issues-0328-1.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# uniapp移动端
|
||||||
|
|
||||||
|
## 佣金记录页面
|
||||||
|
- 1. **已修复**点击“查看全部”时出现参数错误,是不是路径错误?
|
||||||
|
|
||||||
|
## 提现页面,pages/users/user_cash/index
|
||||||
|
- 1. **已修复**提现到余额,不收取手续费
|
||||||
|
|
||||||
|
## 佣金状态页面,pages/queue/status
|
||||||
|
- 1. **已修复**顶部保持和pages/users/user_cash/index页面中的class="cash-withdrawal"的背影色样式一致
|
||||||
|
|
||||||
|
## 我的资产页面,pages/assets/index
|
||||||
|
- 1. **已修复**顶部保持和pages/users/user_cash/index页面中的class="cash-withdrawal"的背影色样式一致
|
||||||
|
|
||||||
|
# API接口
|
||||||
|
|
||||||
|
## fsgx每日积分释放
|
||||||
|
- 1. 手动触发接口就释放一次当日积分,没有看到用户的冻结积分被释放,没有看到任务执行记录
|
||||||
|
请求接口:https://www.fsgx.cn/adminapi/system/timer/run_now/21
|
||||||
|
响应 ```{"status":200,"msg":"任务已触发并执行成功","data":{"result":null}}```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# 其他测试问题
|
||||||
|
|
||||||
|
## **测试问题**
|
||||||
|
- 1. **已修复**返现佣金计算不对
|
||||||
|
A. **已修复**目前新的分销员佣金是按20%,20%,30%,50%来计算的,前2个直推订单返现佣金都是20%,不对,应该是按配置的佣金分档比例(JSON)”[20,30,50]“的来轮巡执行佣金计算
|
||||||
|
B. **已修复**一次下单购买多个报单商品的情况,佣金计算不对,目前按当前佣金比例的N倍来计算,应该按配置的佣金分档比例(JSON)”[20,30,50]“的来执行轮巡执行佣金计算
|
||||||
|
|
||||||
|
# 相关文件
|
||||||
|
|
||||||
|
- 1. **相关文件**:`docs/PRD_fsgx_V1.0.md` `docs/page-dev-specs-fsgx.md`,`.cursor/plans/fix_issues_0325-1_f8488785.plan.md`
|
||||||
|
|
||||||
|
|
||||||
@@ -119,8 +119,8 @@ class SystemTimer extends AuthController
|
|||||||
return $this->fail('定时任务标识不存在');
|
return $this->fail('定时任务标识不存在');
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
$this->services->runNow($mark);
|
$result = $this->services->runNow($mark);
|
||||||
return $this->success('任务已触发并执行成功');
|
return $this->success('任务已触发并执行成功', ['result' => $result]);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return $this->fail($e->getMessage());
|
return $this->fail($e->getMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,7 @@ class SystemTimer extends Cron implements ListenerInterface
|
|||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'msg' => $e->getMessage()
|
'msg' => $e->getMessage()
|
||||||
]);
|
]);
|
||||||
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace app\services\hjf;
|
namespace app\services\hjf;
|
||||||
|
|
||||||
use app\dao\hjf\PointsReleaseLogDao;
|
|
||||||
use app\dao\user\UserDao;
|
use app\dao\user\UserDao;
|
||||||
use app\services\BaseServices;
|
use app\services\BaseServices;
|
||||||
use app\services\user\UserBillServices;
|
use app\services\user\UserBillServices;
|
||||||
use crmeb\services\SystemConfigService;
|
use crmeb\services\SystemConfigService;
|
||||||
use think\annotation\Inject;
|
|
||||||
use think\facade\Db;
|
use think\facade\Db;
|
||||||
use think\facade\Log;
|
use think\facade\Log;
|
||||||
|
|
||||||
@@ -24,14 +22,10 @@ use think\facade\Log;
|
|||||||
*/
|
*/
|
||||||
class PointsReleaseServices extends BaseServices
|
class PointsReleaseServices extends BaseServices
|
||||||
{
|
{
|
||||||
#[Inject]
|
public function __construct(UserDao $dao)
|
||||||
protected PointsReleaseLogDao $logDao;
|
{
|
||||||
|
$this->dao = $dao;
|
||||||
#[Inject]
|
}
|
||||||
protected UserDao $userDao;
|
|
||||||
|
|
||||||
#[Inject]
|
|
||||||
protected UserBillServices $userBillServices;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行今日积分释放(批量)
|
* 执行今日积分释放(批量)
|
||||||
@@ -45,19 +39,16 @@ class PointsReleaseServices extends BaseServices
|
|||||||
$processed = 0;
|
$processed = 0;
|
||||||
$totalReleased = 0;
|
$totalReleased = 0;
|
||||||
|
|
||||||
// 分批处理,每批 200 条,避免内存溢合
|
|
||||||
$page = 1;
|
$page = 1;
|
||||||
$limit = 200;
|
$limit = 200;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
$users = $this->userDao->selectList(
|
$users = Db::table('eb_user')
|
||||||
['frozen_points' => ['>', 0]],
|
->where('frozen_points', '>', 0)
|
||||||
'uid,frozen_points,available_points',
|
->field('uid, frozen_points, available_points')
|
||||||
$page,
|
->page($page, $limit)
|
||||||
$limit,
|
->select()
|
||||||
'uid',
|
->toArray();
|
||||||
'asc'
|
|
||||||
);
|
|
||||||
|
|
||||||
if (empty($users)) {
|
if (empty($users)) {
|
||||||
break;
|
break;
|
||||||
@@ -65,7 +56,6 @@ class PointsReleaseServices extends BaseServices
|
|||||||
|
|
||||||
foreach ($users as $user) {
|
foreach ($users as $user) {
|
||||||
$frozenBefore = (int)$user['frozen_points'];
|
$frozenBefore = (int)$user['frozen_points'];
|
||||||
// 使用 bcmath 确保精度
|
|
||||||
$releaseAmount = (int)bcdiv(bcmul((string)$frozenBefore, (string)$rate), '1000');
|
$releaseAmount = (int)bcdiv(bcmul((string)$frozenBefore, (string)$rate), '1000');
|
||||||
|
|
||||||
if ($releaseAmount <= 0) {
|
if ($releaseAmount <= 0) {
|
||||||
@@ -73,17 +63,25 @@ class PointsReleaseServices extends BaseServices
|
|||||||
}
|
}
|
||||||
|
|
||||||
$frozenAfter = $frozenBefore - $releaseAmount;
|
$frozenAfter = $frozenBefore - $releaseAmount;
|
||||||
|
$newAvailable = (int)$user['available_points'] + $releaseAmount;
|
||||||
|
|
||||||
|
// 主更新:只更新积分字段,失败则跳过本用户
|
||||||
try {
|
try {
|
||||||
Db::transaction(function () use ($user, $releaseAmount, $frozenBefore, $frozenAfter, $releaseDate) {
|
Db::table('eb_user')->where('uid', $user['uid'])->update([
|
||||||
// 更新用户积分字段
|
'frozen_points' => max(0, $frozenAfter),
|
||||||
$this->userDao->update($user['uid'], [
|
'available_points' => $newAvailable,
|
||||||
'frozen_points' => $frozenAfter,
|
]);
|
||||||
'available_points' => Db::raw('available_points + ' . $releaseAmount),
|
|
||||||
], 'uid');
|
|
||||||
|
|
||||||
// 写 points_release_log(本次每日释放记录)
|
$totalReleased += $releaseAmount;
|
||||||
$this->logDao->save([
|
$processed++;
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
Log::error("[PointsRelease] uid={$user['uid']} 积分更新失败: " . $e->getMessage());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 日志写入独立处理,失败不影响已提交的积分更新
|
||||||
|
try {
|
||||||
|
Db::table('eb_points_release_log')->insert([
|
||||||
'uid' => $user['uid'],
|
'uid' => $user['uid'],
|
||||||
'points' => $releaseAmount,
|
'points' => $releaseAmount,
|
||||||
'pm' => 1,
|
'pm' => 1,
|
||||||
@@ -92,25 +90,22 @@ class PointsReleaseServices extends BaseServices
|
|||||||
'mark' => "积分每日自动解冻,释放日期 {$releaseDate}",
|
'mark' => "积分每日自动解冻,释放日期 {$releaseDate}",
|
||||||
'status' => 'released',
|
'status' => 'released',
|
||||||
'release_date' => $releaseDate,
|
'release_date' => $releaseDate,
|
||||||
|
'add_time' => time(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 同步写入 eb_user_bill,使管理后台积分日志页面可见
|
/** @var UserBillServices $billServices */
|
||||||
$integralBalance = (int)($this->userDao->value(['uid' => $user['uid']], 'integral') ?: 0);
|
$billServices = app()->make(UserBillServices::class);
|
||||||
$this->userBillServices->income(
|
$billServices->income(
|
||||||
'frozen_points_release',
|
'frozen_points_release',
|
||||||
(int)$user['uid'],
|
(int)$user['uid'],
|
||||||
(int)$releaseAmount,
|
(int)$releaseAmount,
|
||||||
$integralBalance,
|
$newAvailable,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
"积分每日自动解冻,释放日期 {$releaseDate}"
|
"积分每日自动解冻,释放日期 {$releaseDate}"
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
$totalReleased += $releaseAmount;
|
|
||||||
$processed++;
|
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
Log::error("[PointsRelease] uid={$user['uid']} 释放失败: " . $e->getMessage());
|
Log::warning("[PointsRelease] uid={$user['uid']} 日志写入失败(积分已释放): " . $e->getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -926,6 +926,9 @@ class StoreOrderCreateServices extends BaseServices
|
|||||||
$storeBrokerageTwo = $spread_two_uid = 0;
|
$storeBrokerageTwo = $spread_two_uid = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fsgx B-2B: 同一订单内多个报单商品件数累计偏移,保证位次轮巡
|
||||||
|
$queueGoodsOffset = 0;
|
||||||
|
|
||||||
foreach ($cartInfo as &$cart) {
|
foreach ($cartInfo as &$cart) {
|
||||||
$oneBrokerage = '0';//一级返佣金额
|
$oneBrokerage = '0';//一级返佣金额
|
||||||
$twoBrokerage = '0';//二级返佣金额
|
$twoBrokerage = '0';//二级返佣金额
|
||||||
@@ -1002,8 +1005,12 @@ class StoreOrderCreateServices extends BaseServices
|
|||||||
$useCycleBrokerage = ($cycleCount > 0 && is_array($cycleRates) && count($cycleRates) > 0 && $isQueueGoods === 1);
|
$useCycleBrokerage = ($cycleCount > 0 && is_array($cycleRates) && count($cycleRates) > 0 && $isQueueGoods === 1);
|
||||||
|
|
||||||
if ($useCycleBrokerage && $spread_uid > 0) {
|
if ($useCycleBrokerage && $spread_uid > 0) {
|
||||||
// fsgx B1 + B6:用事务锁序列化位次计算,防止并发竞态导致两笔订单拿到相同位次
|
// fsgx B1 + B6 + B-2B:用事务锁序列化位次计算,支持多件商品逐件轮巡
|
||||||
$brokerageResult = \think\facade\Db::transaction(function () use ($spread_uid, $cycleCount, $cycleRates, $price) {
|
$cartNumInt = (int)$cartNum;
|
||||||
|
// 计算单件价格,用于逐件计算佣金
|
||||||
|
$unitPrice = $cartNumInt > 0 ? bcdiv((string)$price, (string)$cartNumInt, 4) : '0';
|
||||||
|
$currentOffset = $queueGoodsOffset;
|
||||||
|
$brokerageResult = \think\facade\Db::transaction(function () use ($spread_uid, $cycleCount, $cycleRates, $unitPrice, $cartNumInt, $currentOffset) {
|
||||||
// 锁定推荐人行,确保同一推荐人同时只有一个事务在计算位次
|
// 锁定推荐人行,确保同一推荐人同时只有一个事务在计算位次
|
||||||
\think\facade\Db::name('user')
|
\think\facade\Db::name('user')
|
||||||
->where('uid', $spread_uid)
|
->where('uid', $spread_uid)
|
||||||
@@ -1021,8 +1028,7 @@ class StoreOrderCreateServices extends BaseServices
|
|||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
|
|
||||||
// fsgx B6:按 id ASC 排序取位次,比 count 更精确且在锁保护下无竞态
|
// fsgx B6:统计推荐人已完成的报单订单数,作为起始位次基准
|
||||||
// 当前订单在 paid=1 后已写入,这里取所有已完成订单并按序找到本单排名
|
|
||||||
$completedCount = (int)\think\facade\Db::name('store_order')
|
$completedCount = (int)\think\facade\Db::name('store_order')
|
||||||
->where('spread_uid', $spread_uid)
|
->where('spread_uid', $spread_uid)
|
||||||
->where('is_queue_goods', 1)
|
->where('is_queue_goods', 1)
|
||||||
@@ -1030,14 +1036,20 @@ class StoreOrderCreateServices extends BaseServices
|
|||||||
->where('is_del', 0)
|
->where('is_del', 0)
|
||||||
->count();
|
->count();
|
||||||
|
|
||||||
$position = max(0, $completedCount - 1) % $cycleCount;
|
// fsgx B-2B:逐件轮巡,每件商品取下一个位次的佣金比例后累加
|
||||||
|
$total = '0';
|
||||||
|
for ($i = 0; $i < $cartNumInt; $i++) {
|
||||||
|
$position = ($completedCount + $currentOffset + $i) % $cycleCount;
|
||||||
$cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0);
|
$cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0);
|
||||||
if ($cycleRatePercent > 0) {
|
if ($cycleRatePercent > 0) {
|
||||||
return bcmul((string)$price, bcdiv((string)$cycleRatePercent, '100', 4), 2);
|
$total = bcadd($total, bcmul((string)$unitPrice, bcdiv((string)$cycleRatePercent, '100', 4), 2), 2);
|
||||||
}
|
}
|
||||||
return '0';
|
}
|
||||||
|
return $total;
|
||||||
});
|
});
|
||||||
$oneBrokerage = $brokerageResult;
|
$oneBrokerage = $brokerageResult;
|
||||||
|
// 当前购物车项的件数已消费,累加到偏移量
|
||||||
|
$queueGoodsOffset += $cartNumInt;
|
||||||
} else {
|
} else {
|
||||||
//一级返佣比例 小于等于零时直接返回 不返佣
|
//一级返佣比例 小于等于零时直接返回 不返佣
|
||||||
if ($storeBrokerageRatio > 0) {
|
if ($storeBrokerageRatio > 0) {
|
||||||
|
|||||||
@@ -236,15 +236,15 @@ class SystemTimerServices extends BaseServices
|
|||||||
/**
|
/**
|
||||||
* 手动立即执行指定定时任务(HTTP 请求上下文,绕过 Swoole Cron 构造,用反射直接调用 implement_timer)
|
* 手动立即执行指定定时任务(HTTP 请求上下文,绕过 Swoole Cron 构造,用反射直接调用 implement_timer)
|
||||||
* @param string $mark
|
* @param string $mark
|
||||||
* @return void
|
* @return mixed 任务返回值(如 executeRelease 返回的处理统计数组),可为 null
|
||||||
*/
|
*/
|
||||||
public function runNow(string $mark): void
|
public function runNow(string $mark): mixed
|
||||||
{
|
{
|
||||||
$this->update(['mark' => $mark], ['last_execution_time' => time()]);
|
$this->update(['mark' => $mark], ['last_execution_time' => time()]);
|
||||||
try {
|
try {
|
||||||
$ref = new \ReflectionClass(SystemTimerListener::class);
|
$ref = new \ReflectionClass(SystemTimerListener::class);
|
||||||
$instance = $ref->newInstanceWithoutConstructor();
|
$instance = $ref->newInstanceWithoutConstructor();
|
||||||
$instance->implement_timer($mark);
|
return $instance->implement_timer($mark);
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$taskName = $this->taskName[$mark] ?? $mark;
|
$taskName = $this->taskName[$mark] ?? $mark;
|
||||||
throw new \RuntimeException("定时任务[{$taskName}]执行失败: " . $e->getMessage(), 0, $e);
|
throw new \RuntimeException("定时任务[{$taskName}]执行失败: " . $e->getMessage(), 0, $e);
|
||||||
|
|||||||
@@ -220,7 +220,7 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.hjf-assets-page {
|
.hjf-assets-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f4f5f7;
|
background-color: #db5d02;
|
||||||
padding-bottom: 60rpx;
|
padding-bottom: 60rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
goToCommissionDetail() {
|
goToCommissionDetail() {
|
||||||
uni.navigateTo({ url: '/pages/users/user_spread_money/index' });
|
uni.navigateTo({ url: '/pages/users/user_spread_money/index?type=2' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -183,12 +183,12 @@ export default {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.brokerage-page {
|
.brokerage-page {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f4f5f7;
|
background: #db5d02;
|
||||||
padding-bottom: 60rpx;
|
padding-bottom: 60rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-gradient {
|
.header-gradient {
|
||||||
background: linear-gradient(135deg, var(--view-theme, #e93323) 0%, var(--view-gradient, #f76b1c) 100%);
|
background: linear-gradient(90deg, var(--view-theme) 0%, var(--view-gradient) 100%);
|
||||||
padding: 40rpx 30rpx 56rpx;
|
padding: 40rpx 30rpx 56rpx;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
说明: <text class="num">每笔佣金的冻结期为{{userInfo.broken_day}}天,到期后可提现</text>
|
说明: <text class="num">每笔佣金的冻结期为{{userInfo.broken_day}}天,到期后可提现</text>
|
||||||
</view>
|
</view>
|
||||||
<view class='tip tip-warning'>
|
<view class='tip tip-warning'>
|
||||||
温馨提示: <text class="num">公排退款提现需收取 <text class="fee">{{withdraw_fee}}%</text> 手续费,实际到账金额以扣除手续费后为准</text>
|
温馨提示: <text class="num">提现需收取 <text class="fee">{{withdraw_fee}}%</text> 手续费,实际到账金额以扣除手续费后为准</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
<button formType="submit" class='bnt bg-color'>立即提现</button>
|
<button formType="submit" class='bnt bg-color'>立即提现</button>
|
||||||
@@ -113,16 +113,12 @@
|
|||||||
当前可提现金额: <text
|
当前可提现金额: <text
|
||||||
class="price">¥{{userInfo.commissionCount}}</text>,冻结佣金:¥{{userInfo.broken_commission}}
|
class="price">¥{{userInfo.commissionCount}}</text>,冻结佣金:¥{{userInfo.broken_commission}}
|
||||||
</view>
|
</view>
|
||||||
<view class='tip fee-breakdown'>
|
<view class='tip fee-breakdown' style="background-color:#F0F9EB;color:#67C23A;">
|
||||||
手续费(<text class="fee-rate">{{withdraw_fee}}%</text>):<text class="price">¥{{feeAmount}}</text>
|
提现到余额不收取手续费,实际到账:<text class="price">¥{{cashVal || '0.00'}}</text>
|
||||||
<text class="fee-sep"> | </text>实际到账:<text class="price">¥{{actualAmount}}</text>
|
|
||||||
</view>
|
</view>
|
||||||
<view class='tip'>
|
<view class='tip'>
|
||||||
说明: <text class="num">每笔佣金的冻结期为{{userInfo.broken_day}}天,到期后可提现</text>
|
说明: <text class="num">每笔佣金的冻结期为{{userInfo.broken_day}}天,到期后可提现</text>
|
||||||
</view>
|
</view>
|
||||||
<view class='tip tip-warning'>
|
|
||||||
温馨提示: <text class="num">公排退款提现需收取 <text class="fee">{{withdraw_fee}}%</text> 手续费,实际到账金额以扣除手续费后为准</text>
|
|
||||||
</view>
|
|
||||||
</view>
|
</view>
|
||||||
<button formType="submit" class='bnt bg-color'>立即提现</button>
|
<button formType="submit" class='bnt bg-color'>立即提现</button>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user