AutoSubmeterBack.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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. if($tableName == 'box_handle'){
  62. $index = 'all';
  63. $newTableName = $tableName . '_' . $index;
  64. $date = date('Y-m-d H:i:s', $dateTime);
  65. if ($this->checkCatchByTime($tableName, $index, $time)) {
  66. $hasCount++;
  67. RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item,'name'=>$newTableName, 'error' => '未到备份时间间隔'], $this->cacheTime);
  68. continue;
  69. }
  70. // 复制数据
  71. $prefix = env('database.prefix','db_');
  72. $count1 = Db::name($tableName)->where('is_delete', 2)->whereNotIn('id',Db::name($newTableName)->column('id'))->count();
  73. if(!$count1){
  74. $hasCount++;
  75. RedisCache::set($cacheKey . "{$newTableName}_error", ['count1'=>$count1,'sql'=>Db::name($tableName)->getLastSql(),'count'=>$count1,'data' => $item,'name'=>$newTableName, 'error' => '没有数据处理'], $this->cacheTime);
  76. continue;
  77. }
  78. $count2 = Db::name($newTableName)->count();
  79. $sql = "insert into `{$prefix}{$newTableName}` (select * from `{$prefix}{$tableName}` where `id` not in (select `id` from `{$prefix}{$newTableName}`) and `is_delete` = 2 and `create_time` < '".date('Y-m-d')."')";
  80. $res = Db::query($sql);
  81. $count3 = Db::name($newTableName)->count();
  82. if (($count3 - $count2) < $count1) {
  83. RedisCache::set($cacheKey."{$newTableName}_error", ['count2'=>$count2,'count3'=>$count3,'sql1'=>$sql,'msg'=> '复制分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  84. sr_throw("复制[{$newTableName}]分表数据错误");
  85. }
  86. // 清理原表多余数据
  87. Db::name($tableName)->where(['is_delete'=>1])->where('update_time','<',$date)->delete();
  88. $res = Db::name($tableName)->whereIn('id',Db::name($newTableName)->column('id'))->update(['is_delete'=>1,'update_time'=>date('Y-m-d H:i:s')]);
  89. if(!$res){
  90. RedisCache::set($cacheKey."{$newTableName}_error", ['count'=>$count,'sql'=>$sql,'sql1'=>Db::name($tableName)->getLastSql(),'msg'=> '清理备份分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  91. sr_throw("清理[{$tableName}]表旧数据错误");
  92. }
  93. }else{
  94. $index = date('Ymd', $dateTime);
  95. $newTableName = $tableName . '_' . $index;
  96. if ($this->checkCatchByTime($tableName, $index, $time)) {
  97. $hasCount++;
  98. RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item,'name'=>$newTableName, 'error' => '未到备份时间间隔'], $this->cacheTime);
  99. continue;
  100. }
  101. if ($this->checkCatch($tableName, $index)) {
  102. $hasCount++;
  103. RedisCache::set($cacheKey . "{$newTableName}_error", ['data' => $item,'name'=>$newTableName, 'error' => '已经处理过'], $this->cacheTime);
  104. continue;
  105. }
  106. // 备份处理(1:复制表,2-清除日期间隔数据)
  107. $prefix = env('database.prefix','db_');
  108. $date = date('Y-m-d H:i:s', $dateTime);
  109. // 复制表结构
  110. $sql = "create table IF not exists `{$prefix}{$newTableName}` like `{$prefix}{$tableName}`";
  111. Db::query($sql);
  112. // 复制数据
  113. $count1 = Db::name($tableName)->count();
  114. $sql1 = "insert ignore `{$prefix}{$newTableName}` select * from `{$prefix}{$tableName}`";
  115. Db::query($sql1);
  116. if ($count1 != Db::name($newTableName)->count()) {
  117. RedisCache::set($cacheKey."{$newTableName}_error", ['sql'=>$sql,'sql1'=>$sql1,'count1'=>$count1,'msg'=> '复制分表数据错误','data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  118. sr_throw("复制备份[{$newTableName}]分表数据错误");
  119. }
  120. // 清理原表多余数据
  121. $expiredRow = Db::name($tableName)
  122. ->where($timeField,'<', $date)
  123. ->where(function ($query) use($tableName){
  124. if($tableName == 'money_log'){
  125. $query->whereNotIn('type', [8]);
  126. }
  127. })
  128. ->count();
  129. $isDelete = Db::name($tableName)
  130. ->where($timeField,'<', $date)
  131. ->where(function ($query) use($tableName){
  132. if($tableName == 'money_log'){
  133. $query->whereNotIn('type', [8]);
  134. }
  135. })
  136. ->delete();
  137. if($expiredRow && !$isDelete){
  138. RedisCache::set($cacheKey."{$newTableName}_error", ['sql'=>$sql,'msg'=> '清理备份分表数据错误','count'=>$expiredRow,'data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  139. sr_throw("清理[{$tableName}]表旧数据错误");
  140. }
  141. }
  142. $data = [
  143. 'table_name'=> $tableName,
  144. 'table_index'=> $index,
  145. 'update_time'=> date('Y-m-d H:i:s'),
  146. 'expired_at'=> $saveTime?date('Y-m-d H:i:s', time() + $saveTime * 24 * 3600):'',
  147. 'status'=>1
  148. ];
  149. if(!SubmeterModel::insertGetId($data)){
  150. RedisCache::set($cacheKey."{$newTableName}_error", ['sql'=>$sql,'msg'=> '处理分表记录错误','log'=>$data,'data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  151. sr_throw("处理[{$newTableName}]分表记录错误");
  152. }
  153. RedisCache::set($cacheKey."{$newTableName}_success", ['sql'=>$sql,'msg'=> '处理分表备份成功','log'=>$data,'data'=> $item,'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  154. $count++;
  155. }
  156. Db::commit();
  157. echo json_encode(['code'=>200,'msg'=>"运行成功:共处理{$count}个表备份,{$hasCount}个已备份过",'date'=>date('Y-m-d H:i:s')],256)."\n";
  158. }catch (\Exception $exception){
  159. Db::rollback();
  160. RedisCache::clear("caches:submeter:lock");
  161. RedisCache::set("caches:submeter:error", ['msg'=> '运行错误:'.$exception->getMessage(),'trace'=>$exception->getTrace(),'date'=>date('Y-m-d H:i:s')], $this->cacheTime);
  162. echo json_encode(['code'=>500,'msg'=>'运行错误:'.$exception->getMessage(),'date'=>date('Y-m-d H:i:s')], 256)."\n";
  163. }
  164. return true;
  165. }
  166. /**
  167. * 验证是否备份分表过
  168. * @param $tableName 表名
  169. * @return bool|mixed
  170. */
  171. protected function checkCatch($tableName, $index)
  172. {
  173. $cacheKey = "caches:submeter:check:{$tableName}_{$index}";
  174. if(RedisCache::get($cacheKey)){
  175. return true;
  176. }
  177. $data = SubmeterModel::where(['table_name'=> $tableName,'table_index'=>$index,'status'=>1])->value('id');
  178. if($data){
  179. RedisCache::set($cacheKey, $data, rand(10, 20));
  180. }
  181. return $data;
  182. }
  183. /**
  184. * 验证是否备份分表过
  185. * @param $tableName 表名
  186. * @return bool|mixed
  187. */
  188. protected function checkCatchByTime($tableName, $index, $day)
  189. {
  190. $cacheKey = "caches:submeter:check:time_{$tableName}_{$index}_{$day}";
  191. if(RedisCache::get($cacheKey)){
  192. return true;
  193. }
  194. $date = $day == 1? date('Y-m-d') : date('Y-m-d H:i:s', time() - $day * 24 * 3600);
  195. $data = SubmeterModel::where(['table_name'=> $tableName,'status'=>1])
  196. ->where('update_time','>=', $date)
  197. ->value('id');
  198. //var_dump(SubmeterModel::getLastSql());
  199. if($data){
  200. RedisCache::set($cacheKey, $data, rand(10, 20));
  201. }
  202. return $data;
  203. }
  204. }