LogService.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. <?php
  2. namespace App\Module\Point\Services;
  3. use App\Module\Point\Dto\PointLogDto;
  4. use App\Module\Point\Models\PointLogModel;
  5. use App\Module\Point\Enums\LOG_TYPE;
  6. /**
  7. * 积分日志服务类
  8. *
  9. * 提供积分日志相关的业务功能
  10. */
  11. class LogService
  12. {
  13. /**
  14. * 获取用户积分日志
  15. *
  16. * @param int $userId 用户ID
  17. * @param int|null $pointId 积分类型ID(可选)
  18. * @param int $limit 限制数量
  19. * @return array 日志DTO数组
  20. */
  21. public static function getUserLogs(int $userId, ?int $pointId = null, int $limit = 50): array
  22. {
  23. $logs = PointLogModel::getUserLogs($userId, $pointId, $limit);
  24. $result = [];
  25. foreach ($logs as $log) {
  26. $result[] = new PointLogDto([
  27. 'id' => $log->id,
  28. 'user_id' => $log->user_id,
  29. 'point_id' => $log->point_id,
  30. 'amount' => $log->amount,
  31. 'operate_id' => $log->operate_id,
  32. 'operate_type' => $log->operate_type->value,
  33. 'operate_type_name' => $log->getOperateTypeName(),
  34. 'remark' => $log->remark,
  35. 'create_time' => $log->create_time,
  36. 'create_ip' => $log->create_ip,
  37. 'later_balance' => $log->later_balance,
  38. 'before_balance' => $log->before_balance,
  39. 'is_income' => $log->isIncome(),
  40. 'is_expense' => $log->isExpense(),
  41. ]);
  42. }
  43. return $result;
  44. }
  45. /**
  46. * 获取用户指定时间范围的积分日志
  47. *
  48. * @param int $userId 用户ID
  49. * @param int $startTime 开始时间
  50. * @param int $endTime 结束时间
  51. * @param int|null $pointId 积分类型ID(可选)
  52. * @return array 日志DTO数组
  53. */
  54. public static function getUserLogsByTimeRange(
  55. int $userId,
  56. int $startTime,
  57. int $endTime,
  58. ?int $pointId = null
  59. ): array {
  60. $logs = PointLogModel::getUserLogsByTimeRange($userId, $startTime, $endTime, $pointId);
  61. $result = [];
  62. foreach ($logs as $log) {
  63. $result[] = new PointLogDto([
  64. 'id' => $log->id,
  65. 'user_id' => $log->user_id,
  66. 'point_id' => $log->point_id,
  67. 'amount' => $log->amount,
  68. 'operate_id' => $log->operate_id,
  69. 'operate_type' => $log->operate_type->value,
  70. 'operate_type_name' => $log->getOperateTypeName(),
  71. 'remark' => $log->remark,
  72. 'create_time' => $log->create_time,
  73. 'create_ip' => $log->create_ip,
  74. 'later_balance' => $log->later_balance,
  75. 'before_balance' => $log->before_balance,
  76. 'is_income' => $log->isIncome(),
  77. 'is_expense' => $log->isExpense(),
  78. ]);
  79. }
  80. return $result;
  81. }
  82. /**
  83. * 获取用户积分收支统计
  84. *
  85. * @param int $userId 用户ID
  86. * @param int|null $pointId 积分类型ID(可选)
  87. * @param int|null $startTime 开始时间(可选)
  88. * @param int|null $endTime 结束时间(可选)
  89. * @return array 统计信息
  90. */
  91. public static function getUserPointStats(
  92. int $userId,
  93. ?int $pointId = null,
  94. ?int $startTime = null,
  95. ?int $endTime = null
  96. ): array {
  97. $query = PointLogModel::where('user_id', $userId);
  98. if ($pointId !== null) {
  99. $query->where('point_id', $pointId);
  100. }
  101. if ($startTime !== null && $endTime !== null) {
  102. $query->whereBetween('create_time', [$startTime, $endTime]);
  103. }
  104. $logs = $query->get();
  105. $stats = [
  106. 'total_logs' => $logs->count(),
  107. 'total_income' => 0,
  108. 'total_expense' => 0,
  109. 'income_count' => 0,
  110. 'expense_count' => 0,
  111. 'by_operate_type' => [],
  112. ];
  113. foreach ($logs as $log) {
  114. if ($log->isIncome()) {
  115. $stats['total_income'] += $log->amount;
  116. $stats['income_count']++;
  117. } else {
  118. $stats['total_expense'] += abs($log->amount);
  119. $stats['expense_count']++;
  120. }
  121. $operateTypeName = $log->getOperateTypeName();
  122. if (!isset($stats['by_operate_type'][$operateTypeName])) {
  123. $stats['by_operate_type'][$operateTypeName] = [
  124. 'count' => 0,
  125. 'total_amount' => 0,
  126. ];
  127. }
  128. $stats['by_operate_type'][$operateTypeName]['count']++;
  129. $stats['by_operate_type'][$operateTypeName]['total_amount'] += $log->amount;
  130. }
  131. return $stats;
  132. }
  133. /**
  134. * 验证日志记录的完整性
  135. *
  136. * @param int $logId 日志ID
  137. * @return bool 是否完整
  138. */
  139. public static function verifyLogIntegrity(int $logId): bool
  140. {
  141. $log = PointLogModel::find($logId);
  142. return $log ? $log->verifyIntegrity() : false;
  143. }
  144. /**
  145. * 批量验证日志记录的完整性
  146. *
  147. * @param array $logIds 日志ID数组
  148. * @return array 验证结果数组
  149. */
  150. public static function batchVerifyLogIntegrity(array $logIds): array
  151. {
  152. $results = [];
  153. foreach ($logIds as $logId) {
  154. $results[$logId] = self::verifyLogIntegrity($logId);
  155. }
  156. return $results;
  157. }
  158. /**
  159. * 获取积分日志统计信息(按操作类型)
  160. *
  161. * @param int|null $pointId 积分类型ID(可选)
  162. * @param int|null $startTime 开始时间(可选)
  163. * @param int|null $endTime 结束时间(可选)
  164. * @return array 统计信息
  165. */
  166. public static function getLogStatsByOperateType(
  167. ?int $pointId = null,
  168. ?int $startTime = null,
  169. ?int $endTime = null
  170. ): array {
  171. $query = PointLogModel::query();
  172. if ($pointId !== null) {
  173. $query->where('point_id', $pointId);
  174. }
  175. if ($startTime !== null && $endTime !== null) {
  176. $query->whereBetween('create_time', [$startTime, $endTime]);
  177. }
  178. $logs = $query->get();
  179. $stats = [];
  180. foreach ($logs as $log) {
  181. $operateType = $log->operate_type->value;
  182. $operateTypeName = $log->getOperateTypeName();
  183. if (!isset($stats[$operateType])) {
  184. $stats[$operateType] = [
  185. 'operate_type' => $operateType,
  186. 'operate_type_name' => $operateTypeName,
  187. 'count' => 0,
  188. 'total_amount' => 0,
  189. 'income_count' => 0,
  190. 'expense_count' => 0,
  191. 'income_amount' => 0,
  192. 'expense_amount' => 0,
  193. ];
  194. }
  195. $stats[$operateType]['count']++;
  196. $stats[$operateType]['total_amount'] += $log->amount;
  197. if ($log->isIncome()) {
  198. $stats[$operateType]['income_count']++;
  199. $stats[$operateType]['income_amount'] += $log->amount;
  200. } else {
  201. $stats[$operateType]['expense_count']++;
  202. $stats[$operateType]['expense_amount'] += abs($log->amount);
  203. }
  204. }
  205. return array_values($stats);
  206. }
  207. /**
  208. * 获取每日积分变化统计
  209. *
  210. * @param int $userId 用户ID
  211. * @param int $pointId 积分类型ID
  212. * @param int $days 统计天数
  213. * @return array 每日统计数组
  214. */
  215. public static function getDailyPointStats(int $userId, int $pointId, int $days = 30): array
  216. {
  217. $endTime = time();
  218. $startTime = $endTime - ($days * 24 * 60 * 60);
  219. $logs = PointLogModel::where('user_id', $userId)
  220. ->where('point_id', $pointId)
  221. ->whereBetween('create_time', [$startTime, $endTime])
  222. ->orderBy('create_time')
  223. ->get();
  224. $dailyStats = [];
  225. // 初始化每日统计
  226. for ($i = 0; $i < $days; $i++) {
  227. $date = date('Y-m-d', $endTime - ($i * 24 * 60 * 60));
  228. $dailyStats[$date] = [
  229. 'date' => $date,
  230. 'income' => 0,
  231. 'expense' => 0,
  232. 'net_change' => 0,
  233. 'log_count' => 0,
  234. ];
  235. }
  236. // 统计每日数据
  237. foreach ($logs as $log) {
  238. $date = date('Y-m-d', $log->create_time);
  239. if (isset($dailyStats[$date])) {
  240. $dailyStats[$date]['log_count']++;
  241. if ($log->isIncome()) {
  242. $dailyStats[$date]['income'] += $log->amount;
  243. } else {
  244. $dailyStats[$date]['expense'] += abs($log->amount);
  245. }
  246. $dailyStats[$date]['net_change'] = $dailyStats[$date]['income'] - $dailyStats[$date]['expense'];
  247. }
  248. }
  249. return array_values($dailyStats);
  250. }
  251. /**
  252. * 搜索积分日志
  253. *
  254. * @param array $filters 搜索条件
  255. * @param int $limit 限制数量
  256. * @return array 日志数组
  257. */
  258. public static function searchLogs(array $filters, int $limit = 50): array
  259. {
  260. $query = PointLogModel::query();
  261. if (isset($filters['user_id'])) {
  262. $query->where('user_id', $filters['user_id']);
  263. }
  264. if (isset($filters['point_id'])) {
  265. $query->where('point_id', $filters['point_id']);
  266. }
  267. if (isset($filters['operate_type'])) {
  268. $query->where('operate_type', $filters['operate_type']);
  269. }
  270. if (isset($filters['operate_id'])) {
  271. $query->where('operate_id', 'like', '%' . $filters['operate_id'] . '%');
  272. }
  273. if (isset($filters['start_time']) && isset($filters['end_time'])) {
  274. $query->whereBetween('create_time', [$filters['start_time'], $filters['end_time']]);
  275. }
  276. if (isset($filters['min_amount'])) {
  277. $query->where('amount', '>=', $filters['min_amount']);
  278. }
  279. if (isset($filters['max_amount'])) {
  280. $query->where('amount', '<=', $filters['max_amount']);
  281. }
  282. $logs = $query->orderBy('create_time', 'desc')->limit($limit)->get();
  283. $result = [];
  284. foreach ($logs as $log) {
  285. $result[] = [
  286. 'id' => $log->id,
  287. 'user_id' => $log->user_id,
  288. 'point_id' => $log->point_id,
  289. 'amount' => $log->amount,
  290. 'operate_id' => $log->operate_id,
  291. 'operate_type' => $log->operate_type->value,
  292. 'operate_type_name' => $log->getOperateTypeName(),
  293. 'remark' => $log->remark,
  294. 'create_time' => $log->create_time,
  295. 'create_ip' => $log->create_ip,
  296. 'later_balance' => $log->later_balance,
  297. 'before_balance' => $log->before_balance,
  298. 'is_income' => $log->isIncome(),
  299. 'is_expense' => $log->isExpense(),
  300. ];
  301. }
  302. return $result;
  303. }
  304. }