zoukankan      html  css  js  c++  java
  • Linux机器上实现程序自动部署以及更新

    平台化管理Linux环境的客户端程序的部署、更新、以及进程关闭

    
    

    平台是用SSH实现,前端是avalon,数据库mysql客户端程序(Monitor, CLientSetup)是spring boot实现平台操作界面如下:

    客户端程序访问路径为 http://ip:8034, 

    获取版本方式为 http://ip:8034/Deploy/Version

    心跳地址为 http://ip:8034/Deploy/ReturnOk

    1. 更新状态访问版本地址,返回该ip对应的版本,落入数据库,更新页面信息

    2. 部署以及更新应用所有服务器路径都在ftpuser用户目录下,即/STRESS

    应用源码放在服务器A上,为Monitor.tar,上传脚本Deploy.sh也在服务器A上,

    上传脚本如下:

    IP=$1
    
    echo "put tar to "${IP}
    ftp -n<<!
    open ${IP}
    user ftpuser 1qaz@WSX
    binary
    prompt
    mkdir tools
    cd tools
    put Monitor.tar
    put ClientSetup.tar
    put clientDeploy.sh
    #chmod 777 clientDeploy.sh
    close
    bye
    !
    

    平台调用上传脚本把源码从服务器A传到目标服务器B上。

    平台再调用服务器B上脚本tools/clientDeploy.sh

    cd tools
    rm -rf Monitor
    rm -rf ClientSetup
    tar -xvf Monitor.tar
    tar -xvf ClientSetup.tar
    cd ~/tools/Monitor
    sh restart.sh
    cd ~/tools/ClientSetup
    sh restart.sh
    cd ~/tools
    rm -rf Monitor.tar
    rm -rf ClientSetup.tar
    

    平台调用java代码

    
    
    
    public String DeployEnvAgency(String ip) throws IOException, InterruptedException
    	{
    		 RemoteShellTool tool = new RemoteShellTool("172.16.103.126", "ftpuser",  
    	                "******", "utf-8");  
    	  
    	        String result = tool.exec("sh Deploy.sh "+ip);  
    	        System.out.print("result:"+result);  
    	        
    	        RemoteShellTool tool2 = new RemoteShellTool(ip, "ftpuser",  
    	                "******", "utf-8");  
    	  
    	        String result2 = tool2.exec("sh tools/clientDeploy.sh");  
    	        System.out.print("result2:"+result2);  
    			
    			return "ok";
    	}

    调用RemoteShellTool类如下:

    package com.ymt.testplatform.util;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.nio.charset.Charset;
    
    import ch.ethz.ssh2.Connection;
    import ch.ethz.ssh2.Session;
    
    public class RemoteShellTool {
    	private Connection conn;  
        private String ipAddr;  
        private String charset = Charset.defaultCharset().toString();  
        private String userName;  
        private String password;  
      
        public RemoteShellTool(String ipAddr, String charset) {  
            this.ipAddr = ipAddr;  
            if (charset != null) {  
                this.charset = charset;  
            }  
        }  
        
        public RemoteShellTool(String ipAddr, String userName, String password,  
                String charset) {  
            this.ipAddr = ipAddr;  
            this.userName = userName;  
            this.password = password;  
            if (charset != null) {  
                this.charset = charset;  
            }  
        }  
      
        public boolean login() throws IOException {  
            conn = new Connection(ipAddr);  
            conn.connect(); // 连接  
            return conn.authenticateWithPassword(userName, password); // 认证  
        }  
        
        public boolean loginCentos() throws IOException
    	{
        	 conn = new Connection(ipAddr);  
             conn.connect(); // 连接  
    		
    		String [] pass = {"*****","*****","******","******","******"};
    		
    		for (String pa : pass) {
    			if(conn.authenticateWithPassword("root", pa))
    			{
    				this.userName = "root";  
    		        this.password = pa;  
    		        return true;
    			}
    		}		
    		
    		return false;
    		
    	}
      
        public boolean login(String userName,String password) throws IOException {  
            conn = new Connection(ipAddr);  
            conn.connect(); // 连接  
            this.userName = userName;  
            this.password = password;  
            return conn.authenticateWithPassword(userName, password); // 认证  
        }  
      
        public String exec(String cmds) {  
            InputStream in = null;  
            String result = "";  
            try {  
                if (this.login()) {  
                    Session session = conn.openSession(); // 打开一个会话  
                    session.execCommand(cmds);  
                      
                    in = session.getStdout();  
                    result = this.processStdout(in, this.charset);  
                    session.close();  
                    conn.close();  
                }  
            } catch (IOException e1) {  
                e1.printStackTrace();  
            }  
            return result;  
        }  
      
        public String processStdout(InputStream in, String charset) {  
          
            byte[] buf = new byte[1024];  
            StringBuffer sb = new StringBuffer();  
            try {  
                while (in.read(buf) != -1) {  
                    sb.append(new String(buf, charset));  
                }  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
            return sb.toString();  
        }  
      
        /** 
         * @param args 
         */  
        public static void main(String[] args) {  
      
            RemoteShellTool tool = new RemoteShellTool("172.16.103.126", "ftpuser",  
                    "*****", "utf-8");  
      
            String result = tool.exec("sh Deploy.sh 172.16.103.121");  
            System.out.print("result:"+result);  
            
            RemoteShellTool tool2 = new RemoteShellTool("172.16.103.121", "ftpuser",  
                    "*****", "utf-8");  
      
            String result2 = tool2.exec("sh tools/clientDeploy.sh");  
            System.out.print("result2:"+result2);  
      
        }  
      
    }
    


    3. 关闭进程
    java代码:

    	public String killClient(String ip) throws IOException, InterruptedException
    	{
    		 RemoteShellTool tool = new RemoteShellTool(ip,"utf-8");
    		 tool.loginCentos();
    		 tool.exec("kill -9 $(ps -ef|grep ClientSetup|gawk '$0 !~/grep/ {print $2}'|tr -s '
    ''')");  
    		 tool.exec("kill -9 $(ps -ef|grep SpringMVCDemo|gawk '$0 !~/grep/ {print $2}'|tr -s '
    ''')");  
    		 
    		 return "ok";
    	}



    <!DOCTYPE html>
    <html lang="zh-CN">
    
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <link href="/css/reset.css" rel="stylesheet">
        <link href="/bootstrap/css/bootstrap.min.css" rel="stylesheet">
        <link href="/css/admin/layout.css" rel="stylesheet">
        <link href="/css/admin/envinfo.css" rel="stylesheet">
        <script type="text/javascript" src="/lib/jquery.js"></script>
        <script type="text/javascript" src="/bootstrap/js/bootstrap.min.js"></script>
        <script type="text/javascript" src="/bootstrap/js/jquery.bootpag.min.js"></script>
        <script type="text/javascript" src="/lib/avalon.js"></script>
        <script type="text/javascript" src="/js/common/util.js"></script>
        <script type="text/javascript" src="/js/admin/common/common.js"></script>
        <script type="text/javascript" src="/js/admin/monitordeploy.js"></script>
    
        <title>基础信息管理</title>
    </head>
    
    <body ms-controller="vm">
        <!-- ADMIN HEAD -->
        <div ms-include-src="'/admin/header.html'"></div>
        <!-- Content -->
        <div class="container">
            <div ms-controller="monitordeploy">
                <div class="tabbable">
                    
                    <div class="tab-content">
                        <div class="tab-pane active" id="vms">
                            <div id="vmsTab-pane">
                                <br/>
                                <div class="row" id="search1Div">
                                    <div class="col-md-2">
                                        <div class="input-group">
                                            <span class="input-group-addon">环境:</span>
                                            <select class="form-control" ms-duplex="envType">
                                                <option value="STRESS" selected>STRESS</option>
                                                <option value="ALL">全部</option>
                                                <option value="SIT1">SIT1</option>
                                                <option value="SIT2">SIT2</option>
                                                <option value="UAT">UAT</option>
                                                <option value="other">其他</option>
                                            </select>
                                        </div>
                                    </div>
                                <div class="row" id="search2Div">
                                    <div class="col-md-2">
                                        <div class="input-group">
                                            <span class="input-group-addon">类型:</span>
                                            <select class="form-control" ms-duplex="conType">
                                                <option value="" selected>请选择</option>
                                                <option value=".Net Web">.Net Web</option>
                                                <option value="Windows Service">Windows Service</option>
                                                <option value="Node">Node</option>
                                                <option value="Java App">Java App</option>
                                                <option value="Java Web">Java Web</option>
                                                <option value="其他">其他</option>
                                            </select>
                                        </div>
                                    </div>
                                    <div class="col-md-1">
                                        <button type="button" id="searchBtn" class="btn btn-primary" ms-click="listVmInfosByPage('init')" style="margin: auto;">
                                           	搜 索
                                        </button>
                                    </div>
                                   
                                     <div class="col-md-1">
                                        <button type="button" id="allCheckBtn" class="btn btn-info" ms-click="checkAll()" style="margin: auto;">
                                           	全选
                                        </button>
                                    </div>
                                    
                                    <div class="col-md-1">
                                        <button type="button" id="allUncheckBtn" class="btn btn-info" ms-click="uncheckAll()" style="margin: auto;">
                                           	全不选
                                        </button>
                                    </div>
                                    
                                     <div class="col-md-1">
                                        <button type="button" id="allStatusBtn" class="btn btn-success" ms-click="viewAllStatus()" style="margin: auto;">
                                           	update all
                                        </button>
                                    </div>
                                    
                                     <div class="col-md-1">
                                        <button type="button" id="allDeployBtn" class="btn btn-warning" ms-click="deployAll()" style="margin: auto;">
                                           	deploy all
                                        </button>
                                    </div>
                                     
                                    <div class="col-md-1">
                                        <button type="button" id="allDeployBtn" class="btn btn-danger" ms-click="killAll()" style="margin: auto;">
                                           	kill all
                                        </button>
                                    </div>
                                  
                                </div>
                                
                          
                                <div>
                                    <div id="pageSizeSelect"><a><span ms-class="{{pagesize1Cls}}"
                                                  ms-click="changePageSize(pagesize1)">{{pagesize1}}</span></a> | <a><span
                        ms-class="{{pagesize2Cls}}" ms-click="changePageSize(pagesize2)">{{pagesize2}}</span></a> |
                                        <a>
                                            <spam ms-class="{{pagesize3Cls}}" ms-click="changePageSize(pagesize3)">{{pagesize3}}</spam>
                                        </a>
                                    </div>
                                </div>
                                <table class="table table-condensed table-hover">
                                    <thead>
                                        <tr>
                                       		<td class="width-50"></td>
                                            <td class="width-50">ID</td>
                                            <td>名称</td>
                                            <td class="width-125">IP</td>
                                            <td class="width-300">操作系统</td>
                                            
                                            <td class="width-100">监控版本</td>
                                            <td class="width-100">Setup版本</td>
                                            <td class="width-100">监控状态</td>
                                            <td class="width-100">Setup状态</td>
                                            <td class="width-100">更新时间</td>
                                            <td class="width-50">状态更新</td>
                                            <td class="width-50">部署</td>
                                             <td class="width-50">kill</td>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        <tr ms-repeat="vmsList">
                                       		<td><label><input type="checkbox" ms-class="check_{{el.name}}" ></label></td>
                                            <td>{{$index+jpageSize*(jpageIndex-1)+1}}</td>
                                            <td>{{el.name}}</td>
                                            <td><a ms-href="'/admin/vmdetails.html?vmid='+el.vm.id" target="_blank">{{el.ip}}</a></td>
                                            <td>{{el.os}}</td>                                     
                                            <td>{{el.version}}</td>
                                            <td>{{el.setupVersion}}</td>
                                            <td>{{el.clientOn}}</td>
                                            <td>{{el.setupOn}}</td>
                                            <td>{{el.time}}</td>
                                            <td>
                                             <div ms-class="buttonDiv_view_{{el.name}}">
                                                <i style="color:#009100;" ms-class="glyphicon glyphicon-eye-open icon-white i_view_{{el.name}}"  ms-click="postViewStatus(el.name,el.ClientOn,el.SetupOn,el.ip)"></i>
                                             </div> 
                                             <div ms-class="loadDiv_view_{{el.name}} loadDiv"  style="display:none">	
                                             	<img src="/img/load2.jpg" style="16px;height:16px;"/>
                                             </div> 
                                            </td>
                                             <td>
                                             <div ms-class="buttonDiv_{{el.name}}">
                                                <i style="color:#000066;" ms-class="glyphicon glyphicon-play icon-white i_{{el.name}}" ms-click="postDeploy(el.name,el.ip,el.os)"> </i>
                                             </div> 
                                             <div ms-class="loadDiv_{{el.name}} loadDiv"  style="display:none">	
                                             	<img src="/img/load2.jpg" style="16px;height:16px;"/>
                                             </div> 
                                              
                                            </td>
                                            <td>
                                             <div ms-class="buttonDiv_kill_{{el.name}}">
                                                <i style="color:#CE0000;" ms-class="glyphicon glyphicon-remove icon-white ikill_{{el.name}}" ms-click="postkill(el.name,el.ip,el.os)"> </i>
                                             </div> 
                                             <div ms-class="loadDiv_kill_{{el.name}} loadDiv"  style="display:none">	
                                             	<img src="/img/load2.jpg" style="16px;height:16px;"/>
                                             </div> 
                                              
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                                <div class="text-center">
                                    <p id="pagination"></p>
                                </div>
                            </div>
                        </div>
                       
                   
                </div>
            </div>
    
        </div>
    </div>
    <!-- /.container-->
    
    </body>
    </html>
    
    /**
     * Created by chenjiazhu on 2017/2/15.
     */
    var monitordeploy = avalon.define({
        $id: 'monitordeploy',
       
        //VM Start
        pagesize1: "20",
        pagesize1Cls: "pageSizeSelected",
        pagesize2: "50",
        pagesize2Cls: "",
        pagesize3: "100",
        pagesize3Cls: "",
        changePageSize: function(pgsize) {
            monitordeploy.jpageSize = pgsize;
            monitordeploy.listVmInfosByPage("init");
        },
        clearsearch: function() {
            monitordeploy.conType = "";
            monitordeploy.listVmInfosByPage("init");
        },
        jpageIndex: 1,
        jpageSize: 20,
        envType: "STRESS",
        conType: "",
        
        vmsList: [],
        
        listVmInfosByPage: function(tag) {
            if (tag) {
                monitordeploy.jpageIndex = 1;
            }
            $.ajax({
                type: "post",
                url: 'listDeployVmInfosByPageByEnvType.action',
                data: {
                    "pageindex": monitordeploy.jpageIndex,
                    "pagesize": monitordeploy.jpageSize,
                    "type": monitordeploy.conType,
                    "envType": monitordeploy.envType
                },
                dataType: "json",
                success: function(data) {
                    if (tag) {
                        $('#pagination').bootpag({
                            total: data.pagenum,
                            page: monitordeploy.jpageIndex
                        });
                    }
                    if (data.retCode == "1000") {
                        monitordeploy.vmsList = data.vms;
                    } else {
                        alert(data.retMSG);
                    }
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert("请求数据异常,状态码:" + XMLHttpRequest.status+",Error:"+errorThrown+",textStatus:"+textStatus);
                }
            });
        },
        
        postViewStatus: function(name, status1,status2,ip) {
           if(status1==null || status1=="")
           {
        	   status1=0;
           }
           
           if(status2==null || status2=="")
           {
        	   status2=0;
           }
           
           $(".loadDiv_view_"+name).show();
           $(".buttonDiv_view_"+name).hide();
           
           // 检查Monitor状态    	
        	$.ajax({
                type: "GET",
                dataType : 'jsonp',  
                jsonp:"jsoncallback",  
                url: "http://"+ip+":8034/Deploy/ReturnOk",
    
                success: function (data) {
                	  if(data.data=="ok")
      			    {
      			    	if(status1!="ok")
      			    	{
      			    		monitordeploy.updateMonitorStatus(ip,"ok");
      			    	}
      			    }
      			    else
      			    {
      			    	if(status1!="fail")
      			    	{
      			    		monitordeploy.updateMonitorStatus(ip,"fail");
      			    	}
      			    }
                	  
                	  $(".loadDiv_view_"+name).hide();
                      $(".buttonDiv_view_"+name).show();
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert("请求数据异常,状态码:" + XMLHttpRequest.status+",Error:"+errorThrown+",textStatus:"+textStatus);
                    $(".loadDiv_view_"+name).hide();
                    $(".buttonDiv_view_"+name).show();
                }
            });
        	
        	// 检查ClientSetup状态    	
        	$.ajax({
                type: "GET",
                dataType : 'jsonp',  
                jsonp:"jsoncallback",  
                url: "http://"+ip+":8035/Deploy/ReturnOk",
    
                success: function (data) {
                	if(data.data="ok")
      			    {
      			    	if(status1!="ok")
      			    	{
      			    		monitordeploy.updateClientSetupStatus(ip,"ok");
      			    	}
      			    }
      			    else
      			    {
      			    	if(status1!="fail")
      			    	{
      			    		monitordeploy.updateClientSetupStatus(ip,"fail");
      			    	}
      			    }
                	
                	 
                	$(".loadDiv_view_"+name).hide();
                    $(".buttonDiv_view_"+name).show();
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert("请求数据异常,状态码:" + XMLHttpRequest.status+",Error:"+errorThrown+",textStatus:"+textStatus);
                    
                    $(".loadDiv_view_"+name).hide();
                    $(".buttonDiv_view_"+name).show();
                }
            });
        },
        
        updateMonitorStatus: function(ip,status) {
            
            $.ajax({
                type: "post",
                url: 'updateMonitorStatus.action',
                data: {
                    "ip": ip,
                    "status1": status,
                },
                dataType: "json",
                success: function(data) {
                   
                    if (data.retCode != "1000") {
                    	 alert("更新"+ip+"Monitor状态失败!");
                    } 
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert("请求数据异常,状态码:" + XMLHttpRequest.status+ ",<br/>"+XMLHttpRequest.readyState+  ",<br/>"+XMLHttpRequest.responseText+",<br/>Error:"+errorThrown+",<br/>textStatus:"+textStatus);
                }
            });
        },
        
        updateClientSetupStatus: function(ip,status) {
            
            $.ajax({
                type: "post",
                url: 'updateClientSetupStatus.action',
                data: {
                    "ip": ip,
                    "status2": status,
                },
                dataType: "json",
                success: function(data) {
                   
                    if (data.retCode != "1000") {
                    	 alert("更新"+ip+"ClientSetupStatus!");
                    } 
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                    alert("请求数据异常,状态码:" + XMLHttpRequest.status+ ",<br/>"+XMLHttpRequest.readyState+  ",<br/>"+XMLHttpRequest.responseText+",<br/>Error:"+errorThrown+",<br/>textStatus:"+textStatus);
                }
            });
        },
        
        postDeploy: function(name,ip,os) {
            
            $(".loadDiv_"+name).show();
            $(".buttonDiv_"+name).hide();
            
            $.ajax({
                type: "post",
                url: 'deploy.action',
                data: {
                    "ip": ip,
                    "os": os
                },
                dataType: "json",
                success: function(data) {
                	$(".loadDiv_"+name).hide();
                    $(".buttonDiv_"+name).show();
                    
                	if(data.data=="false")
     			    {
     			    	alert("更新"+ip+"客户端失败!");
     			    } 
                	else
                		{
                		monitordeploy.listVmInfosByPage();
                		}
                	
                },
                error: function (XMLHttpRequest, textStatus, errorThrown) {
                	$(".loadDiv_"+name).hide();
    	            $(".buttonDiv_"+name).show();
                    alert("请求数据异常,状态码:" + XMLHttpRequest.status+ ",<br/>"+XMLHttpRequest.readyState+  ",<br/>"+XMLHttpRequest.responseText+",<br/>Error:"+errorThrown+",<br/>textStatus:"+textStatus);
                }
            });
         },
         
         postkill: function(name,ip,os) {
             
             $(".loadDiv_kill_"+name).show();
             $(".buttonDiv_kill_"+name).hide();
             
             $.ajax({
                 type: "post",
                 url: 'kill.action',
                 data: {
                     "ip": ip,
                     "os": os
                 },
                 dataType: "json",
                 success: function(data) {
                 	$(".loadDiv_kill_"+name).hide();
                     $(".buttonDiv_kill_"+name).show();
                     
                 	if(data.data=="false")
      			    {
      			    	alert("杀死"+ip+"监控进程失败!");
      			    } 
                 	             	
                 },
                 error: function (XMLHttpRequest, textStatus, errorThrown) {
                 	$(".loadDiv_kill_"+name).hide();
     	            $(".buttonDiv_kill_"+name).show();
                     alert("请求数据异常,状态码:" + XMLHttpRequest.status+ ",<br/>"+XMLHttpRequest.readyState+  ",<br/>"+XMLHttpRequest.responseText+",<br/>Error:"+errorThrown+",<br/>textStatus:"+textStatus);
                 }
             });
          },
         
         viewAllStatus:function() {
        	 $("input[type='checkbox']").each(
        			 function(){
        				 if($(this).get(0).checked)
        					 {
        					
        					 var name = $(this).attr("class").substr(6);
        					 //alert(name);
        					 $(".i_view_"+name).click();
        					 }
        				 
        			 }
        	 );
         },
         
         deployAll:function() {
        	 $("input[type='checkbox']").each(
        			 function(){
        				 if($(this).get(0).checked)
        					 {
        					
        					 var name = $(this).attr("class").substr(6);
        					 //alert(name);
        					 $(".i_"+name).click();
        					 }
        				 
        			 }
        	 );
         },
         
         killAll:function() {
        	 $("input[type='checkbox']").each(
        			 function(){
        				 if($(this).get(0).checked)
        					 {
        					
        					 var name = $(this).attr("class").substr(6);
        					 //alert(name);
        					 $(".ikill_"+name).click();
        					 }
        				 
        			 }
        	 );
         },
         
        checkAll:function(){
        	$("input[type='checkbox']").each(
       			 function(){
       				 $(this).attr("checked","true");  
       			 }
       	 );
        },
        
        uncheckAll:function(){
        	 $("input[type='checkbox']").each(
        			 function(){
        				 $(this).removeAttr("checked"); 
        			 }
        	 );
        },
        
        loadVmTAB: function() {
            monitordeploy.listVmInfosByPage("init");
            $('#vms').tab('show');
        },
        //VM END
        userOps: ops(4),
        bootpagFuc: function() {
        	 
            $('#pagination').bootpag({
                total: 1,
                maxVisible: 10
            }).on('page', function(event, num) {
                monitordeploy.jpageIndex = num;
                monitordeploy.listVmInfosByPage();
            });
        }
    });
    
    avalon.ready(function() {
        if (monitordeploy.userOps) {
            monitordeploy.loadVmTAB();
           
        } else {
            redirectAdminIndexPage();
        }
        monitordeploy.bootpagFuc();
    //    $(".loadDiv").hide();
    });
    
    
    monitordeploy.$watch("jpageSize", function(newValue) {
        monitordeploy.pagesize1Cls = "";
        monitordeploy.pagesize2Cls = "";
        monitordeploy.pagesize3Cls = "";
        if (newValue == monitordeploy.pagesize1) {
            monitordeploy.pagesize1Cls = "pageSizeSelected";
        } else if (newValue == monitordeploy.pagesize2) {
            monitordeploy.pagesize2Cls = "pageSizeSelected";
        } else if (newValue == monitordeploy.pagesize3) {
            monitordeploy.pagesize3Cls = "pageSizeSelected";
        }
    })



  • 相关阅读:
    c#使用NPOI导出Excel及往Excel里追加记录
    c#自定义进度条
    游戏中的过程生成——元胞自动机 Celluar Automata 生成洞穴地形
    在Unity(C#)下实现Lazy Theta*寻路
    A*算法改进——Any-Angle Path Planning的Theta*算法与Lazy Theta*算法
    unity下的Line of Sight(LOS)的绘制
    unity中绘制战争迷雾
    unet中可视性检查的一些笔记
    在DirectX11下用Stencil Buffer绘制可视化Depth Complexity
    漏洞复现-CVE-2015-1427-Groovy远程代码执行
  • 原文地址:https://www.cnblogs.com/chenjiazhu/p/7473635.html
Copyright © 2011-2022 走看看