AutoSubmeter.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. <?php
  2. declare (strict_types=1);
  3. namespace app\api\command;
  4. use app\common\model\SubmeterModel;
  5. use think\console\Command;
  6. use think\console\Input;
  7. use think\console\Output;
  8. use think\facade\Db;
  9. use utils\RedisCache;
  10. /**
  11. * 自动分表备份 by wes 每天凌晨 0点后运行,运行两次间隔10分钟
  12. * Class AutoSubmeter
  13. * @package app\api\command
  14. */
  15. class AutoSubmeter extends Command
  16. {
  17. protected $cacheTime = 86400; // 一天
  18. protected $tables = [
  19. // time 备份分表周期,save_time 数据保存时间天,time_field 时间字段
  20. 'money_log' => ['time' => 2, 'save_time' => 14, 'time_field' => 'create_at'],
  21. 'box_record' => ['time' => 2, 'save_time' => 10, 'time_field' => 'create_time'],
  22. 'box_mid_handle' => ['time' => 2, 'save_time' => 10, 'time_field' => 'create_time'],
  23. 'score_log' => ['time' => 2, 'save_time' => 10, 'time_field' => 'create_at'],
  24. // 'box_handle'=>['time'=>1, 'save_time'=> 0,'time_field'=>'create_time'],
  25. ];
  26. protected function configure()
  27. {
  28. $this->setName('auto_backup_submeter')
  29. ->setDescription('the auto_backup_submeter command');
  30. }
  31. /**
  32. * 处理分表
  33. * @param Input $input
  34. * @param Output $output
  35. * @return int
  36. */
  37. protected function execute(Input $input, Output $output)
  38. {
  39. set_time_limit(0);
  40. if (date('H:i') > '12:00') {
  41. echo json_encode(['code' => 500, 'msg' => '不在运行时间段内,请于早上0点~8点执行', 'date' => date('Y-m-d H:i:s')], 256) . "\n";
  42. return false;
  43. }
  44. if (RedisCache::get("caches:submeter:lock")) {
  45. echo json_encode(['code' => 500, 'msg' => '请不要频繁执行,稍后再试~', 'date' => date('Y-m-d H:i:s')], 256) . "\n";
  46. return false;
  47. }
  48. RedisCache::setnx("caches:submeter:lock", date('Y-m-d H:i:s'), rand(10, 20));
  49. Db::startTrans();
  50. try {
  51. $count = 0;
  52. $hasCount = 0;
  53. $cacheKey = "caches:submeter:backup:";
  54. foreach ($this->tables as $tableName => $item) {
  55. $time = isset($item['time']) ? $item['time'] : 0; // 备份周期
  56. $saveTime = isset($item['save_time']) ? $item['save_time'] : 0; // 保存时间
  57. $timeField = isset($item['time_field']) ? $item['time_field'] : 0; // 时间字段
  58. $time = $time ? $time : 2;
  59. // 验证是否备份
  60. $dateTime = strtotime(date('Y-m-d')) - 86400;
  61. $index = date('Ymd', $dateTime);
  62. $newTableName = $tableName . '_' . $index;
  63. if ($this->checkCatchByTime($tableName, $index, $time)) {
  64. $hasCount++;
  65. RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item, 'name' => $newTableName, 'error' => '未到备份时间间隔'], $this->cacheTime);
  66. continue;
  67. }
  68. if ($this->checkCatch($tableName, $index)) {
  69. $hasCount++;
  70. RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item, 'name' => $newTableName, 'error' => '已经处理过'], $this->cacheTime);
  71. continue;
  72. }
  73. // 备份处理(1:复制表,2-清除日期间隔数据)
  74. $prefix = env('database.prefix', 'db_');
  75. $date = date('Y-m-d H:i:s', $dateTime);
  76. // 复制表结构
  77. $sql = "create table IF not exists `{$prefix}{$newTableName}` like `{$prefix}{$tableName}`";
  78. Db::query($sql);
  79. // 复制数据
  80. $count1 = Db::name($tableName)->count();
  81. $sql1 = "insert ignore `{$prefix}{$newTableName}` select * from `{$prefix}{$tableName}`";
  82. Db::query($sql1);
  83. if ($count1 != Db::name($newTableName)->count()) {
  84. RedisCache::set($cacheKey . "{$newTableName}_error", ['sql' => $sql, 'sql1' => $sql1, 'count1' => $count1, 'msg' => '复制分表数据错误', 'data' => $item, 'date' => date('Y-m-d H:i:s')], $this->cacheTime);
  85. sr_throw("复制备份[{$newTableName}]分表数据错误");
  86. }
  87. // 清理原表多余数据
  88. $expiredRow = Db::name($tableName)
  89. ->where($timeField, '<', $date)
  90. ->where(function ($query) use ($tableName) {
  91. if ($tableName == 'money_log') {
  92. $query->whereNotIn('type', [8]);
  93. }
  94. })
  95. ->count();
  96. $isDelete = Db::name($tableName)
  97. ->where($timeField, '<', $date)
  98. ->where(function ($query) use ($tableName) {
  99. if ($tableName == 'money_log') {
  100. $query->whereNotIn('type', [8]);
  101. }
  102. })
  103. ->delete();
  104. if ($expiredRow && !$isDelete) {
  105. RedisCache::set($cacheKey . "{$newTableName}_error", ['sql' => $sql, 'msg' => '清理备份分表数据错误', 'count' => $expiredRow, 'data' => $item, 'date' => date('Y-m-d H:i:s')], $this->cacheTime);
  106. sr_throw("清理[{$tableName}]表旧数据错误");
  107. }
  108. $data = [
  109. 'table_name' => $tableName,
  110. 'table_index' => $index,
  111. 'update_time' => date('Y-m-d H:i:s'),
  112. 'expired_at' => $saveTime ? date('Y-m-d H:i:s', time() + $saveTime * 24 * 3600) : '',
  113. 'status' => 1
  114. ];
  115. if (!SubmeterModel::insertGetId($data)) {
  116. RedisCache::set($cacheKey . "{$newTableName}_error", ['sql' => $sql, 'msg' => '处理分表记录错误', 'log' => $data, 'data' => $item, 'date' => date('Y-m-d H:i:s')], $this->cacheTime);
  117. sr_throw("处理[{$newTableName}]分表记录错误");
  118. }
  119. RedisCache::set($cacheKey . "{$newTableName}_success", ['sql' => $sql, 'msg' => '处理分表备份成功', 'log' => $data, 'data' => $item, 'date' => date('Y-m-d H:i:s')], $this->cacheTime);
  120. $count++;
  121. }
  122. Db::commit();
  123. echo json_encode(['code' => 200, 'msg' => "运行成功:共处理{$count}个表备份,{$hasCount}个已备份过", 'date' => date('Y-m-d H:i:s')], 256) . "\n";
  124. } catch (\Exception $exception) {
  125. Db::rollback();
  126. RedisCache::clear("caches:submeter:lock");
  127. RedisCache::set("caches:submeter:error", ['msg' => '运行错误:' . $exception->getMessage(), 'trace' => $exception->getTrace(), 'date' => date('Y-m-d H:i:s')], $this->cacheTime);
  128. echo json_encode(['code' => 500, 'msg' => '运行错误:' . $exception->getMessage(), 'date' => date('Y-m-d H:i:s')], 256) . "\n";
  129. }
  130. return true;
  131. }
  132. /**
  133. * 验证是否备份分表过
  134. * @param $tableName 表名
  135. * @return bool|mixed
  136. */
  137. protected function checkCatch($tableName, $index)
  138. {
  139. $cacheKey = "caches:submeter:check:{$tableName}_{$index}";
  140. if (RedisCache::get($cacheKey)) {
  141. return true;
  142. }
  143. $data = SubmeterModel::where(['table_name' => $tableName, 'table_index' => $index, 'status' => 1])->value('id');
  144. if ($data) {
  145. RedisCache::set($cacheKey, $data, rand(10, 20));
  146. }
  147. return $data;
  148. }
  149. /**
  150. * 验证是否备份分表过
  151. * @param $tableName 表名
  152. * @return bool|mixed
  153. */
  154. protected function checkCatchByTime($tableName, $index, $day)
  155. {
  156. $cacheKey = "caches:submeter:check:time_{$tableName}_{$index}_{$day}";
  157. if (RedisCache::get($cacheKey)) {
  158. return true;
  159. }
  160. $date = $day == 1 ? date('Y-m-d') : date('Y-m-d H:i:s', time() - $day * 24 * 3600);
  161. $data = SubmeterModel::where(['table_name' => $tableName, 'status' => 1])
  162. ->where('update_time', '>=', $date)
  163. ->value('id');
  164. //var_dump(SubmeterModel::getLastSql());
  165. if ($data) {
  166. RedisCache::set($cacheKey, $data, rand(10, 20));
  167. }
  168. return $data;
  169. }
  170. }