Procházet zdrojové kódy

1.积分规则接口新增superadmin角色验证2.新增可发放积分用户列表和发放积分接口

lh před 1 měsícem
rodič
revize
2a2c2f0057

+ 72 - 0
app/Http/Controllers/Points/PointsController.php

@@ -2,6 +2,7 @@
 
 namespace App\Http\Controllers\Points;
 
+use App\Facade\Site;
 use App\Libs\ApiResponse;
 use App\Libs\Utils;
 use App\Services\PointsService;
@@ -51,6 +52,8 @@ class PointsController extends BaseController
      */
     public function rules(Request $request)
     {
+        $this->checkSuperadmin();
+
         return $this->success($this->pointsService->getPointsRules());
     }
 
@@ -68,6 +71,8 @@ class PointsController extends BaseController
      */
     public function editRules(Request $request)
     {
+        $this->checkSuperadmin();
+
         $data = $request->all();
 
         $validator = Validator::make($data, [
@@ -98,4 +103,71 @@ class PointsController extends BaseController
             'remark' => '计费规则已更新,后续生成任务按新规则扣费',
         ]);
     }
+
+    /**
+     * 发放积分(平台给组织 / 组织给同公司组员)
+     *
+     * 入参:
+     * - uid:目标用户ID(必填)
+     * - points:发放积分数(必填,大于0)
+     * - remark:备注文案(可选)
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function grantPoints(Request $request)
+    {
+        $data = $request->all();
+
+        $validator = Validator::make($data, [
+            'uid' => 'required|integer|min:1',
+            'points' => 'required|integer|gt:0',
+            'remark' => 'nullable|string|max:200',
+        ], [
+            'uid.required' => '请传入目标用户uid',
+            'uid.integer' => 'uid必须为整数',
+            'uid.min' => 'uid必须大于0',
+            'points.required' => '请传入发放积分数',
+            'points.integer' => '发放积分数必须为整数',
+            'points.gt' => '发放积分数必须大于0',
+            'remark.max' => '备注不能超过200个字符',
+        ]);
+
+        if ($validator->fails()) {
+            Utils::throwError('1002:' . $validator->errors()->first());
+        }
+
+        $result = $this->pointsService->grantPoints($data);
+
+        return $this->success($result);
+    }
+
+    /**
+     * 获取可发放积分的用户列表
+     *
+     * 同一接口按当前角色返回:
+     * - 平台(superadmin):所有组织
+     * - 组织(admin):同公司组员
+     *
+     * @param Request $request
+     * @return mixed
+     */
+    public function grantUsers(Request $request)
+    {
+        return $this->success([
+            'list' => $this->pointsService->getGrantUserList(),
+        ]);
+    }
+
+    /**
+     * 校验当前用户是否为平台
+     *
+     * @return void
+     */
+    private function checkSuperadmin(): void
+    {
+        if (Site::getRole() !== 'superadmin') {
+            Utils::throwError('1005:仅平台可操作积分规则');
+        }
+    }
 }

+ 10 - 0
app/Models/MpUserPointsDetail.php

@@ -12,6 +12,16 @@ class MpUserPointsDetail extends Model
     const TYPE_IMAGE = 'image';
     const TYPE_CHAT = 'chat';
 
