diff --git a/docs/fsgx-issues-0328-1.md b/docs/fsgx-issues-0328-1.md new file mode 100644 index 00000000..1a1fd794 --- /dev/null +++ b/docs/fsgx-issues-0328-1.md @@ -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` + + diff --git a/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php b/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php index 89e61018..cef57108 100644 --- a/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php +++ b/pro_v3.5.1/app/controller/admin/v1/system/SystemTimer.php @@ -119,8 +119,8 @@ class SystemTimer extends AuthController return $this->fail('定时任务标识不存在'); } try { - $this->services->runNow($mark); - return $this->success('任务已触发并执行成功'); + $result = $this->services->runNow($mark); + return $this->success('任务已触发并执行成功', ['result' => $result]); } catch (\Throwable $e) { return $this->fail($e->getMessage()); } diff --git a/pro_v3.5.1/app/listener/system/timer/SystemTimer.php b/pro_v3.5.1/app/listener/system/timer/SystemTimer.php index af108fe2..282c86ba 100644 --- a/pro_v3.5.1/app/listener/system/timer/SystemTimer.php +++ b/pro_v3.5.1/app/listener/system/timer/SystemTimer.php @@ -206,6 +206,7 @@ class SystemTimer extends Cron implements ListenerInterface 'line' => $e->getLine(), 'msg' => $e->getMessage() ]); + throw $e; } } } diff --git a/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php b/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php index 02663849..18f6d53b 100644 --- a/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php +++ b/pro_v3.5.1/app/services/hjf/PointsReleaseServices.php @@ -3,12 +3,10 @@ declare(strict_types=1); namespace app\services\hjf; -use app\dao\hjf\PointsReleaseLogDao; use app\dao\user\UserDao; use app\services\BaseServices; use app\services\user\UserBillServices; use crmeb\services\SystemConfigService; -use think\annotation\Inject; use think\facade\Db; use think\facade\Log; @@ -24,14 +22,10 @@ use think\facade\Log; */ class PointsReleaseServices extends BaseServices { - #[Inject] - protected PointsReleaseLogDao $logDao; - - #[Inject] - protected UserDao $userDao; - - #[Inject] - protected UserBillServices $userBillServices; + public function __construct(UserDao $dao) + { + $this->dao = $dao; + } /** * 执行今日积分释放(批量) @@ -40,24 +34,21 @@ class PointsReleaseServices extends BaseServices */ public function executeRelease(): array { - $rate = (int)SystemConfigService::get('hjf_release_rate', 4); + $rate = (int)SystemConfigService::get('hjf_release_rate', 4); $releaseDate = date('Y-m-d'); $processed = 0; $totalReleased = 0; - // 分批处理,每批 200 条,避免内存溢合 - $page = 1; + $page = 1; $limit = 200; do { - $users = $this->userDao->selectList( - ['frozen_points' => ['>', 0]], - 'uid,frozen_points,available_points', - $page, - $limit, - 'uid', - 'asc' - ); + $users = Db::table('eb_user') + ->where('frozen_points', '>', 0) + ->field('uid, frozen_points, available_points') + ->page($page, $limit) + ->select() + ->toArray(); if (empty($users)) { break; @@ -65,52 +56,56 @@ class PointsReleaseServices extends BaseServices foreach ($users as $user) { $frozenBefore = (int)$user['frozen_points']; - // 使用 bcmath 确保精度 $releaseAmount = (int)bcdiv(bcmul((string)$frozenBefore, (string)$rate), '1000'); if ($releaseAmount <= 0) { continue; } - $frozenAfter = $frozenBefore - $releaseAmount; + $frozenAfter = $frozenBefore - $releaseAmount; + $newAvailable = (int)$user['available_points'] + $releaseAmount; + // 主更新:只更新积分字段,失败则跳过本用户 try { - Db::transaction(function () use ($user, $releaseAmount, $frozenBefore, $frozenAfter, $releaseDate) { - // 更新用户积分字段 - $this->userDao->update($user['uid'], [ - 'frozen_points' => $frozenAfter, - 'available_points' => Db::raw('available_points + ' . $releaseAmount), - ], 'uid'); - - // 写 points_release_log(本次每日释放记录) - $this->logDao->save([ - 'uid' => $user['uid'], - 'points' => $releaseAmount, - 'pm' => 1, - 'type' => 'release', - 'title' => '每日释放', - 'mark' => "积分每日自动解冻,释放日期 {$releaseDate}", - 'status' => 'released', - 'release_date' => $releaseDate, - ]); - - // 同步写入 eb_user_bill,使管理后台积分日志页面可见 - $integralBalance = (int)($this->userDao->value(['uid' => $user['uid']], 'integral') ?: 0); - $this->userBillServices->income( - 'frozen_points_release', - (int)$user['uid'], - (int)$releaseAmount, - $integralBalance, - 0, - 0, - "积分每日自动解冻,释放日期 {$releaseDate}" - ); - }); + Db::table('eb_user')->where('uid', $user['uid'])->update([ + 'frozen_points' => max(0, $frozenAfter), + 'available_points' => $newAvailable, + ]); $totalReleased += $releaseAmount; $processed++; } catch (\Throwable $e) { - Log::error("[PointsRelease] uid={$user['uid']} 释放失败: " . $e->getMessage()); + Log::error("[PointsRelease] uid={$user['uid']} 积分更新失败: " . $e->getMessage()); + continue; + } + + // 日志写入独立处理,失败不影响已提交的积分更新 + try { + Db::table('eb_points_release_log')->insert([ + 'uid' => $user['uid'], + 'points' => $releaseAmount, + 'pm' => 1, + 'type' => 'release', + 'title' => '每日释放', + 'mark' => "积分每日自动解冻,释放日期 {$releaseDate}", + 'status' => 'released', + 'release_date' => $releaseDate, + 'add_time' => time(), + ]); + + /** @var UserBillServices $billServices */ + $billServices = app()->make(UserBillServices::class); + $billServices->income( + 'frozen_points_release', + (int)$user['uid'], + (int)$releaseAmount, + $newAvailable, + 0, + 0, + "积分每日自动解冻,释放日期 {$releaseDate}" + ); + } catch (\Throwable $e) { + Log::warning("[PointsRelease] uid={$user['uid']} 日志写入失败(积分已释放): " . $e->getMessage()); } } @@ -120,9 +115,9 @@ class PointsReleaseServices extends BaseServices Log::info("[PointsRelease] 完成,processed={$processed} total_released={$totalReleased}"); return [ - 'processed' => $processed, + 'processed' => $processed, 'total_released' => $totalReleased, - 'release_date' => $releaseDate, + 'release_date' => $releaseDate, ]; } } diff --git a/pro_v3.5.1/app/services/order/StoreOrderCreateServices.php b/pro_v3.5.1/app/services/order/StoreOrderCreateServices.php index 2efb9a78..369684dd 100644 --- a/pro_v3.5.1/app/services/order/StoreOrderCreateServices.php +++ b/pro_v3.5.1/app/services/order/StoreOrderCreateServices.php @@ -926,6 +926,9 @@ class StoreOrderCreateServices extends BaseServices $storeBrokerageTwo = $spread_two_uid = 0; } + // fsgx B-2B: 同一订单内多个报单商品件数累计偏移,保证位次轮巡 + $queueGoodsOffset = 0; + foreach ($cartInfo as &$cart) { $oneBrokerage = '0';//一级返佣金额 $twoBrokerage = '0';//二级返佣金额 @@ -1002,8 +1005,12 @@ class StoreOrderCreateServices extends BaseServices $useCycleBrokerage = ($cycleCount > 0 && is_array($cycleRates) && count($cycleRates) > 0 && $isQueueGoods === 1); if ($useCycleBrokerage && $spread_uid > 0) { - // fsgx B1 + B6:用事务锁序列化位次计算,防止并发竞态导致两笔订单拿到相同位次 - $brokerageResult = \think\facade\Db::transaction(function () use ($spread_uid, $cycleCount, $cycleRates, $price) { + // fsgx B1 + B6 + B-2B:用事务锁序列化位次计算,支持多件商品逐件轮巡 + $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') ->where('uid', $spread_uid) @@ -1021,8 +1028,7 @@ class StoreOrderCreateServices extends BaseServices return '0'; } - // fsgx B6:按 id ASC 排序取位次,比 count 更精确且在锁保护下无竞态 - // 当前订单在 paid=1 后已写入,这里取所有已完成订单并按序找到本单排名 + // fsgx B6:统计推荐人已完成的报单订单数,作为起始位次基准 $completedCount = (int)\think\facade\Db::name('store_order') ->where('spread_uid', $spread_uid) ->where('is_queue_goods', 1) @@ -1030,14 +1036,20 @@ class StoreOrderCreateServices extends BaseServices ->where('is_del', 0) ->count(); - $position = max(0, $completedCount - 1) % $cycleCount; - $cycleRatePercent = isset($cycleRates[$position]) ? (int)$cycleRates[$position] : (int)($cycleRates[0] ?? 0); - if ($cycleRatePercent > 0) { - return bcmul((string)$price, bcdiv((string)$cycleRatePercent, '100', 4), 2); + // 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); + if ($cycleRatePercent > 0) { + $total = bcadd($total, bcmul((string)$unitPrice, bcdiv((string)$cycleRatePercent, '100', 4), 2), 2); + } } - return '0'; + return $total; }); $oneBrokerage = $brokerageResult; + // 当前购物车项的件数已消费,累加到偏移量 + $queueGoodsOffset += $cartNumInt; } else { //一级返佣比例 小于等于零时直接返回 不返佣 if ($storeBrokerageRatio > 0) { diff --git a/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php b/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php index 20e44be1..aa001655 100644 --- a/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php +++ b/pro_v3.5.1/app/services/system/timer/SystemTimerServices.php @@ -236,15 +236,15 @@ class SystemTimerServices extends BaseServices /** * 手动立即执行指定定时任务(HTTP 请求上下文,绕过 Swoole Cron 构造,用反射直接调用 implement_timer) * @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()]); try { $ref = new \ReflectionClass(SystemTimerListener::class); $instance = $ref->newInstanceWithoutConstructor(); - $instance->implement_timer($mark); + return $instance->implement_timer($mark); } catch (\Throwable $e) { $taskName = $this->taskName[$mark] ?? $mark; throw new \RuntimeException("定时任务[{$taskName}]执行失败: " . $e->getMessage(), 0, $e); diff --git a/pro_v3.5.1/view/uniapp_v2/pages/assets/index.vue b/pro_v3.5.1/view/uniapp_v2/pages/assets/index.vue index 90ac0c38..aafdf11a 100644 --- a/pro_v3.5.1/view/uniapp_v2/pages/assets/index.vue +++ b/pro_v3.5.1/view/uniapp_v2/pages/assets/index.vue @@ -220,7 +220,7 @@ export default {