|
@@ -525,17 +525,17 @@ class PointsService
|
|
|
|
|
|
|
|
$user = DB::table('mp_manage_users')->where('id', $uid)->first();
|
|
$user = DB::table('mp_manage_users')->where('id', $uid)->first();
|
|
|
$summary = [
|
|
$summary = [
|
|
|
- 'points_balance' => (float)getProp($user, 'points', 0),
|
|
|
|
|
- 'total_points_consumed' => (float)MpUserPointsDetail::where('uid', $uid)
|
|
|
|
|
|
|
+ 'points_balance' => (int)getProp($user, 'points', 0),
|
|
|
|
|
+ 'total_points_consumed' => (int)MpUserPointsDetail::where('uid', $uid)
|
|
|
->where('points_consumed', '>', 0)
|
|
->where('points_consumed', '>', 0)
|
|
|
->where($excludeTestRecords)
|
|
->where($excludeTestRecords)
|
|
|
->sum('points_consumed'),
|
|
->sum('points_consumed'),
|
|
|
- 'total_points_refunded' => (float)MpUserPointsDetail::where('uid', $uid)
|
|
|
|
|
|
|
+ 'total_points_refunded' => (int)MpUserPointsDetail::where('uid', $uid)
|
|
|
->where('points_consumed', '<', 0)
|
|
->where('points_consumed', '<', 0)
|
|
|
->whereNotIn('type', [MpUserPointsDetail::TYPE_SYSTEM, MpUserPointsDetail::TYPE_COMPANY])
|
|
->whereNotIn('type', [MpUserPointsDetail::TYPE_SYSTEM, MpUserPointsDetail::TYPE_COMPANY])
|
|
|
->where($excludeTestRecords)
|
|
->where($excludeTestRecords)
|
|
|
->sum('points_consumed'),
|
|
->sum('points_consumed'),
|
|
|
- 'total_points_granted' => (float)abs(MpUserPointsDetail::where('uid', $uid)
|
|
|
|
|
|
|
+ 'total_points_granted' => (int)abs(MpUserPointsDetail::where('uid', $uid)
|
|
|
->whereIn('type', [MpUserPointsDetail::TYPE_SYSTEM, MpUserPointsDetail::TYPE_COMPANY])
|
|
->whereIn('type', [MpUserPointsDetail::TYPE_SYSTEM, MpUserPointsDetail::TYPE_COMPANY])
|
|
|
->where('points_consumed', '<', 0)
|
|
->where('points_consumed', '<', 0)
|
|
|
->where($excludeTestRecords)
|
|
->where($excludeTestRecords)
|
|
@@ -555,43 +555,132 @@ class PointsService
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 发放积分(公共方法)
|
|
|
|
|
|
|
+ * 发放/回收积分(公共方法)
|
|
|
*
|
|
*
|
|
|
* 权限规则:
|
|
* 权限规则:
|
|
|
- * - superadmin(平台):仅可给 role=admin(组织)发放积分,明细 type=system
|
|
|
|
|
- * - admin(组织):仅可给同 cpid 的 role=user(组员)发放积分,明细 type=company
|
|
|
|
|
- * - 其他角色不允许发放
|
|
|
|
|
|
|
+ * - superadmin(平台):仅可对 role=admin(组织)操作,明细 type=system
|
|
|
|
|
+ * - admin(组织):仅可对同 cpid 的 role=user(组员)操作,明细 type=company
|
|
|
|
|
+ * - 其他角色不允许操作
|
|
|
*
|
|
*
|
|
|
- * 发放成功后写入 mp_user_points_details(points_consumed 记负值,表示积分流入),
|
|
|
|
|
- * 并原子累加目标用户余额。
|
|
|
|
|
|
|
+ * action=grant(发放,默认):目标用户积分增加,points_consumed 记负值(积分流入)
|
|
|
|
|
+ * action=revoke(回收):目标用户积分减少,需校验目标积分充足(只针对组织/组员角色),points_consumed 记正值(积分流出)
|
|
|
*
|
|
*
|
|
|
- * @param array $params uid(目标用户ID)/ points(发放积分数,正数)/ remark(可选文案)
|
|
|
|
|
|
|
+ * @param array $params uid(目标用户ID,支持多个ID用英文逗号隔开,如 "142857,142858")/ points(积分数,正整数)/ action(grant|revoke,默认 grant)/ remark(可选文案)
|
|
|
* @return array
|
|
* @return array
|
|
|
*/
|
|
*/
|
|
|
public function grantPoints(array $params): 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);
|
|
|
|
|
|
|
+ $uidRaw = trim((string)getProp($params, 'uid', ''));
|
|
|
$points = (float)getProp($params, 'points', 0);
|
|
$points = (float)getProp($params, 'points', 0);
|
|
|
|
|
+ $action = (string)getProp($params, 'action', 'grant');
|
|
|
$remark = trim((string)getProp($params, 'remark', ''));
|
|
$remark = trim((string)getProp($params, 'remark', ''));
|
|
|
|
|
|
|
|
- if ($targetUid <= 0) {
|
|
|
|
|
|
|
+ // 解析 uid:单个ID或多个ID(英文逗号隔开),自动去重、忽略空白
|
|
|
|
|
+ $uids = array_values(array_unique(array_filter(array_map(function ($v) {
|
|
|
|
|
+ return (int)trim((string)$v);
|
|
|
|
|
+ }, explode(',', $uidRaw)), function ($v) {
|
|
|
|
|
+ return $v > 0;
|
|
|
|
|
+ })));
|
|
|
|
|
+ if (empty($uids)) {
|
|
|
Utils::throwError('1002:请传入目标用户uid');
|
|
Utils::throwError('1002:请传入目标用户uid');
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ $this->validatePoints($points);
|
|
|
|
|
+ $this->validateAction($action);
|
|
|
|
|
+
|
|
|
|
|
+ // 单用户:保持原有返回结构
|
|
|
|
|
+ if (count($uids) === 1) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ DB::beginTransaction();
|
|
|
|
|
+ $result = $this->applyPointsChange($uids[0], $points, $action, $remark);
|
|
|
|
|
+ DB::commit();
|
|
|
|
|
+ return $result;
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ throw $e;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 多用户:先对全部目标校验(只读,不写库),任一失败整体拒绝;再单事务统一写库,保证原子性
|
|
|
|
|
+ foreach ($uids as $uid) {
|
|
|
|
|
+ $this->buildGrantContext($uid, $action, $points);
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ DB::beginTransaction();
|
|
|
|
|
+ $results = [];
|
|
|
|
|
+ foreach ($uids as $uid) {
|
|
|
|
|
+ $results[] = $this->applyPointsChange($uid, $points, $action, $remark);
|
|
|
|
|
+ }
|
|
|
|
|
+ DB::commit();
|
|
|
|
|
+ } catch (\Exception $e) {
|
|
|
|
|
+ DB::rollBack();
|
|
|
|
|
+ throw $e;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ dLog('points')->info('积分批量操作成功', [
|
|
|
|
|
+ 'action' => $action,
|
|
|
|
|
+ 'count' => count($uids),
|
|
|
|
|
+ 'points' => $points,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
|
|
+ 'action' => $action,
|
|
|
|
|
+ 'points' => $points,
|
|
|
|
|
+ 'success_count' => count($results),
|
|
|
|
|
+ 'results' => $results,
|
|
|
|
|
+ ];
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验积分数(正整数)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param float $points
|
|
|
|
|
+ * @return void
|
|
|
|
|
+ */
|
|
|
|
|
+ private function validatePoints(float $points): void
|
|
|
|
|
+ {
|
|
|
if ($points <= 0) {
|
|
if ($points <= 0) {
|
|
|
- Utils::throwError('1002:发放积分数必须大于0');
|
|
|
|
|
|
|
+ Utils::throwError('1002:积分数必须大于0');
|
|
|
}
|
|
}
|
|
|
if (floor($points) != $points) {
|
|
if (floor($points) != $points) {
|
|
|
- Utils::throwError('1002:发放积分数必须为整数');
|
|
|
|
|
|
|
+ Utils::throwError('1002:积分数必须为整数');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验操作类型
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param string $action
|
|
|
|
|
+ * @return void
|
|
|
|
|
+ */
|
|
|
|
|
+ private function validateAction(string $action): void
|
|
|
|
|
+ {
|
|
|
|
|
+ if (!in_array($action, ['grant', 'revoke'], true)) {
|
|
|
|
|
+ Utils::throwError('1002:action仅支持grant(发放)或revoke(回收)');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 校验并构建单用户积分操作上下文(只读)
|
|
|
|
|
+ *
|
|
|
|
|
+ * 校验当前操作者角色、目标用户存在/启用、目标角色与公司归属;
|
|
|
|
|
+ * 回收时额外校验目标用户积分充足;管理员发放时额外校验管理员自身可用积分充足。
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $targetUid
|
|
|
|
|
+ * @param string $action
|
|
|
|
|
+ * @param float $points
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ private function buildGrantContext(int $targetUid, string $action, float $points): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $fromUid = (int)Site::getUid();
|
|
|
|
|
+ if ($fromUid <= 0) {
|
|
|
|
|
+ Utils::throwError(ErrorConst::NOT_LOGIN);
|
|
|
|
|
+ }
|
|
|
|
|
+ $fromRole = (string)Site::getRole();
|
|
|
|
|
+ $fromUser = DB::table('mp_manage_users')->where('id', $fromUid)->first();
|
|
|
|
|
+ if (!$fromUser) {
|
|
|
|
|
+ Utils::throwError(ErrorConst::USER_IS_NOT_EXIST);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
$targetUser = DB::table('mp_manage_users')->where('id', $targetUid)->first();
|
|
$targetUser = DB::table('mp_manage_users')->where('id', $targetUid)->first();
|
|
@@ -604,102 +693,200 @@ class PointsService
|
|
|
|
|
|
|
|
$targetRole = (string)getProp($targetUser, 'role', '');
|
|
$targetRole = (string)getProp($targetUser, 'role', '');
|
|
|
$targetCpid = (int)getProp($targetUser, 'cpid', 0);
|
|
$targetCpid = (int)getProp($targetUser, 'cpid', 0);
|
|
|
|
|
+ $verb = $action === 'revoke' ? '回收' : '发放';
|
|
|
|
|
|
|
|
- // 角色与发放对象校验
|
|
|
|
|
|
|
+ // 角色与操作对象校验
|
|
|
if ($fromRole === 'superadmin') {
|
|
if ($fromRole === 'superadmin') {
|
|
|
if ($targetRole !== 'admin') {
|
|
if ($targetRole !== 'admin') {
|
|
|
- Utils::throwError('1005:平台仅可给组织发放积分');
|
|
|
|
|
|
|
+ Utils::throwError('1005:平台仅可给组织' . $verb . '积分');
|
|
|
}
|
|
}
|
|
|
$type = MpUserPointsDetail::TYPE_SYSTEM;
|
|
$type = MpUserPointsDetail::TYPE_SYSTEM;
|
|
|
- $defaultRemark = '平台发放积分';
|
|
|
|
|
|
|
+ $defaultRemark = $action === 'revoke' ? '平台回收积分' : '平台发放积分';
|
|
|
} elseif ($fromRole === 'admin') {
|
|
} elseif ($fromRole === 'admin') {
|
|
|
if ($targetRole !== 'user') {
|
|
if ($targetRole !== 'user') {
|
|
|
- Utils::throwError('1005:组织仅可给同公司的组员发放积分');
|
|
|
|
|
|
|
+ Utils::throwError('1005:组织仅可给同公司的组员' . $verb . '积分');
|
|
|
}
|
|
}
|
|
|
if ($targetCpid !== (int)Site::getCpid()) {
|
|
if ($targetCpid !== (int)Site::getCpid()) {
|
|
|
- Utils::throwError('1005:只能给同一公司的组员发放积分');
|
|
|
|
|
|
|
+ Utils::throwError('1005:只能给同一公司的组员' . $verb . '积分');
|
|
|
}
|
|
}
|
|
|
$type = MpUserPointsDetail::TYPE_COMPANY;
|
|
$type = MpUserPointsDetail::TYPE_COMPANY;
|
|
|
- $defaultRemark = '组织发放积分';
|
|
|
|
|
|
|
+ $defaultRemark = $action === 'revoke' ? '组织回收积分' : '组织发放积分';
|
|
|
} else {
|
|
} else {
|
|
|
- Utils::throwError('1005:当前角色无权发放积分');
|
|
|
|
|
|
|
+ Utils::throwError('1005:当前角色无权操作积分');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- $finalRemark = $remark !== '' ? $defaultRemark . ':' . $remark : $defaultRemark;
|
|
|
|
|
- $chargeInfo = [
|
|
|
|
|
|
|
+ // 回收时校验目标积分充足(目标只能是组织/组员角色,超管不在操作范围内,无需判断)
|
|
|
|
|
+ if ($action === 'revoke') {
|
|
|
|
|
+ $balance = (float)getProp($targetUser, 'points', 0);
|
|
|
|
|
+ if ($balance < $points) {
|
|
|
|
|
+ Utils::throwError('1002:目标用户积分不足,无法回收');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 管理员发放:积分从管理员自身扣减转移给组员,需校验管理员可用积分充足(平台发放不校验)
|
|
|
|
|
+ if ($fromRole === 'admin' && $action === 'grant') {
|
|
|
|
|
+ $operatorBalance = (float)getProp($fromUser, 'points', 0);
|
|
|
|
|
+ if ($operatorBalance < $points) {
|
|
|
|
|
+ Utils::throwError('1002:可用积分不足,无法发放');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return [
|
|
|
'from_uid' => $fromUid,
|
|
'from_uid' => $fromUid,
|
|
|
'from_role' => $fromRole,
|
|
'from_role' => $fromRole,
|
|
|
|
|
+ 'from_cpid' => (int)getProp($fromUser, 'cpid', 0),
|
|
|
|
|
+ 'from_user' => $fromUser,
|
|
|
|
|
+ 'target_user' => $targetUser,
|
|
|
'target_role' => $targetRole,
|
|
'target_role' => $targetRole,
|
|
|
'target_cpid' => $targetCpid,
|
|
'target_cpid' => $targetCpid,
|
|
|
- 'grant_type' => $type,
|
|
|
|
|
|
|
+ 'target_uid' => $targetUid,
|
|
|
|
|
+ 'type' => $type,
|
|
|
|
|
+ 'default_remark' => $defaultRemark,
|
|
|
];
|
|
];
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- try {
|
|
|
|
|
- DB::beginTransaction();
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 对单个用户执行积分变动并写入明细(须在事务内调用)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param int $targetUid
|
|
|
|
|
+ * @param float $points
|
|
|
|
|
+ * @param string $action grant|revoke
|
|
|
|
|
+ * @param string $remark
|
|
|
|
|
+ * @return array
|
|
|
|
|
+ */
|
|
|
|
|
+ private function applyPointsChange(int $targetUid, float $points, string $action, string $remark): array
|
|
|
|
|
+ {
|
|
|
|
|
+ $ctx = $this->buildGrantContext($targetUid, $action, $points);
|
|
|
|
|
|
|
|
- $pointsBefore = (float)getProp($targetUser, 'points', 0);
|
|
|
|
|
- $pointsAfter = $pointsBefore + $points;
|
|
|
|
|
|
|
+ $finalRemark = $remark !== '' ? $ctx['default_remark'] . ':' . $remark : $ctx['default_remark'];
|
|
|
|
|
+ $chargeInfo = [
|
|
|
|
|
+ 'from_uid' => $ctx['from_uid'],
|
|
|
|
|
+ 'from_role' => $ctx['from_role'],
|
|
|
|
|
+ 'target_uid' => $targetUid,
|
|
|
|
|
+ 'target_role' => $ctx['target_role'],
|
|
|
|
|
+ 'target_cpid' => $ctx['target_cpid'],
|
|
|
|
|
+ 'grant_type' => $ctx['type'],
|
|
|
|
|
+ 'action' => $action,
|
|
|
|
|
+ ];
|
|
|
|
|
|
|
|
- // 原子累加积分余额,避免并发覆盖
|
|
|
|
|
- DB::table('mp_manage_users')->where('id', $targetUid)->update([
|
|
|
|
|
- 'points' => DB::raw('points + ' . (float)$points),
|
|
|
|
|
- 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
- ]);
|
|
|
|
|
|
|
+ $pointsBefore = (float)getProp($ctx['target_user'], 'points', 0);
|
|
|
|
|
+ $delta = $action === 'revoke' ? -$points : $points;
|
|
|
|
|
+ $pointsAfter = $pointsBefore + $delta;
|
|
|
|
|
+
|
|
|
|
|
+ $now = date('Y-m-d H:i:s');
|
|
|
|
|
+ $operatorAfter = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 原子更新余额,避免并发覆盖;COALESCE 处理余额为 NULL 的用户(NULL ± X 在 MySQL 中仍为 NULL)
|
|
|
|
|
+ if ($ctx['from_role'] === 'admin') {
|
|
|
|
|
+ // 转移模式:积分在管理员与组员之间流转,整体数额不变
|
|
|
|
|
+ $operatorBefore = (float)getProp($ctx['from_user'], 'points', 0);
|
|
|
|
|
+ if ($action === 'revoke') {
|
|
|
|
|
+ // 组员扣减(带余额条件),管理员增加
|
|
|
|
|
+ $affected = DB::table('mp_manage_users')
|
|
|
|
|
+ ->where('id', $targetUid)
|
|
|
|
|
+ ->whereRaw('COALESCE(points, 0) >= ?', [(float)$points])
|
|
|
|
|
+ ->update([
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) - ' . (float)$points),
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ if ($affected === 0) {
|
|
|
|
|
+ Utils::throwError('1002:目标用户积分不足,无法回收');
|
|
|
|
|
+ }
|
|
|
|
|
+ DB::table('mp_manage_users')->where('id', $ctx['from_uid'])->update([
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) + ' . (float)$points),
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ $operatorDelta = $points;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 管理员扣减(带余额条件,防止并发超发),组员增加
|
|
|
|
|
+ $affected = DB::table('mp_manage_users')
|
|
|
|
|
+ ->where('id', $ctx['from_uid'])
|
|
|
|
|
+ ->whereRaw('COALESCE(points, 0) >= ?', [(float)$points])
|
|
|
|
|
+ ->update([
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) - ' . (float)$points),
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ if ($affected === 0) {
|
|
|
|
|
+ Utils::throwError('1002:可用积分不足,无法发放');
|
|
|
|
|
+ }
|
|
|
|
|
+ DB::table('mp_manage_users')->where('id', $targetUid)->update([
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) + ' . (float)$points),
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ $operatorDelta = -$points;
|
|
|
|
|
+ }
|
|
|
|
|
+ $operatorAfter = $operatorBefore + $operatorDelta;
|
|
|
|
|
|
|
|
- // 发放明细:points_consumed 记负值,表示积分流入
|
|
|
|
|
|
|
+ // 管理员侧明细(方向与组员相反):发放记支出(正),回收记收入(负)
|
|
|
DB::table('mp_user_points_details')->insert([
|
|
DB::table('mp_user_points_details')->insert([
|
|
|
- 'uid' => $targetUid,
|
|
|
|
|
- 'cpid' => $targetCpid,
|
|
|
|
|
|
|
+ 'uid' => $ctx['from_uid'],
|
|
|
|
|
+ 'cpid' => $ctx['from_cpid'],
|
|
|
'task_id' => null,
|
|
'task_id' => null,
|
|
|
- 'type' => $type,
|
|
|
|
|
- 'api_type' => 'grant',
|
|
|
|
|
|
|
+ 'type' => $ctx['type'],
|
|
|
|
|
+ 'api_type' => $action === 'revoke' ? 'revoke' : 'grant',
|
|
|
'charge_info' => json_encode($chargeInfo, JSON_UNESCAPED_UNICODE),
|
|
'charge_info' => json_encode($chargeInfo, JSON_UNESCAPED_UNICODE),
|
|
|
- 'points_before' => $pointsBefore,
|
|
|
|
|
- 'points_consumed' => -$points,
|
|
|
|
|
- 'points_after' => $pointsAfter,
|
|
|
|
|
|
|
+ 'points_before' => $operatorBefore,
|
|
|
|
|
+ 'points_consumed' => -$operatorDelta,
|
|
|
|
|
+ 'points_after' => $operatorAfter,
|
|
|
'tokens_consumed' => 0,
|
|
'tokens_consumed' => 0,
|
|
|
'remark' => $finalRemark,
|
|
'remark' => $finalRemark,
|
|
|
- 'created_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
- 'updated_at' => date('Y-m-d H:i:s'),
|
|
|
|
|
|
|
+ 'created_at' => $now,
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
]);
|
|
]);
|
|
|
-
|
|
|
|
|
- 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(),
|
|
|
|
|
|
|
+ } elseif ($action === 'revoke') {
|
|
|
|
|
+ // 平台回收:目标(组织)扣减,带余额条件
|
|
|
|
|
+ $affected = DB::table('mp_manage_users')
|
|
|
|
|
+ ->where('id', $targetUid)
|
|
|
|
|
+ ->whereRaw('COALESCE(points, 0) >= ?', [(float)$points])
|
|
|
|
|
+ ->update([
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) - ' . (float)$points),
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+ if ($affected === 0) {
|
|
|
|
|
+ Utils::throwError('1002:目标用户积分不足,无法回收');
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 平台发放:目标(组织)增加(平台为积分源头,不校验平台余额)
|
|
|
|
|
+ DB::table('mp_manage_users')->where('id', $targetUid)->update([
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) + ' . (float)$points),
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
]);
|
|
]);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- Utils::throwError('1006:积分发放失败,请稍后重试');
|
|
|
|
|
|
|
+ // 目标侧明细:发放 points_consumed 记负值(流入),回收记正值(流出)
|
|
|
|
|
+ DB::table('mp_user_points_details')->insert([
|
|
|
|
|
+ 'uid' => $targetUid,
|
|
|
|
|
+ 'cpid' => $ctx['target_cpid'],
|
|
|
|
|
+ 'task_id' => null,
|
|
|
|
|
+ 'type' => $ctx['type'],
|
|
|
|
|
+ 'api_type' => $action === 'revoke' ? 'revoke' : 'grant',
|
|
|
|
|
+ 'charge_info' => json_encode($chargeInfo, JSON_UNESCAPED_UNICODE),
|
|
|
|
|
+ 'points_before' => $pointsBefore,
|
|
|
|
|
+ 'points_consumed' => -$delta,
|
|
|
|
|
+ 'points_after' => $pointsAfter,
|
|
|
|
|
+ 'tokens_consumed' => 0,
|
|
|
|
|
+ 'remark' => $finalRemark,
|
|
|
|
|
+ 'created_at' => $now,
|
|
|
|
|
+ 'updated_at' => $now,
|
|
|
|
|
+ ]);
|
|
|
|
|
+
|
|
|
|
|
+ $result = [
|
|
|
|
|
+ 'uid' => $targetUid,
|
|
|
|
|
+ 'success' => true,
|
|
|
|
|
+ 'action' => $action,
|
|
|
|
|
+ 'type' => $ctx['type'],
|
|
|
|
|
+ 'points' => $points,
|
|
|
|
|
+ 'points_before' => $pointsBefore,
|
|
|
|
|
+ 'points_after' => $pointsAfter,
|
|
|
|
|
+ 'remark' => $finalRemark,
|
|
|
|
|
+ ];
|
|
|
|
|
+ if ($operatorAfter !== null) {
|
|
|
|
|
+ $result['operator_uid'] = $ctx['from_uid'];
|
|
|
|
|
+ $result['operator_points_before'] = $operatorBefore;
|
|
|
|
|
+ $result['operator_points_after'] = $operatorAfter;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ return $result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -1217,9 +1404,10 @@ class PointsService
|
|
|
|
|
|
|
|
if (!$isTestUser) {
|
|
if (!$isTestUser) {
|
|
|
// 原子扣减积分:基于数据库当前值执行(points = points - X),
|
|
// 原子扣减积分:基于数据库当前值执行(points = points - X),
|
|
|
- // 避免并发处理不同任务时基于旧快照覆盖写入导致丢失更新(少扣)
|
|
|
|
|
|
|
+ // 避免并发处理不同任务时基于旧快照覆盖写入导致丢失更新(少扣);
|
|
|
|
|
+ // COALESCE 处理余额为 NULL 的用户(NULL - X 在 MySQL 中仍为 NULL)
|
|
|
DB::table('mp_manage_users')->where('id', $uid)->update([
|
|
DB::table('mp_manage_users')->where('id', $uid)->update([
|
|
|
- 'points' => DB::raw('points - ' . (float)$points),
|
|
|
|
|
|
|
+ 'points' => DB::raw('COALESCE(points, 0) - ' . (float)$points),
|
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
'updated_at' => date('Y-m-d H:i:s')
|
|
|
]);
|
|
]);
|
|
|
}
|
|
}
|