OpenApiStats.php 9.6 KB

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