PointLogModel.php 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  1. <?php
  2. namespace App\Module\Point\Models;
  3. use App\Module\Point\Enums\POINT_TYPE;
  4. use App\Module\Point\Enums\LOG_TYPE;
  5. use UCore\ModelCore;
  6. /**
  7. * 积分日志表
  8. *
  9. * 记录用户积分的所有变更操作,包括增加、减少、转账等
  10. * 专注于整数积分处理,不涉及小数运算
  11. *
  12. * field start
  13. * @property int $id 自增
  14. * @property int $user_id 用户ID
  15. * @property \App\Module\Point\Enums\POINT_TYPE $point_id 积分类型ID
  16. * @property int $amount 操作积分数量,正值为收入,负值为支出
  17. * @property string $operate_id 上游操作ID
  18. * @property \App\Module\Point\Enums\LOG_TYPE $operate_type 上游操作类型
  19. * @property string $remark 备注
  20. * @property int $create_time 创建时间
  21. * @property string $create_ip 创建IP
  22. * @property int $later_balance 操作后余额
  23. * @property int $before_balance 操作前余额
  24. * @property int $date_key 日期key(用于分表)
  25. * @property string $hash 防篡改哈希值
  26. * @property string $prev_hash 上一条记录的哈希值
  27. * field end
  28. */
  29. class PointLogModel extends ModelCore
  30. {
  31. protected $table = 'point_logs';
  32. // attrlist start
  33. protected $fillable = [
  34. 'id',
  35. 'user_id',
  36. 'point_id',
  37. 'amount',
  38. 'operate_id',
  39. 'operate_type',
  40. 'remark',
  41. 'create_time',
  42. 'create_ip',
  43. 'later_balance',
  44. 'before_balance',
  45. 'date_key',
  46. 'hash',
  47. 'prev_hash',
  48. ];
  49. // attrlist end
  50. public $timestamps = false;
  51. protected $casts = [
  52. 'point_id' => POINT_TYPE::class,
  53. 'operate_type' => LOG_TYPE::class,
  54. ];
  55. /**
  56. * 创建积分日志
  57. *
  58. * @param int $userId 用户ID
  59. * @param int $pointId 积分类型ID
  60. * @param int $amount 积分数量(正数为收入,负数为支出)
  61. * @param LOG_TYPE $operateType 操作类型
  62. * @param string $operateId 操作ID
  63. * @param string $remark 备注
  64. * @param int $beforeBalance 操作前余额
  65. * @param int $laterBalance 操作后余额
  66. * @return PointLogModel|false 成功返回日志模型,失败返回false
  67. */
  68. public static function createLog(
  69. int $userId,
  70. int $pointId,
  71. int $amount,
  72. LOG_TYPE $operateType,
  73. string $operateId,
  74. string $remark,
  75. int $beforeBalance,
  76. int $laterBalance
  77. ) {
  78. $log = new self();
  79. $log->user_id = $userId;
  80. $log->point_id = $pointId;
  81. $log->amount = $amount;
  82. $log->operate_type = $operateType;
  83. $log->operate_id = $operateId;
  84. $log->remark = $remark;
  85. $log->before_balance = $beforeBalance;
  86. $log->later_balance = $laterBalance;
  87. $log->create_time = time();
  88. $log->create_ip = request()->ip() ?? '';
  89. $log->date_key = (int)date('Ym');
  90. // 获取上一条记录的哈希值
  91. $prevLog = self::where('user_id', $userId)
  92. ->where('point_id', $pointId)
  93. ->orderBy('id', 'desc')
  94. ->first();
  95. $log->prev_hash = $prevLog ? $prevLog->hash : '';
  96. // 生成当前记录的哈希值
  97. $log->hash = self::generateHash($log);
  98. if ($log->save()) {
  99. return $log;
  100. }
  101. return false;
  102. }
  103. /**
  104. * 生成防篡改哈希值
  105. *
  106. * @param PointLogModel $log 日志模型
  107. * @return string 哈希值
  108. */
  109. private static function generateHash(PointLogModel $log): string
  110. {
  111. $data = [
  112. $log->user_id,
  113. $log->point_id->value,
  114. $log->amount,
  115. $log->operate_type->value,
  116. $log->operate_id,
  117. $log->before_balance,
  118. $log->later_balance,
  119. $log->create_time,
  120. $log->prev_hash
  121. ];
  122. return hash('sha256', implode('|', $data));
  123. }
  124. /**
  125. * 验证日志记录的完整性
  126. *
  127. * @return bool 是否完整
  128. */
  129. public function verifyIntegrity(): bool
  130. {
  131. $expectedHash = self::generateHash($this);
  132. return $this->hash === $expectedHash;
  133. }
  134. /**
  135. * 获取用户积分日志
  136. *
  137. * @param int $userId 用户ID
  138. * @param int|null $pointId 积分类型ID(可选)
  139. * @param int $limit 限制数量
  140. * @return \Illuminate\Database\Eloquent\Collection 日志集合
  141. */
  142. public static function getUserLogs(int $userId, ?int $pointId = null, int $limit = 50)
  143. {
  144. $query = self::where('user_id', $userId);
  145. if ($pointId !== null) {
  146. $query->where('point_id', $pointId);
  147. }
  148. return $query->orderBy('create_time', 'desc')
  149. ->limit($limit)
  150. ->get();
  151. }
  152. /**
  153. * 获取用户指定时间范围的积分日志
  154. *
  155. * @param int $userId 用户ID
  156. * @param int $startTime 开始时间
  157. * @param int $endTime 结束时间
  158. * @param int|null $pointId 积分类型ID(可选)
  159. * @return \Illuminate\Database\Eloquent\Collection 日志集合
  160. */
  161. public static function getUserLogsByTimeRange(
  162. int $userId,
  163. int $startTime,
  164. int $endTime,
  165. ?int $pointId = null
  166. ) {
  167. $query = self::where('user_id', $userId)
  168. ->whereBetween('create_time', [$startTime, $endTime]);
  169. if ($pointId !== null) {
  170. $query->where('point_id', $pointId);
  171. }
  172. return $query->orderBy('create_time', 'desc')->get();
  173. }
  174. /**
  175. * 获取操作类型名称
  176. *
  177. * @return string 操作类型名称
  178. */
  179. public function getOperateTypeName(): string
  180. {
  181. return $this->operate_type->getTypeName();
  182. }
  183. /**
  184. * 判断是否为收入记录
  185. *
  186. * @return bool 是否为收入
  187. */
  188. public function isIncome(): bool
  189. {
  190. return $this->amount > 0;
  191. }
  192. /**
  193. * 判断是否为支出记录
  194. *
  195. * @return bool 是否为支出
  196. */
  197. public function isExpense(): bool
  198. {
  199. return $this->amount < 0;
  200. }
  201. /**
  202. * 获取积分类型名称
  203. *
  204. * @return string 积分类型名称
  205. */
  206. public function getPointTypeName(): string
  207. {
  208. return $this->point_id->getTypeName();
  209. }
  210. }