+    /**
+     * 平台给组织发放积分
+     */
+    const TYPE_SYSTEM = 'system';
+
+    /**
+     * 组织给同公司组员发放积分
+     */
+    const TYPE_COMPANY = 'company';
+
     protected $fillable = [
         'uid',
         'cpid',

+ 194 - 0
app/Services/PointsService.php

@@ -532,8 +532,14 @@ class PointsService
                 ->sum('points_consumed'),
             'total_points_refunded' => (float)MpUserPointsDetail::where('uid', $uid)
                 ->where('points_consumed', '<', 0)
+                ->whereNotIn('type', [MpUserPointsDetail::TYPE_SYSTEM, MpUserPointsDetail::TYPE_COMPANY])
                 ->where($excludeTestRecords)
                 ->sum('points_consumed'),
+            'total_points_granted' => (float)abs(MpUserPointsDetail::where('uid', $uid)
+                ->whereIn('type', [MpUserPointsDetail::TYPE_SYSTEM, MpUserPointsDetail::TYPE_COMPANY])
+                ->where('points_consumed', '<', 0)
+                ->where($excludeTestRecords)
+                ->sum('points_consumed')),
             'total_tokens_consumed' => (int)MpUserPointsDetail::where('uid', $uid)
                 ->where($excludeTestRecords)
                 ->sum('tokens_consumed'),
@@ -549,6 +555,194 @@ class PointsService
     }
 
     /**
+     * 发放积分(公共方法)
+     *
+     * 权限规则:
+     * - superadmin(平台):仅可给 role=admin(组织)发放积分,明细 type=system
+     * - admin(组织):仅可给同 cpid 的 role=user(组员)发放积分,明细 type=company
+     * - 其他角色不允许发放
+     *
+     * 发放成功后写入 mp_user_points_details(points_consumed 记负值,表示积分流入),
+     * 并原子累加目标用户余额。
+     *
+     * @param array $params uid(目标用户ID)/ points(发放积分数,正数)/ remark(可选文案)
+     * @return array
+     */
+    public function grantPoints(array $params): array
+    {
+        $fromUid = (int)Site::getUid();
+        $fromRole = (string)Site::getRole();
+        if ($fromUid <= 0) {
+            Utils::throwError(ErrorConst::NOT_LOGIN);
+        }
+        $fromUser = DB::table('mp_manage_users')->where('id', $fromUid)->first();
+        if (!$fromUser) {
+            Utils::throwError(ErrorConst::USER_IS_NOT_EXIST);
+        }
+
+        $targetUid = (int)getProp($params, 'uid', 0);
+        $points = (float)getProp($params, 'points', 0);
+        $remark = trim((string)getProp($params, 'remark', ''));
+
+        if ($targetUid <= 0) {
+            Utils::throwError('1002:请传入目标用户uid');
+        }
+        if ($points <= 0) {
+            Utils::throwError('1002:发放积分数必须大于0');
+        }
+        if (floor($points) != $points) {
+            Utils::throwError('1002:发放积分数必须为整数');
+        }
+
+        $targetUser = DB::table('mp_manage_users')->where('id', $targetUid)->first();
+        if (!$targetUser) {
+            Utils::throwError('20003:目标用户不存在');
+        }
+        if ((int)getProp($targetUser, 'is_enabled', 1) === 0) {
+            Utils::throwError('20003:目标用户已被禁用');
+        }
+
+        $targetRole = (string)getProp($targetUser, 'role', '');
+        $targetCpid = (int)getProp($targetUser, 'cpid', 0);
+
+        // 角色与发放对象校验
+        if ($fromRole === 'superadmin') {
+            if ($targetRole !== 'admin') {
+                Utils::throwError('1005:平台仅可给组织发放积分');
+            }
+            $type = MpUserPointsDetail::TYPE_SYSTEM;
+            $defaultRemark = '平台发放积分';
+        } elseif ($fromRole === 'admin') {
+            if ($targetRole !== 'user') {
+                Utils::throwError('1005:组织仅可给同公司的组员发放积分');
+            }
+            if ($targetCpid !== (int)Site::getCpid()) {
+                Utils::throwError('1005:只能给同一公司的组员发放积分');
+            }
+            $type = MpUserPointsDetail::TYPE_COMPANY;
+            $defaultRemark = '组织发放积分';
+        } else {
+            Utils::throwError('1005:当前角色无权发放积分');
+        }
+
+        $finalRemark = $remark !== '' ? $defaultRemark . ':' . $remark : $defaultRemark;
+        $chargeInfo = [
+            'from_uid' => $fromUid,
+            'from_role' => $fromRole,
+            'target_role' => $targetRole,
+            'target_cpid' => $targetCpid,
+            'grant_type' => $type,
+        ];
+
+        try {
+            DB::beginTransaction();
+
+            $pointsBefore = (float)getProp($targetUser, 'points', 0);
+            $pointsAfter = $pointsBefore + $points;
+
+            // 原子累加积分余额,避免并发覆盖
+            DB::table('mp_manage_users')->where('id', $targetUid)->update([
+                'points' => DB::raw('points + ' . (float)$points),
+                'updated_at' => date('Y-m-d H:i:s'),
+            ]);
+
+            // 发放明细:points_consumed 记负值,表示积分流入
+            DB::table('mp_user_points_details')->insert([
+                'uid' => $targetUid,
+                'cpid' => $targetCpid,
+                'task_id' => null,
+                'type' => $type,
+                'api_type' => 'grant',
+                'charge_info' => json_encode($chargeInfo, JSON_UNESCAPED_UNICODE),
+                'points_before' => $pointsBefore,
+                'points_consumed' => -$points,
+                'points_after' => $pointsAfter,
+                'tokens_consumed' => 0,
+                'remark' => $finalRemark,
+                'created_at' => date('Y-m-d H:i:s'),
+                'updated_at' => date('Y-m-d H:i:s'),
+            ]);
+
+            DB::commit();
+
+            dLog('points')->info('积分发放成功', [
+                'from_uid' => $fromUid,
+                'from_role' => $fromRole,
+                'target_uid' => $targetUid,
+                'points' => $points,
+                'type' => $type,
+            ]);
+
+            return [
+                'granted' => true,
+                'type' => $type,
+                'uid' => $targetUid,
+                'points' => $points,
+                'points_before' => $pointsBefore,
+                'points_after' => $pointsAfter,
+                'remark' => $finalRemark,
+            ];
+
+        } catch (\Exception $e) {
+            DB::rollBack();
+
+            dLog('points')->error('积分发放失败: ' . $e->getMessage(), [
+                'from_uid' => $fromUid,
+                'target_uid' => $targetUid,
+                'points' => $points,
+            ]);
+            logDB('points', 'error', '积分发放失败', [
+                'from_uid' => $fromUid,
+                'target_uid' => $targetUid,
+                'points' => $points,
+                'error' => $e->getMessage(),
+            ]);
+
+            Utils::throwError('1006:积分发放失败,请稍后重试');
+        }
+    }
+
+    /**
+     * 获取可发放积分的用户列表(公共方法)
+     *
+     * 同一接口按当前角色返回不同列表:
+     * - superadmin(平台):所有组织(role=admin)
+     * - admin(组织):同 cpid 的组员(role=user)
+     * - 其他角色不允许查看
+     *
+     * @return array
+     */
+    public function getGrantUserList(): array
+    {
+        $role = (string)Site::getRole();
+
+        $query = DB::table('mp_manage_users')->where('is_enabled', 1);
+        if ($role === 'superadmin') {
+            $query->where('role', 'admin');
+        } elseif ($role === 'admin') {
+            $query->where('role', 'user')->where('cpid', (int)Site::getCpid());
+        } else {
+            Utils::throwError('1005:当前角色无权查看发放列表');
+        }
+
+        $users = $query->orderBy('cpid', 'asc')->orderBy('id', 'asc')
+            ->get(['id', 'account', 'nickname', 'role', 'cpid', 'points', 'is_enabled'])
+            ->map(function ($user) {
+                return [
+                    'id' => (int)getProp($user, 'id', 0),
+                    'account' => (string)getProp($user, 'account', ''),
+                    'nickname' => (string)getProp($user, 'nickname', ''),
+                    // 'role' => (string)getProp($user, 'role', ''),
+                    // 'cpid' => (int)getProp($user, 'cpid', 0),
+                    'points' => (float)getProp($user, 'points', 0),
+                    // 'is_enabled' => (int)getProp($user, 'is_enabled', 1),
+                ];
+            })->all();
+
+        return $users;
+    }
+
+    /**
      * 获取用户当前积分余额
      *
      * @param int $uid 用户ID,缺省取当前登录用户

+ 14 - 1
app/Transformer/Points/PointsTransformer.php

@@ -11,6 +11,8 @@ class PointsTransformer
         'image_refund' => '图片生成退款',
         'chat' => 'AI对话',
         'chat_refund' => 'AI对话退款',
+        'system' => '平台发放',
+        'company' => '组织发放',
     ];
 
     /**
@@ -46,6 +48,16 @@ class PointsTransformer
             $chargeInfo = is_array($chargeInfo) ? $chargeInfo : [];
 
             $type = getProp($item, 'type', '');
+            $pointsConsumed = (float)getProp($item, 'points_consumed', 0);
+            if ($pointsConsumed > 0) {
+                // 消耗:带负号
+                $pointsChange = '-' . $pointsConsumed;
+            } elseif ($pointsConsumed < 0) {
+                // 赠送/入账:带正号
+                $pointsChange = '+' . abs($pointsConsumed);
+            } else {
+                $pointsChange = '0';
+            }
             $result[] = [
                 'id' => (int)getProp($item, 'id'),
                 'type' => $type,
@@ -53,7 +65,8 @@ class PointsTransformer
                 'api_type' => getProp($item, 'api_type'),
                 'task_id' => (int)getProp($item, 'task_id'),
                 'charge_info' => $chargeInfo,
-                'points_consumed' => (float)getProp($item, 'points_consumed', 0),
+                'points_consumed' => $pointsConsumed,
+                'points_change' => $pointsChange,
                 'points_before' => (float)getProp($item, 'points_before', 0),
                 'points_after' => (float)getProp($item, 'points_after', 0),
                 'tokens_consumed' => (int)getProp($item, 'tokens_consumed', 0),

+ 2 - 0
routes/api.php

@@ -89,6 +89,8 @@ Route::group(['middleware' => ['bindToken', 'bindExportToken', 'checkLogin']], f
 
         Route::get('rules', [PointsController::class, 'rules']);            // 获取积分规则
         Route::post('editRule', [PointsController::class, 'editRules']);        // 编辑积分规则
+        Route::post('grant', [PointsController::class, 'grantPoints']);      // 发放积分(超管→管理员 / 管理员→同公司组员)
+        Route::get('grantUsers', [PointsController::class, 'grantUsers']);   // 可发放积分用户列表
     });