• postman做接口自动化测试


    • 可能是目前最好用的web接口调试工具
    • 无需注册(注册后可多终端同步用例)
    • 免费(每年付费$60可用云服务,30天免费试用)
    • 保存历史记录
    • 支持录制请求
    • 基于Chrome的V8引擎,支持JS脚本(基本支持ES6,浏览器相关对象和API和require() import等除外)
    • 同样的代码和用例可用于自动化接口测试,见它的命令行版本Newman介绍
    • 能生成各种语言的HTTP请求代码模板
    • 能生成比较好看的在线API文档(支持MarkDown)
    • 提供简易版持续集成(Monitor功能,只支持公网IP的请求…不如Jenkins实用)

    选它的做自动化测试的理由:

    • 满足互联网公司的多数场景
    • 超轻,比另一个热门选择——JMeter门槛低、开发效率高、界面顺眼
    • 开发几乎人手一个,容易沟通,容易推动开发自测
    • 容易拉功能测试入坑,发生人员变动时容易交接

    不选它的理由:

    • 功能受限:Postman运行在沙盒里,Newman虽然基于Node.js,但一套脚本2边共用决定了它的功能不能比Postman多,无法用在以下场景:
    • 需要读写文件
    • 需要读写数据库
    • 需要引入外部库,如不常见/自定义的加密方式
    • 非HTTP协议
    • 毕竟是调试小工具出身,和成熟的自动化测试框架在管理用例、代码复用、稳定性、报告,甚至易用性上都有明显差距,用的人要有足够能力从坑里爬出来
    • 如果项目经常用JMeter做接口测试,有人足够熟悉JMeter,可以考虑把脚本拆分到单个接口的粒度,花点功夫把功能和性能自动化都做了,不用维护2套
    • 如果全英文和缺乏中文资料也算的话

    安装

    https://www.getpostman.com/apps

    建议选择Mac/Windows app,比起Chrome app,下载不需要FQ,功能更强大


    基本使用

    官方文档

    教程

    接口测试工具 postman, 2016-09-01(基于Chrome版的Postman,和桌面版大同小异)

    [API 测试]postman, 2016-02-29

    API自动化测试利器——Postman, 2015-09-26(教程里的版本比较旧了,但还是讲得不错)

    How to write powerful automated API tests with Postman, Newman and Jenkins, 2015-09-03

    How to write automated tests for APIs using Postman – Part 1, 2014-03-07

    How to write automated tests for APIs with Postman – Part 2, 2014-04-17

    Writing automated tests with Postman – Part 3, 2014-05-09

    • 有些早期文章会提到Jetpack,曾经收10美元,现在成了Postman自带的Collection Runner,可以批量执行用例

    示例

    Postman Echo

    Making the perfect HTTP request using Postman Echo, 2015-11-13

    Cooper's Meal Plan

    Conditional Workflows in Postman, 2016-03-23

    Spotify Playlist Generator

    Generate Spotify Playlists using a Postman Collection, 2016-11-09

    CurencyCloud的Postman集合和使用说明


    录制接口

    Capturing requests (native app)

    Using Postman Proxy to Capture and Inspect API Calls from iOS or Android Devices,2016-06-26

    接口调用方希望测试业务逻辑时,用不着Fiddler/Charles抓包再往里面一个个填这么麻烦

    开启Postman的代理(默认5555端口),浏览器/手机设好对应的IP和端口就行

    支持正则表达式过滤URL,建议排除掉静态资源、流量统计站和别的后台进程时不时请求的网站

    baidu|google|microsoft|github|qq\.com|.*\.(html?|css|js|png|jpe?g|webp|ico)$

    可以设置保存的位置:

    如果所有操作步骤都要做接口测试,建议直接保存到目标集合

    如果一大堆操作里只取其中一个到几个接口,建议保存到历史记录,挑出想要的另存到目标集合,不用浪费时间删


    Collection Runner

    Running a collection

    • 适合一次运行/调试多个用例,不用自己一个个点发送
    • 适合开发自测接口性能(例如循环个10000次),至少响应时间和稳定性都过关了才交给别人调用
    • 适合做简单的数据驱动测试——CSV文件里写上各种合法非法参数组合,1个集合/文件夹只放1个接口,关联起来点运行就是
    • runtime、默认行为(比如不保存环境变量)、可以指定数据文件等跟Newman一样,适合用界面调通了用例再放到命令行做持续集成

    Postman集合的概念

    导出之后可以看到1个集合其实就是1个json文件,打开文件就能看到,里面的item属性是个数组,每个用例都是数组里的1个JSON对象

    Postman/Newman1次只能跑1个集合,各个集合之间没有任何关系

    Postman文件夹的概念

    文件夹实际上也是集合的item数组里的对象,它里面也有1个item数组,文件夹里的用例是这数组里面的对象

    collection runner/Newman批量跑用例的最小粒度是文件夹

    • 目前集合下只支持1级文件夹,不推荐场景搞太复杂,用例管理起来有难度
    • 目前集合和文件夹只能按字母顺序排序,用例可以拖动排序
    • 目前搜索不能搜到URL地址,建议用例名写成 /path/to/api 接口描述,方便看和搜索

    如果打算做自动化和持续集成:

    • 集合、文件夹的名字建议英文无空格,方便命令行调用
    • 要么不放,要么全放文件夹
    • 每个文件夹测完全独立的子场景,文件夹之间的用例不要有顺序、状态等依赖关系

    如果你不是从写用例到搞持续集成都包了并且一直由你自己维护,不能假设别人会按你想要的方式、顺序、设置运行测试

    别人写的脚本可能运行整个集合或逐个文件夹运行,不同文件夹之间可能同步可能异步,可能会保存运行中产生的环境变量也可能不保存

    此外,越多耦合,稳定性越差,维护工作量越大

    流程控制

    只有在collection runner或Newman里才生效(在普通界面你选哪个发送就是哪个)

    /* 假设2个用例的顺序为: 用例A 用例B 如果希望执行顺序为: 用例A -> 用例B -> 用例A,又不想复制一份用例A,那么 */ // 用例A的Tests里写 if (xxx) postman.setNextRequest('null'); // 终止执行 // 用例B的Tests里写 postman.setNextRequest('用例A'); // 【注意】如果不设终止条件,用例A执行完到用例B,用例B执行完又指向用例A,会构成死循环 // PS:postman是Postman提供的全局对象


    断言

    Testing Sandbox 内建对象、方法、变量和可用的第三方库介绍

    Testing examples

    Extracting data from responses and chaining requests, 2014-10-27

    在主界面可以靠肉眼看返回结果,但在collection runner/Newman里如果不加断言,跑完没法知道是成功还是失败

    断言写在Tests标签页里,上手可以参考文档和界面右边的代码模板:

    tests['Status code is 200'] = responseCode.code === 200; // 推荐用全等 ===,确保类型和值都一致

    tests['Response time is less than 200ms'] = responseTime < 200; tests['Body matches string'] = responseBody.has('xxx'); // 只要有指定关键字就行,在哪、有几个等都不管 tests['Content-Type is present'] = postman.getResponseHeader('Content-Type') || false; // Postman的断言实际上就是往全局对象

    tests 添加键值对 // 它的key会显示在报告界面上,value是可以解析为boolean的表达式 // 如果得出的值是true,在报告里显示为成功,false失败 // 【变通】用总是为真的断言来显示信息 tests[`[INFO] Request params: ${JSON.stringify(request.data)}`] = true; // 显示所有请求参数(在自动化测试里很有用)

    tests[`跑第${iteration + 1}次`] = true; // 用在runner里循环很多次时 // 迭代次数 iteration 是Postman提供的全局变量,从0开始 // PS:request是Postman提供的全局对象 // responseCode(对象)、responseTime(数字)、responseBody(字符串)目前是Postman收到服务器返回内容才声明的变量 //

    【注意】如果你在做自动化测试,目前在接口超时没返回时: // responseCode、responseTime、responseBody都没定义,使用时会导致脚本出错,判断是否超时没返回的只能靠header // request.data里的变量在超时时不解析,很容易让人误会请求参数传错了,建议此时不显示这行

    关于'use strict';

    因为Postman提供了不少全局变量,写了'use strict';会在用到这些变量的地方出警告提示

    如果平时有养成好习惯,不用容易出错的写法,不写它也没关系


    提取接口返回值

    返回JSON时

    let json = JSON.parse(responseBody); // responseBody是包含整个返回内容的字符串

    // 提取某字段的值: let foobar = json.foo.bar[0].foobar;

    // 假设结构为 {"foo": {"bar": [{"foobar": 1}, {"baz": 2}]}}

    // 想用在自动化测试可以多写点: let json; try { json = JSON.parse(responseBody); } catch (err) { tests['Expect response body to be valid JSON'] = false; tests[`Response body: ${responseBody}`] = true; console.error(err); }

    返回HTML时

    // A. 用正则表达式匹配 let foo = responseBody.match(/foo/g);

    // g 全局 i 不分大小写 m 多行 tests['blahblahblah'] = foo[0] === 'bar';

    // 正则里包含变量时: let foo = 'xxx'; let bar = responseBody.match(new RegExp(`^${foo}.*$`, 'g');

    // B. 用CheerioJS库(可以让你用jQuery语法): const $ = cheerio.load(responseBody); $.html()

    // 整个网页 $('title').text();

    // 标签里的文字</p> <p>jQuery replaced by CheerioJS in Postman Sandbox, 2016-08-30</p> <hr> <p>提取请求参数的值</p> <p>利用Postman提供的request.data</p> <p>如果POST Body里选了form或x-www-form-urlencoded</p> <p></p> <p>request对象的data属性是个对象,里面每个属性对应你填的参数名</p> <p>// 假设有个参数叫phone request.data.phone</p> <p>如果Body里选的是raw,request.data是字符串</p> <p>// 假设传JSON字符串,里面有叫做phone的属性 JSON.parse(request.data).phone</p> <hr> <p>设置环境变量</p> <p>Setting up an environment with variables</p> <p>Postman的环境变量分为 environment 和 global 2种</p> <p>实际上就是environment、globals这2个全局的对象(字典)里的内容</p> <p></p> <p>它们的key作为变量名,value作为变量的值</p> <p>environment</p> <p>满足99%的需要,平时只用它就够了,global留到后文讲</p> <ul><li>作用域为整个集合</li><li>能创建多个environment文件,方便切换不同测试环境</li></ul> <p>在地址栏、header、请求参数、外部数据文件里,用 {<!-- -->{变量名}} 获取环境变量的值</p> <p></p> <p>如:{<!-- -->{URL}}/path/to/api</p> <p>(更常见的${}在JS的ES6语法里被占用了,Postman只能选这么个奇怪写法)</p> <p>在Pre-request Script和Tests的代码里略有不同:</p> <p>/* 官方提供的方法 */ // 设置 postman.setEnvironmentVariable('variableKey', value);</p> <p>// 注意:通过菜单或以上方法设置的环境变量,值会转成字符串,取的时候要转换</p> <p>// 获取 let foo = postman.getEnvironmentVariable('variableKey');</p> <p>// 最常用到字符串数字转数字:Number(foo) // 或者万能的 JSON.parse(foo) 字符串的数字、数组、对象、布尔值扔进去都能转成对应的类型 // 更新 // 就是再设置一次同名的环境变量,换别的值 // 清除环境变量 postman.clearEnvironmentVariable('variableKey');</p> <p>/* 懒人版 */</p> <p>// 既然知道实际上是操作 environment 对象,如果你有JS基础,可以直接:</p> <p>// 添加属性 environment.variableKey = 12345;</p> <p>// 少打字,取出时也不用转换类型</p> <p>// 获取 let foo = environment.variableKey;</p> <p>// 清除 delete environment.variableKey;</p> <p>// 不要用 environment.variableKey = undefined; 实际上属性还在,只是平时我们发现值是undefined时当它不存在了而已</p> <p>// 而Postman不这么干,它把undefined转成字符串……</p> <p>// 如果你非要跟自己过不去,用了变量名不允许的字符做key(比如有空格),只能写成 environment['variableKey']</p> <p>// 只要没跟自己过不去,可以用ES6的对象解构语法一次取多个: let {key1, key2, key3} = environment;</p> <p>【注意】</p> <p>在Postman主界面运行过后,通过代码设置的环境变量会存到IndexedDB,跟在菜单里设置一样,用例跑完不消失</p> <p>但在collection runner或Newman跑则是默认不保存,跑完就消失,做自动化测试时要注意</p> <hr> <p>动态请求参数</p> <p>在runner里循环发n次请求/做自动化测试时,有些接口不适合写死参数</p> <ol><li>Postman有以下内建变量,适合一次性使用:</li></ol> <p>{<!-- -->{$guid}} // 生成GUID {<!-- -->{$timestamp}} // 当前时间戳 {<!-- -->{$randomInt}} // 0-1000的随机整数</p> <ol><li>参数依赖上一个请求的返回:</li></ol> <p>上个请求的Tests里提取参数存环境变量,这个请求里用{<!-- -->{变量名}}取值</p> <ol><li>参数每次都不同,但之后的断言或别的请求里可能还要用:</li></ol> <p>在Pre-request Script里写代码处理,存为环境变量,参数里用{<!-- -->{变量名}}取值</p> <p>例如</p> <p>const randomInt = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;</p> <p>// 随机整数 const getRandomValue = list => list[randomInt(0, list.length - 1)];</p> <p>// 随机选项 // 随机手机 environment.randomMobile = `18${randomInt(100000000, 999999999)}`;</p> <p>// 随机设备名 environment.randomDevice = getRandomValue(['ios', 'android']);</p> <p>Postman目前没有很方便的重用代码的手段,编辑框也不是IDE,没智能提示,尽量别整那么复杂</p> <hr> <p>调试</p> <p>cmd + alt + c(Windows ctrl + alt + c)打开Postman控制台,可以看请求和响应内容</p> <p>用console.log()打印,到控制台看</p> <p></p> <p>或tests['这里拼出你想看的字符串'] = true在界面/报告看断言</p> <hr> <p>mock</p> <p>Using a mocking service to create Postman Collections,2016-01-26</p> <hr> <p>global</p> <p>不做自动化测试可以跳过这段</p> <p>跟environment几乎完全一样,在地址栏、header、请求参数、外部数据文件里也是{<!-- -->{}}调用,除了:</p> <ul><li>只有1个global文件</li><li>菜单藏得较深,在生成的API文档里也不解析,决定了它不适合做参数化</li><li>environment和global同名时,优先用environment</li></ul> <p>global只建议用在1种场景:定义公共函数</p> <p>先正常地写好函数,再用在线压缩工具压成一行</p> <p></p> <p>在菜单里选 Bulk Edit,以每行一对 key:value 的形式编辑,变量名做key,函数做value</p> <p>assertNotTimeout:var hasResponse=postman.getResponseHeader('Content-Type')?true:false; if(!hasResponse)</p> <p>tests['服务端在超时前没返回任何数据,请检查相关服务、网络或反向代理设置(以下跳过其他断言)']=false; logParams:if(hasResponse)</p> <p>tests[`[INFO] 请求参数(仅限POST,超时没返回时不解析):${JSON.stringify(request.data)}`]=true; getResponseJson:try{if(hasResponse) var json=JSON.parse(responseBody);}catch(err){ tests['服务端没返回合法的JSON格式,请检查相关服务、网络或反向代理设置(以下跳过其他断言)']=false;</p> <p>tests[`[INFO] 返回:${responseBody}`]=true; console.error(err);} assertType:var assertType=(name,value,type)=>{let isType=(type==='array')? Array.isArray(value):typeof value===type; tests[`${name}为${type}(实际值:${value})`]=isType;}; assertEqual:var assertEqual=(name,actual,expected)=>{tests[`${name}等于${expected}(实际值:${actual})`]=actual===expected;}; assertNotEqual:var assertNotEqual=(name,actual,expected)=>{tests[`${name}不等于${expected}(实际值:${actual})`]=actual!==expected;}; // 注意在这里定义变量只有 var 的作用域够大,用 let 或 const 的话eval后就销毁了</p> <p>假设返回 {"name":"张三","userType":1,"settings":[]},在Tests里一上来就写:</p> <p>eval(globals.assertNotTimeout); // 判断是否超时(通过有没有Content-Type请求头),超时则断言失败 eval(globals.logParams); // 如果不超时,显示发出的请求参数 eval(globals.getResponseJson); // 如果不超时,解析返回的JSON对象,赋给json变量,返回值不合法则断言失败 // 下面定义了3个公共函数,免得每次断言都要写一大串: eval(globals.assertType); eval(globals.assertEqual); eval(globals.assertNotEqual); // 上面3个基本满足日常使用需要 if (json) { assertType('用户名', json.name, 'string'); // 在报告中显示为: '用户名为string,(实际值:张三)' assertType('设置', json.settings, 'array'); // JS里其实没有array类型(数组是object),这里做了处理,让报告更好懂 assertEqual('用户类型', json.userType, 1); // 显示为: '用户类型等于1,(实际值:1)' assertNotEqual('用户类型', json.userType, 2); // 显示为: '用户类型不等于2,(实际值:1)' }</p> <p>在官方给出更方便的重用代码的方法前,这是除了复制粘贴外唯一的重用方法</p> <p></p> <p>如果不做自动化测试,且断言写得很简单,不建议这么搞</p> <p>如果不幸跳了自动化的坑,通常一个项目会有100~200个接口要做自动化测试,要仔细比较哪种方法成本更高</p> <p></p> <p>定义函数前要仔细考虑好,万一中途要改参数和返回值,已经写好的n份也得改……</p> <p>建议定义的公共函数不超过个位数,并保留好没压缩的版本,不然别人没法接手</p> <p>Writing a behaviour driven API testing environment within Postman</p> <p>// 如果确实要在代码里设global // 官方的: postman.setGlobalVariable('variableKey', value); // 同样存成字符串 let bar = postman.getGlobalVariable('variableKey'); postman.clearGlobalVariable('variableKey'); // 或者自己操作 globals 对象</p> <hr> <p>数据文件</p> <p>不做自动化测试可以跳过这段</p> <p>Using data variables to run a collection multiple times</p> <p></p> <p>Using CSV and JSON data files in the Postman Collection Runner, 2014-10-28</p> <p>Using variables inside Postman and Collection Runner, 2014-02-20</p> <p>在collection runner或命令行的Newman里可以加载数据文件</p> <p>/* JSON格式 */</p> <p>// 文件里有且只有1个数组,每个对象算1条用例(在Postman里的全局变量叫做data)</p> <p>// key作为变量名,value作为变量的值</p> <p>// 文件里依然可以用 {<!-- -->{}} 拿到环境变量,注意不要把自己绕进去:</p> <p>// 如果是Pre-request Script里生成的环境变量,直接写进请求参数,不用经这里 [ {"mobile": "17000000001", "pwd": "123456"}, {"mobile": "17000000002", "pwd": "654321"}, {"mobile": "17000000003", "pwd": ""}, {"mobile": "{<!-- -->{ADMIN_MOBILE}}", "pwd": "{<!-- -->{ADMIN_PWD}}"} ]</p> <p>// 显然,这是json文件,并不能在里面写代码(除非你蛋疼在value里写字符串然后在用例里eval)</p> <p>// 用例的请求参数里依然用 {<!-- -->{key}} 拿到数据文件里的值,代码里则是 data.key</p> <p>// 如果key跟environment/globals里的key重名,这里的 > environment > globals</p> <p>/* CSV格式 */</p> <p>// 第1行变量名,下面是值,每行1条用例,没有空格</p> <p>// 没JSON格式的数据文件灵活 mobile,pwd 17000000001,123456 17000000002,654321 17000000003,</p> <p>【注意】</p> <p>谨慎使用。这东西增加了调试和定位问题的复杂性,也就大大增加了维护成本</p> <p></p> <p>而它带来的收益并不明显:</p> <ol><li>针对单个接口的简单压力测试</li></ol> <p>Postman不是正经的压测工具,既然选择了它就是图简单方便</p> <p></p> <p>像JMeter那样用CSV文件做数据源的意义不大,还得另外写程序/脚本生成这样的文件,时间上不划算</p> <p></p> <p>直接用代码生成数据就好,不差那一两毫秒</p> <ol><li>数据驱动测试:一条用例测多种参数组合,包括合法和非法值,避免复制粘贴n条略有不同的</li></ol> <p>很诱人,但是</p> <ul><li>产品设计时有考虑异常情况吗?</li><li>需求来源是否统一?</li><li>需求是否足够稳定?</li><li>整个项目有统一的异常处理规范还是看开发个人习惯?</li></ul> <p>如果不确定有些输入要不要/怎么处理,意味着改动可能会非常大</p> <p></p> <p>今天非法,明天变合法,后天又变非法</p> <p>如果冒烟用例用在持续集成,有测试不通过会阻止发布,会严重干扰正常发版,也影响大家对自动化测试的信心</p> <p>此外</p> <ul><li>内部的基础组件为了不同项目通用,可能会允许看起来相当没道理的值</li><li>不对外暴露的接口为了性能,可能会有意去掉所有校验</li></ul> <p>不要因为所谓“测试思维”,就在不了解的情况下为了测试而测试</p> <p></p> <p>这种力气留到探索性测试和安全测试,自动化测试还是要讲求稳定和省事</p> <p>此外</p> <ul><li>为了能匹配正常和异常情况(和具体哪一种异常情况),断言必须写得比平时复杂</li><li>然后你会希望把断言条件也写进数据文件里,一种格式,eval后到处通用</li><li>然后数据文件的格式会变得远比上面的示例复杂</li><li>然后你会准备一键生成模板的脚本,批量修改的脚本,封装Newman的脚本,一个框架的雏形……别问我怎么知道的</li></ul> <p>然后你回过头发现,一开始不用数据文件不就省事多了?!</p> <div class="csdn-video-box"> <iframe id="1dWzk112-1699269857650" frameborder="0" src="https://player.bilibili.com/player.html?aid=420320293" allowfullscreen="true" data-mediaembed="bilibili"></iframe> <p>Python接口自动化测试零基础入门到精通(2023最新版)</p> </div> <p></p> </div> </div> </li> <li class="list-group-item ul-li"> <b>相关阅读:</b><br> <nobr> <a href="/Article/Index/1562855">RabbitMQ和Minio实现头像存储</a> <br /> <a href="/Article/Index/1887166">老电脑不能装纯净版windows</a> <br /> <a href="/Article/Index/761802">js 图形操作一(兼容pc、移动端实现 draggable属性 拖放效果)</a> <br /> <a href="/Article/Index/637514">Java程序员进阶全过程</a> <br /> <a href="/Article/Index/779531">【Unity入门计划】GameObject类(1)-GetComponent方法</a> <br /> <a href="/Article/Index/818750">Java线程池-异步任务编排</a> <br /> <a href="/Article/Index/909290">JS前端开发框架常用的有哪些?</a> <br /> <a href="/Article/Index/1567341">python笔记之面向对象</a> <br /> <a href="/Article/Index/1514406">软考69-上午题-【面向对象技术2-UML】-关系</a> <br /> <a href="/Article/Index/1349052">数据结构 | (一)包装类和初始泛型</a> <br /> </nobr> </li> <li class="list-group-item from-a mb-2"> 原文地址:https://blog.csdn.net/m0_68405758/article/details/134253053 </li> </ul> </div> <div class="col-lg-4 col-sm-12"> <ul class="list-group" style="word-break:break-all;"> <li class="list-group-item ul-li-bg" aria-current="true"> 最新文章 </li> <li class="list-group-item ul-li"> <nobr> <a href="/Article/Index/1484446">攻防演习之三天拿下官网站群</a> <br /> <a href="/Article/Index/1515268">数据安全治理学习——前期安全规划和安全管理体系建设</a> <br /> <a href="/Article/Index/1759065">企业安全 | 企业内一次钓鱼演练准备过程</a> <br /> <a href="/Article/Index/1485036">内网渗透测试 | Kerberos协议及其部分攻击手法</a> <br /> <a href="/Article/Index/1877332">0day的产生 | 不懂代码的"代码审计"</a> <br /> <a href="/Article/Index/1887576">安装scrcpy-client模块av模块异常,环境问题解决方案</a> <br /> <a href="/Article/Index/1887578">leetcode hot100【LeetCode 279. 完全平方数】java实现</a> <br /> <a href="/Article/Index/1887512">OpenWrt下安装Mosquitto</a> <br /> <a href="/Article/Index/1887520">AnatoMask论文汇总</a> <br /> <a href="/Article/Index/1887496">【AI日记】24.11.01 LangChain、openai api和github copilot</a> <br /> </nobr> </li> </ul> <ul class="list-group pt-2" style="word-break:break-all;"> <li class="list-group-item ul-li-bg" aria-current="true"> 热门文章 </li> <li class="list-group-item ul-li"> <nobr> <a href="/Article/Index/888177">十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!</a> <br /> <a href="/Article/Index/797680">奉劝各位学弟学妹们,该打造你的技术影响力了!</a> <br /> <a href="/Article/Index/888183">五年了,我在 CSDN 的两个一百万。</a> <br /> <a href="/Article/Index/888179">Java俄罗斯方块,老程序员花了一个周末,连接中学年代!</a> <br /> <a href="/Article/Index/797730">面试官都震惊,你这网络基础可以啊!</a> <br /> <a href="/Article/Index/797725">你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法</a> <br /> <a href="/Article/Index/797702">心情不好的时候,用 Python 画棵樱花树送给自己吧</a> <br /> <a href="/Article/Index/797709">通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!</a> <br /> <a href="/Article/Index/797716">13 万字 C 语言从入门到精通保姆级教程2021 年版</a> <br /> <a href="/Article/Index/888192">10行代码集2000张美女图,Python爬虫120例,再上征途</a> <br /> </nobr> </li> </ul> </div> </div> </div> <!-- 主体 --> <!--body结束--> <!--这里是footer模板--> <!--footer--> <nav class="navbar navbar-inverse navbar-fixed-bottom"> <div class="container"> <div class="row"> <div class="col-md-12"> <div class="text-muted center foot-height"> Copyright © 2022 侵权请联系<a href="mailto:2656653265@qq.com">2656653265@qq.com</a>    <a href="https://beian.miit.gov.cn/" target="_blank">京ICP备2022015340号-1</a> </div> <div style="width:300px;margin:0 auto; padding:0px 5px;"> <a href="/regex.html">正则表达式工具</a> <a href="/cron.html">cron表达式工具</a> <a href="/pwdcreator.html">密码生成工具</a> </div> <div style="width:300px;margin:0 auto; padding:5px 0;"> <a target="_blank" href="http://www.beian.gov.cn/portal/registerSystemInfo?recordcode=11010502049817" style="display:inline-block;text-decoration:none;height:20px;line-height:20px;"> <img src="" style="float:left;" /><p style="float:left;height:20px;line-height:20px;margin: 0px 0px 0px 5px; color:#939393;">京公网安备 11010502049817号</p></a> </div> </div> </div> </div> </nav> <!--footer--> <!--footer模板结束--> <script src="/js/plugins/jquery/jquery.js"></script> <script src="/js/bootstrap.min.js"></script> <!--这里是scripts模板--> <!--scripts模板结束--> </body> </html>