OpenApiStats.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. <?php
  2. namespace App\Module\OpenAPI\Models;
  3. use UCore\ModelCore;
  4. /**
  5. * OpenAPI统计数据模型
  6. *
  7. * @property int $id
  8. * @property string $app_id 应用ID
  9. * @property string $date 统计日期
  10. * @property int $hour 统计小时(0-23)
  11. * @property string $endpoint 接口端点
  12. * @property int $request_count 请求次数
  13. * @property int $success_count 成功次数
  14. * @property int $error_count 错误次数
  15. * @property float $avg_response_time 平均响应时间(毫秒)
  16. * @property float $max_response_time 最大响应时间(毫秒)
  17. * @property float $min_response_time 最小响应时间(毫秒)
  18. * @property int $rate_limit_hits 限流命中次数
  19. * @property int $unique_ips 唯一IP数量
  20. * @property array|null $error_details 错误详情
  21. * @property \Carbon\Carbon $created_at
  22. * @property \Carbon\Carbon $updated_at
  23. */
  24. class OpenApiStats extends ModelCore
  25. {
  26. // field start
  27. protected $fillable = [
  28. 'app_id',
  29. 'date',
  30. 'hour',
  31. 'endpoint',
  32. 'request_count',
  33. 'success_count',
  34. 'error_count',
  35. 'avg_response_time',
  36. 'max_response_time',
  37. 'min_response_time',
  38. 'rate_limit_hits',
  39. 'unique_ips',
  40. 'error_details',
  41. ];
  42. // field end
  43. /**
  44. * 数据类型转换
  45. *
  46. * @var array
  47. */
  48. protected $casts = [
  49. 'date' => 'date',
  50. 'hour' => 'integer',
  51. 'request_count' => 'integer',
  52. 'success_count' => 'integer',
  53. 'error_count' => 'integer',
  54. 'avg_response_time' => 'float',
  55. 'max_response_time' => 'float',
  56. 'min_response_time' => 'float',
  57. 'rate_limit_hits' => 'integer',
  58. 'unique_ips' => 'integer',
  59. 'error_details' => 'array',
  60. ];
  61. /**
  62. * 默认值
  63. *
  64. * @var array
  65. */
  66. protected $attributes = [
  67. 'request_count' => 0,
  68. 'success_count' => 0,
  69. 'error_count' => 0,
  70. 'avg_response_time' => 0,
  71. 'max_response_time' => 0,
  72. 'min_response_time' => 0,
  73. 'rate_limit_hits' => 0,
  74. 'unique_ips' => 0,
  75. ];
  76. /**
  77. * 关联应用模型
  78. *
  79. * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
  80. */
  81. public function app()
  82. {
  83. return $this->belongsTo(OpenApiApp::class, 'app_id', 'app_id');
  84. }
  85. /**
  86. * 获取成功率
  87. *
  88. * @return float
  89. */
  90. public function getSuccessRateAttribute(): float
  91. {
  92. if ($this->request_count === 0) {
  93. return 0;
  94. }
  95. return round(($this->success_count / $this->request_count) * 100, 2);
  96. }
  97. /**
  98. * 获取错误率
  99. *
  100. * @return float
  101. */
  102. public function getErrorRateAttribute(): float
  103. {
  104. if ($this->request_count === 0) {
  105. return 0;
  106. }
  107. return round(($this->error_count / $this->request_count) * 100, 2);
  108. }
  109. /**
  110. * 获取限流命中率
  111. *
  112. * @return float
  113. */
  114. public function getRateLimitHitRateAttribute(): float
  115. {
  116. if ($this->request_count === 0) {
  117. return 0;
  118. }
  119. return round(($this->rate_limit_hits / $this->request_count) * 100, 2);
  120. }
  121. /**
  122. * 获取时间段标识
  123. *
  124. * @return string
  125. */
  126. public function getTimePeriodAttribute(): string
  127. {
  128. if ($this->hour !== null) {
  129. return $this->date->format('Y-m-d') . ' ' . sprintf('%02d:00', $this->hour);
  130. }
  131. return $this->date->format('Y-m-d');
  132. }
  133. /**
  134. * 获取格式化的平均响应时间
  135. *
  136. * @return string
  137. */
  138. public function getFormattedAvgResponseTimeAttribute(): string
  139. {
  140. if ($this->avg_response_time < 1000) {
  141. return round($this->avg_response_time, 2) . 'ms';
  142. }
  143. return round($this->avg_response_time / 1000, 2) . 's';
  144. }
  145. /**
  146. * 更新统计数据
  147. *
  148. * @param array $data
  149. * @return void
  150. */
  151. public function updateStats(array $data): void
  152. {
  153. $this->increment('request_count', $data['request_count'] ?? 1);
  154. if (isset($data['success']) && $data['success']) {
  155. $this->increment('success_count');
  156. } else {
  157. $this->increment('error_count');
  158. }
  159. if (isset($data['response_time'])) {
  160. $this->updateResponseTime($data['response_time']);
  161. }
  162. if (isset($data['rate_limit_hit']) && $data['rate_limit_hit']) {
  163. $this->increment('rate_limit_hits');
  164. }
  165. if (isset($data['unique_ip'])) {
  166. $this->updateUniqueIps($data['unique_ip']);
  167. }
  168. if (isset($data['error_details'])) {
  169. $this->updateErrorDetails($data['error_details']);
  170. }
  171. }
  172. /**
  173. * 更新响应时间统计
  174. *
  175. * @param float $responseTime
  176. * @return void
  177. */
  178. protected function updateResponseTime(float $responseTime): void
  179. {
  180. // 更新平均响应时间
  181. $totalTime = $this->avg_response_time * ($this->request_count - 1) + $responseTime;
  182. $this->avg_response_time = $totalTime / $this->request_count;
  183. // 更新最大响应时间
  184. if ($responseTime > $this->max_response_time) {
  185. $this->max_response_time = $responseTime;
  186. }
  187. // 更新最小响应时间
  188. if ($this->min_response_time === 0 || $responseTime < $this->min_response_time) {
  189. $this->min_response_time = $responseTime;
  190. }
  191. $this->save();
  192. }
  193. /**
  194. * 更新唯一IP统计
  195. *
  196. * @param string $ip
  197. * @return void
  198. */
  199. protected function updateUniqueIps(string $ip): void
  200. {
  201. // 这里可以使用Redis Set来精确统计唯一IP
  202. // 为了简化,这里只是增加计数
  203. $this->increment('unique_ips');
  204. }
  205. /**
  206. * 更新错误详情
  207. *
  208. * @param array $errorDetails
  209. * @return void
  210. */
  211. protected function updateErrorDetails(array $errorDetails): void
  212. {
  213. $currentDetails = $this->error_details ?? [];
  214. $errorCode = $errorDetails['code'] ?? 'unknown';
  215. if (!isset($currentDetails[$errorCode])) {
  216. $currentDetails[$errorCode] = [
  217. 'count' => 0,
  218. 'message' => $errorDetails['message'] ?? '',
  219. 'first_seen' => now()->toISOString(),
  220. ];
  221. }
  222. $currentDetails[$errorCode]['count']++;
  223. $currentDetails[$errorCode]['last_seen'] = now()->toISOString();
  224. $this->update(['error_details' => $currentDetails]);
  225. }
  226. /**
  227. * 按应用ID查询
  228. *
  229. * @param \Illuminate\Database\Eloquent\Builder $query
  230. * @param string $appId
  231. * @return \Illuminate\Database\Eloquent\Builder
  232. */
  233. public function scopeByApp($query, string $appId)
  234. {
  235. return $query->where('app_id', $appId);
  236. }
  237. /**
  238. * 按日期查询
  239. *
  240. * @param \Illuminate\Database\Eloquent\Builder $query
  241. * @param string $date
  242. * @return \Illuminate\Database\Eloquent\Builder
  243. */
  244. public function scopeByDate($query, string $date)
  245. {
  246. return $query->where('date', $date);
  247. }
  248. /**
  249. * 按日期范围查询
  250. *
  251. * @param \Illuminate\Database\Eloquent\Builder $query
  252. * @param string $startDate
  253. * @param string $endDate
  254. * @return \Illuminate\Database\Eloquent\Builder
  255. */
  256. public function scopeDateRange($query, string $startDate, string $endDate)
  257. {
  258. return $query->whereBetween('date', [$startDate, $endDate]);
  259. }
  260. /**
  261. * 按小时查询
  262. *
  263. * @param \Illuminate\Database\Eloquent\Builder $query
  264. * @param int $hour
  265. * @return \Illuminate\Database\Eloquent\Builder
  266. */
  267. public function scopeByHour($query, int $hour)
  268. {
  269. return $query->where('hour', $hour);
  270. }
  271. /**
  272. * 按接口端点查询
  273. *
  274. * @param \Illuminate\Database\Eloquent\Builder $query
  275. * @param string $endpoint
  276. * @return \Illuminate\Database\Eloquent\Builder
  277. */
  278. public function scopeByEndpoint($query, string $endpoint)
  279. {
  280. return $query->where('endpoint', $endpoint);
  281. }
  282. /**
  283. * 查询今日统计
  284. *
  285. * @param \Illuminate\Database\Eloquent\Builder $query
  286. * @return \Illuminate\Database\Eloquent\Builder
  287. */
  288. public function scopeToday($query)
  289. {
  290. return $query->where('date', now()->toDateString());
  291. }
  292. /**
  293. * 查询昨日统计
  294. *
  295. * @param \Illuminate\Database\Eloquent\Builder $query
  296. * @return \Illuminate\Database\Eloquent\Builder
  297. */
  298. public function scopeYesterday($query)
  299. {
  300. return $query->where('date', now()->subDay()->toDateString());
  301. }
  302. /**
  303. * 查询本周统计
  304. *
  305. * @param \Illuminate\Database\Eloquent\Builder $query
  306. * @return \Illuminate\Database\Eloquent\Builder
  307. */
  308. public function scopeThisWeek($query)
  309. {
  310. return $query->whereBetween('date', [
  311. now()->startOfWeek()->toDateString(),
  312. now()->endOfWeek()->toDateString()
  313. ]);
  314. }
  315. /**
  316. * 查询本月统计
  317. *
  318. * @param \Illuminate\Database\Eloquent\Builder $query
  319. * @return \Illuminate\Database\Eloquent\Builder
  320. */
  321. public function scopeThisMonth($query)
  322. {
  323. return $query->whereBetween('date', [
  324. now()->startOfMonth()->toDateString(),
  325. now()->endOfMonth()->toDateString()
  326. ]);
  327. }
  328. }