• Html5 小游戏 俄罗斯方块


    导言

    在一个风和日丽的一天,看完了疯狂HTML 5+CSS 3+JavaScript讲义,跟着做了书里最后一章的俄罗斯方块小游戏,并做了一些改进,作为自己前端学习的第一站。

    游戏效果:

     

    制作思路

    因为书里的俄罗斯方块比较普通,太常规了,不是很好看,所以我在网上找了上面那张图片,打算照着它来做。(请无视成品和原图的差距)

    然后便是游戏界面和常规的俄罗斯方块游戏逻辑。

    接着便是游戏结束界面了。

    原本想做个弹出层,但觉得找图片有点麻烦,所以就在网上找了文字特效,套用了一下。

    代码实现:

     首先是html文件和css文件,主要涉及了布局方面。作为新手,在上面真的是翻来覆去的踩坑。o(╥﹏╥)o

    index.html

    <!DOCTYPE html>
    <html>
    <head>
        <title>俄罗斯方块</title>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
        <link rel=stylesheet type="text/css" href="teris.css">
        <style type="text/css">
            /*导入外部的字体文件*/
            @font-face{
                font-family:tmb;/*为字体命名为tmb*/
                src:url("DS-DIGIB.TTF") format("TrueType");/*format为字体文件格式,TrueType为ttf*/
            }
            div>span{
                font-family:tmb;
                font-size:18pt;
                color:green;
            }
        </style>
    </head>
    
    <body>
        <div id="container" class="bg">
            <!--ui-->
            <div class="ui_bg">
                <div style="float:left;margin-right:4px;">
                    速度:<span id="cur_speed">1</span>
                </div>
                <div style="float:left;">
                    当前分数:<span id="cur_points">0</span>
                </div>
                <div style="float:right;">
                    最高分数:<span id="max_points">0</span>
                </div>
            </div>
            <canvas id="text" width="500" height="100" style="position:absolute;"></canvas>
            <canvas id="stage" width="500" height="100" style="position:absolute;"></canvas>
        </div>
        <script src='EasePack.min.js'></script>
        <script src='TweenLite.min.js'></script>
        <script src='easeljs-0.7.1.min.js'></script>
        <script src='requestAnimationFrame.js'></script>
        <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
        <script type="text/javascript" src="teris.js"></script>
    </body>
    </html>

    teris.css

    *{
        margin:0;
        padding:0;
    }
    html, body{
        width:100%;
        height:100%;
    }
    
    .bg{
        font-size:13pt;
        background-color:rgb(239, 239, 227);
        /*好看的渐变色*/
        background-image:radial-gradient(rgb(239, 239, 227), rgb(230, 220, 212));
        /*阴影*/
        box-shadow:#cdc8c1 -1px -1px 7px 0px;
        padding-bottom:4px;
    }
    
    .ui_bg{
        border-bottom:1px #a69e9ea3 solid;
        padding-bottom:2px;
        overflow:hidden;/*没有这句的话因为子div都设置了float,所以是浮在网页上的,所以父div就没有高度,这句清除了浮动,让父div有了子div的高度*/
    }

    然后是重头戏,teris.js

    游戏变量

    //游戏设定
    var TETRIS_ROWS = 20;
    var TETRIS_COLS = 14;
    var CELL_SIZE = 24;
    var NO_BLOCK=0;
    var HAVE_BLOCK=1;
    // 定义几种可能出现的方块组合
    var blockArr = [
        // Z
        [
            {x: TETRIS_COLS / 2 - 1 , y:0},
            {x: TETRIS_COLS / 2 , y:0},
            {x: TETRIS_COLS / 2 , y:1},
            {x: TETRIS_COLS / 2 + 1 , y:1}
        ],
        // 反Z
        [
            {x: TETRIS_COLS / 2 + 1 , y:0},
            {x: TETRIS_COLS / 2 , y:0},
            {x: TETRIS_COLS / 2 , y:1},
            {x: TETRIS_COLS / 2 - 1 , y:1}
        ],
        //
        [
            {x: TETRIS_COLS / 2 - 1 , y:0},
            {x: TETRIS_COLS / 2 , y:0},
            {x: TETRIS_COLS / 2 - 1 , y:1},
            {x: TETRIS_COLS / 2 , y:1}
        ],
        // L
        [
            {x: TETRIS_COLS / 2 - 1 , y:0},
            {x: TETRIS_COLS / 2 - 1, y:1},
            {x: TETRIS_COLS / 2 - 1 , y:2},
            {x: TETRIS_COLS / 2 , y:2}
        ],
        // J
        [
            {x: TETRIS_COLS / 2  , y:0},
            {x: TETRIS_COLS / 2 , y:1},
            {x: TETRIS_COLS / 2  , y:2},
            {x: TETRIS_COLS / 2 - 1, y:2}
        ],
        // □□□□
        [
            {x: TETRIS_COLS / 2 , y:0},
            {x: TETRIS_COLS / 2 , y:1},
            {x: TETRIS_COLS / 2 , y:2},
            {x: TETRIS_COLS / 2 , y:3}
        ],
        //
        [
            {x: TETRIS_COLS / 2 , y:0},
            {x: TETRIS_COLS / 2 - 1 , y:1},
            {x: TETRIS_COLS / 2 , y:1},
            {x: TETRIS_COLS / 2 + 1, y:1}
        ]
    ];
    
    // 记录当前积分
    var curScore=0;
    // 记录曾经的最高积分
    var maxScore=1;
    var curSpeed=1;
    //ui元素
    var curSpeedEle=document.getElementById("cur_speed");
    var curScoreEle=document.getElementById("cur_points");
    var maxScoreEle=document.getElementById("max_points");
    
    var timer;//方块下落控制
    
    var myCanvas;
    var canvasCtx;
    var tetris_status;//地图数据
    var currentFall;//当前下落的block

    游戏界面的完善

    //create canvas
    function createCanvas(){
        myCanvas=document.createElement("canvas");
        myCanvas.width=TETRIS_COLS*CELL_SIZE;
        myCanvas.height=TETRIS_ROWS*CELL_SIZE;
        //绘制背景
        canvasCtx=myCanvas.getContext("2d");
        canvasCtx.beginPath();
        //TETRIS_COS
        for(let i=1; i<TETRIS_COLS; i++){
            canvasCtx.moveTo(i*CELL_SIZE, 0);
            canvasCtx.lineTo(i*CELL_SIZE, myCanvas.height);
        }
        for(let i=1; i<TETRIS_ROWS; i++){
            canvasCtx.moveTo(0, i*CELL_SIZE);
            canvasCtx.lineTo(myCanvas.width, i*CELL_SIZE);
        }
        canvasCtx.closePath();
        canvasCtx.strokeStyle="#b4a79d";
        canvasCtx.lineWidth=0.6;
        canvasCtx.stroke();
        //第一行,最后一行,第一列,最后一列粗一点。
        canvasCtx.beginPath();
        canvasCtx.moveTo(0, 0);
        canvasCtx.lineTo(myCanvas.width, 0);
        canvasCtx.moveTo(0, myCanvas.height);
        canvasCtx.lineTo(myCanvas.width, myCanvas.height);
        canvasCtx.moveTo(0, 0);
        canvasCtx.lineTo(0, myCanvas.height);
        canvasCtx.moveTo(myCanvas.width, 0);
        canvasCtx.lineTo(myCanvas.width, myCanvas.height);
        canvasCtx.closePath();
        canvasCtx.strokeStyle="#b4a79d";
        canvasCtx.lineWidth=4;
        canvasCtx.stroke();
        //设置绘制block时的style
        canvasCtx.fillStyle="#201a14";
    }
    draw canvas
     1 function changeWidthAndHeight(w, h){
     2     //通过jquery设置css
     3     h+=$("ui_bg").css("height")+$("ui_bg").css("margin-rop")+$("ui_bg").css("margin-bottom")+$("ui_bg").css("padding-top")+$("ui_bg").css("padding-bottom");
     4     $(".bg").css({
     5         "width":w,
     6         "height":h,
     7         "top":0, "bottom":0, "right":0, "left":0,
     8         "margin":"auto"
     9     });
    10 }
    change width and height
     1 //draw blocks
     2 function drawBlocks(){
     3     //清空地图
     4     for(let i=0; i<TETRIS_ROWS;i++){
     5         for(let j=0;j<TETRIS_COLS;j++)
     6             canvasCtx.clearRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);
     7     }
     8     //绘制地图
     9     for(let i=0; i<TETRIS_ROWS;i++){
    10         for(let j=0;j<TETRIS_COLS;j++){
    11             if(tetris_status[i][j]!=NO_BLOCK)
    12                 canvasCtx.fillRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);//中间留点缝隙
    13         }
    14     }
    15     //绘制currentFall
    16     for(let i=0;i<currentFall.length;i++)
    17         canvasCtx.fillRect(currentFall[i].x*CELL_SIZE+1, currentFall[i].y*CELL_SIZE+1, CELL_SIZE-2,CELL_SIZE-2);
    18 }
    draw block

    游戏逻辑

     1 function rotate(){
     2     // 定义记录能否旋转的旗标
     3     var canRotate = true;
     4     for (var i = 0 ; i < currentFall.length ; i++)
     5     {
     6         var preX = currentFall[i].x;
     7         var preY = currentFall[i].y;
     8         // 始终以第三个方块作为旋转的中心,
     9         // i == 2时,说明是旋转的中心
    10         if(i != 2)
    11         {
    12             // 计算方块旋转后的x、y坐标
    13             var afterRotateX = currentFall[2].x + preY - currentFall[2].y;
    14             var afterRotateY = currentFall[2].y + currentFall[2].x - preX;
    15             // 如果旋转后所在位置已有方块,表明不能旋转
    16             if(tetris_status[afterRotateY][afterRotateX + 1] != NO_BLOCK)
    17             {
    18                 canRotate = false;
    19                 break;
    20             }
    21             // 如果旋转后的坐标已经超出了最左边边界
    22             if(afterRotateX < 0 || tetris_status[afterRotateY - 1][afterRotateX] != NO_BLOCK)
    23             {
    24                 moveRight();
    25                 afterRotateX = currentFall[2].x + preY - currentFall[2].y;
    26                 afterRotateY = currentFall[2].y + currentFall[2].x - preX;
    27                 break;
    28             }
    29             if(afterRotateX < 0 || tetris_status[afterRotateY-1][afterRotateX] != NO_BLOCK)
    30             {
    31                 moveRight();
    32                 break;
    33             }
    34             // 如果旋转后的坐标已经超出了最右边边界
    35             if(afterRotateX >= TETRIS_COLS - 1 || 
    36                 tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
    37             {
    38                 moveLeft();
    39                 afterRotateX = currentFall[2].x + preY - currentFall[2].y;
    40                 afterRotateY = currentFall[2].y + currentFall[2].x - preX;
    41                 break;
    42             }
    43             if(afterRotateX >= TETRIS_COLS - 1 || 
    44                 tetris_status[afterRotateY][afterRotateX+1] != NO_BLOCK)
    45             {
    46                 moveLeft();
    47                 break;
    48             }
    49         }
    50     }
    51     if(canRotate){
    52         for (var i = 0 ; i < currentFall.length ; i++){
    53             var preX = currentFall[i].x;
    54             var preY = currentFall[i].y;
    55             if(i != 2){
    56                 currentFall[i].x = currentFall[2].x + 
    57                     preY - currentFall[2].y;
    58                 currentFall[i].y = currentFall[2].y + 
    59                     currentFall[2].x - preX;
    60             }
    61         }
    62         localStorage.setItem("currentFall", JSON.stringify(currentFall));
    63     }
    64 }
    旋转
     1 //按下 下 或 interval到了
     2 function next(){
     3     if(moveDown()){
     4         //记录block
     5         for(let i=0;i<currentFall.length;i++)
     6             tetris_status[currentFall[i].y][currentFall[i].x]=HAVE_BLOCK;
     7         //判断有没有满行的
     8         for(let j=0;j<currentFall.length;j++){
     9             for(let i=0;i<TETRIS_COLS; i++){
    10                 if(tetris_status[currentFall[j].y][i]==NO_BLOCK)
    11                     break;
    12                 //最后一行满了
    13                 if(i==TETRIS_COLS-1){
    14                     //消除最后一行
    15                     for(let i=currentFall[j].y; i>0;i--){
    16                         for(let j=0;j<TETRIS_COLS;j++)
    17                             tetris_status[i][j]=tetris_status[i-1][j];
    18                     }
    19                     //分数增加
    20                     curScore+=5;
    21                     localStorage.setItem("curScore", curScore);
    22                     if(curScore>maxScore){
    23                         //超越最高分
    24                         maxScore=curScore;
    25                         localStorage.setItem("maxScore", maxScore);
    26                     }
    27                     //加速
    28                     curSpeed+=0.1;
    29                     localStorage.setItem("curSpeed", curSpeed);
    30                     //ui输出
    31                     curScoreEle.innerHTML=""+curScore;
    32                     maxScoreEle.innerHTML=""+maxScore;
    33                     curSpeedEle.innerHTML=curSpeed.toFixed(1);//保留两位小数
    34                     clearInterval(timer);
    35                     timer=setInterval(function(){
    36                         next();
    37                     }, 500/curSpeed);
    38                 }
    39             }
    40         }
    41         //判断是否触顶
    42         for(let i=0;i<currentFall.length;i++){
    43             if(currentFall[i].y==0){
    44                 gameEnd();
    45                 return;
    46             }
    47         }
    48         localStorage.setItem("tetris_status", JSON.stringify(tetris_status));
    49         //新的block
    50         createBlock();
    51         localStorage.setItem("currentFall", JSON.stringify(currentFall));
    52     }
    53     drawBlocks();
    54 }
    55 
    56 //右移
    57 function moveRight(){
    58     for(let i=0;i<currentFall.length;i++){
    59         if(currentFall[i].x+1>=TETRIS_ROWS || tetris_status[currentFall[i].y][currentFall[i].x+1]!=NO_BLOCK)
    60             return;
    61     }
    62     for(let i=0;i<currentFall.length;i++)
    63         currentFall[i].x++;
    64     localStorage.setItem("currentFall", JSON.stringify(currentFall));
    65     return;
    66 }
    67 //左移
    68 function moveLeft(){
    69     for(let i=0;i<currentFall.length;i++){
    70         if(currentFall[i].x-1<0 || tetris_status[currentFall[i].y][currentFall[i].x-1]!=NO_BLOCK)
    71             return;
    72     }
    73     for(let i=0;i<currentFall.length;i++)
    74         currentFall[i].x--;
    75     localStorage.setItem("currentFall", JSON.stringify(currentFall));
    76     return;
    77 }
    78 //judge can move down and if arrive at end return 1, if touch other blocks return 2, else, return 0
    79 function moveDown(){
    80     for(let i=0;i<currentFall.length;i++){
    81         if(currentFall[i].y>=TETRIS_ROWS-1 || tetris_status[currentFall[i].y+1][currentFall[i].x]!=NO_BLOCK)
    82             return true;
    83     }
    84 
    85     for(let i=0;i<currentFall.length;i++)
    86         currentFall[i].y+=1;
    87     return false;
    88 }
    上下左右移动
     1 function gameKeyEvent(evt){
     2     switch(evt.keyCode){
     3         //向下
     4         case 40://
     5         case 83://S
     6             next();
     7             drawBlocks();
     8             break;
     9         //向左
    10         case 37://
    11         case 65://A
    12             moveLeft();
    13             drawBlocks();
    14             break;
    15         //向右
    16         case 39://
    17         case 68://D
    18             moveRight();
    19             drawBlocks();
    20             break;
    21         //旋转
    22         case 38://
    23         case 87://W
    24             rotate();
    25             drawBlocks();
    26             break;
    27     }
    28 }
    keydown事件监听

    其他的详细情况可以看源代码,我就不整理了。

    接下来我们看游戏结束时的特效。因为我也不是很懂,所以在这里整理的会比较详细。当做学习。

     1 //game end
     2 function gameEnd(){
     3     clearInterval(timer);
     4     //键盘输入监听结束
     5     window.onkeydown=function(){
     6         //按任意键重新开始游戏
     7         window.onkeydown=gameKeyEvent;
     8         //初始化游戏数据
     9         initData();
    10         createBlock();
    11         localStorage.setItem("currentFall", JSON.stringify(currentFall));
    12         localStorage.setItem("tetris_status", JSON.stringify(tetris_status));
    13         localStorage.setItem("curScore", curScore);
    14         localStorage.setItem("curSpeed", curSpeed);
    15         //绘制
    16         curScoreEle.innerHTML=""+curScore;
    17         curSpeedEle.innerHTML=curSpeed.toFixed(1);//保留两位小数
    18         drawBlocks();
    19         timer=setInterval(function(){
    20             next();
    21         }, 500/curSpeed);
    22         //清除特效
    23         this.stage.removeAllChildren();
    24         this.textStage.removeAllChildren();
    25     };
    26     //特效,游戏结束
    27     setTimeout(function(){
    28         initAnim();
    29         //擦除黑色方块
    30         for(let i=0; i<TETRIS_ROWS;i++){
    31             for(let j=0;j<TETRIS_COLS;j++)
    32                 canvasCtx.clearRect(j*CELL_SIZE+1, i*CELL_SIZE+1, CELL_SIZE-2, CELL_SIZE-2);
    33         }
    34     }, 200);
    35     //推迟显示Failed
    36     setTimeout(function(){
    37         if(textFormed) {
    38             explode();
    39             setTimeout(function() {
    40                 createText("FAILED");
    41             }, 810);
    42         } else {
    43             createText("FAILED");
    44         }
    45     }, 800);
    46 }

    上面代码里的localstorage是html5的本地数据存储。因为不是运用很难,所以具体看代码。

    整个特效是运用了createjs插件。要引入几个文件。

    easeljs-0.7.1.min.js, EasePacj.min.js, requestAnimationFrame.js和TweenLite.min.js
    游戏重新开始就要清除特效。我看api里我第一眼望过去最明显的就是removeAllChildren(),所以就选了这个。其他的改进日后再说。

            //清除特效
            this.stage.removeAllChildren();
            this.textStage.removeAllChildren();
    function initAnim() {
        initStages();
        initText();
        initCircles();
        //在stage下方添加文字——按任意键重新开始游戏.
        tmp = new createjs.Text("t", "12px 'Source Sans Pro'", "#54555C");
        tmp.textAlign = 'center';
        tmp.x = 180;
        tmp.y=350;
        tmp.text = "按任意键重新开始游戏";
        stage.addChild(tmp);
        animate();
    }
    initAnim

    上面初始化了一个stage,用于存放特效,一个textstage,用于形成“FAILED”的像素图片。还有一个按任意键重新游戏的提示。同时开始每隔一段时间就刷新stage。

    根据block的位置来初始化小圆点。

     1 function initCircles() {
     2     circles = [];
     3     var p=[];
     4     var count=0;
     5     for(let i=0; i<TETRIS_ROWS;i++)
     6         for(let j=0;j<TETRIS_COLS;j++)
     7             if(tetris_status[i][j]!=NO_BLOCK)
     8                 p.push({'x':j*CELL_SIZE+2, 'y':i*CELL_SIZE+2, 'w':CELL_SIZE-3, 'h':CELL_SIZE-4});
     9     for(var i=0; i<250; i++) {
    10         var circle = new createjs.Shape();
    11         var r = 7;
    12         //x和y范围限定在黑色block内
    13         var x = p[count]['x']+p[count]['w']*Math.random();
    14         var y = p[count]['y']+p[count]['h']*Math.random();
    15         count++;
    16         if(count>=p.length)
    17             count=0;
    18         var color = colors[Math.floor(i%colors.length)];
    19         var alpha = 0.2 + Math.random()*0.5;
    20         circle.alpha = alpha;
    21         circle.radius = r;
    22         circle.graphics.beginFill(color).drawCircle(0, 0, r);
    23         circle.x = x;
    24         circle.y = y;
    25         circles.push(circle);
    26         stage.addChild(circle);
    27         circle.movement = 'float';
    28         tweenCircle(circle);
    29     }
    30 }
    initCircles

    然后再讲显示特效Failed的createText()。先将FAILED的text显示在textstage里,然后ctx.getImageData.data获取像素数据,并以此来为每个小圆点定义位置。

     1 function createText(t) {
     2     curText=t;
     3     var fontSize = 500/(t.length);
     4     if (fontSize > 80) fontSize = 80;
     5     text.text = t;
     6     text.font = "900 "+fontSize+"px 'Source Sans Pro'";
     7     text.textAlign = 'center';
     8     text.x = TETRIS_COLS*CELL_SIZE/2;
     9     text.y = 0;
    10     textStage.addChild(text);
    11     textStage.update();
    12 
    13     var ctx = document.getElementById('text').getContext('2d');
    14     var pix = ctx.getImageData(0,0,600,200).data;
    15     textPixels = [];
    16     for (var i = pix.length; i >= 0; i -= 4) {
    17         if (pix[i] != 0) {
    18             var x = (i / 4) % 600;
    19             var y = Math.floor(Math.floor(i/600)/4);
    20             if((x && x%8 == 0) && (y && y%8 == 0)) textPixels.push({x: x, y: y});
    21         }
    22     }
    23 
    24     formText();
    25     textStage.clear();//清楚text的显示
    26 }
    CreateText

    跟着代码的节奏走,我们现在来到了formtext.

     1 function formText() {
     2     for(var i= 0, l=textPixels.length; i<l; i++) {
     3         circles[i].originX = offsetX + textPixels[i].x;
     4         circles[i].originY = offsetY + textPixels[i].y;
     5         tweenCircle(circles[i], 'in');
     6     }
     7     textFormed = true;
     8     if(textPixels.length < circles.length) {
     9         for(var j = textPixels.length; j<circles.length; j++) {
    10             circles[j].tween = TweenLite.to(circles[j], 0.4, {alpha: 0.1});
    11         }
    12     }
    13 }
    formtext

    explode()就是讲已组成字的小圆点给重新遣散。

    动画实现是使用了tweenlite.

     1 function tweenCircle(c, dir) {
     2     if(c.tween) c.tween.kill();
     3     if(dir == 'in') {
     4         /*TweenLite.to 改变c实例的x坐标,y坐标,使用easeInOut弹性函数,透明度提到1,改变大小,radius,总用时0.4s*/
     5         c.tween = TweenLite.to(c, 0.4, {x: c.originX, y: c.originY, ease:Quad.easeInOut, alpha: 1, radius: 5, scaleX: 0.4, scaleY: 0.4, onComplete: function() {
     6             c.movement = 'jiggle';/*轻摇*/
     7             tweenCircle(c);
     8         }});
     9     } else if(dir == 'out') {
    10         c.tween = TweenLite.to(c, 0.8, {x: window.innerWidth*Math.random(), y: window.innerHeight*Math.random(), ease:Quad.easeInOut, alpha: 0.2 + Math.random()*0.5, scaleX: 1, scaleY: 1, onComplete: function() {
    11             c.movement = 'float';
    12             tweenCircle(c);
    13         }});
    14     } else {
    15         if(c.movement == 'float') {
    16             c.tween = TweenLite.to(c, 5 + Math.random()*3.5, {x: c.x + -100+Math.random()*200, y: c.y + -100+Math.random()*200, ease:Quad.easeInOut, alpha: 0.2 + Math.random()*0.5,
    17                 onComplete: function() {
    18                     tweenCircle(c);
    19                 }});
    20         } else {
    21             c.tween = TweenLite.to(c, 0.05, {x: c.originX + Math.random()*3, y: c.originY + Math.random()*3, ease:Quad.easeInOut,
    22                 onComplete: function() {
    23                     tweenCircle(c);
    24                 }});
    25         }
    26     }
    27 }

    TweenLite.to函数第一个参数,要做动画的实例,第二个参数,事件,第三个参数,动画改变参数。

    Quad.easeInOut()意思是在动画开始和结束时缓动。
    onComplete动画完成时调用的函数。易得,在我们的应用中,我们将开始下一次动画。

     个人感言

    其实刚开始没想做这么复杂,所以文件排的比较随意,然后就导致了后期项目完成时那副杂乱无章的样子。^_^,以后改。等我等看懂动画效果时在说,现在用的有点半懵半懂。

    这篇博客写得有点乱。新手之作,就先这样吧。同上,以后改。因为不知道这个项目会不会拿来直接当我们计算机职业实践的作业。要是的话,我就彻改,连同博客。

    以下是源代码地址。(我还以为csdn的下载要的积分是自己定的。等我下次彻改的时候我传到github上。现在将就一下)

    https://download.csdn.net/download/qq_26136211/12011640

  • 相关阅读:
    git+jenkins持续集成一:git上传代码
    性能测试五十:Jmeter+Influxdb+Grafana实时数据展示系统搭建
    性能测试四十九:ngrinder压测平台
    性能测试四十八:Jenkins+Ant+Jmeter系统部署
    性能测试四十七:jmeter性能监控工具ServerAgent
    工具篇:使用natapp工具映射内网到外网访问
    总想自己动动手系列·3·微信公众号和外网服务交互之通过TOKEN验证(准备篇·1)
    总想自己动动手系列·2·本地和外网(Liunx服务器上部署的web项目)按照自定义的报文格式进行交互(完结篇)
    总想自己动动手系列·1·本地和外网(Liunx服务器上部署的web项目)按照自定义的报文格式进行交互(准备篇)
    Linux-在新买的阿里云服务器上部署Tomcat并支持外网访问的配置(步骤记录)
  • 原文地址:https://www.cnblogs.com/zyyz1126/p/11976260.html
Copyright © 2020-2023  润新知