0822 NOTE

0822 NOTE

资料参考:

Express 框架的使用

Node之中间件

nodejs学习之中间件

nodejs的中间件

1、中间件就是一种功能的封装方式,就是封装在程序中处理http请求的功能,

2、中间件是在管道中执行

3、中间件有一个next()函数,如果不调用next函数,请求就在这个中间件中终止

4、中间件和路由处理器的参数中都有回调函数,这个函数有2,3,4个参数

​ 如果有两个参数就是req和res;

​ 如果有三个参数就是req,res和next

​ 如果有四个参数就是err,req,res,next

5、如果不调用next ,管道就会终止,不会再有处理器做后续响应,应该向客户端发送一个响应

6、如果调用了next,不应该发送响应到客户端,如果发送了,则后面发送的响应都会被忽略

7、中间件的第一个参数可以是路径,如果忽略则全部都匹配

中间件的书写方法

function middleware(req,res,next){next()}

在Express框架中,使用应用程序实例对象的use方法调用中间件

app.use([path],function)

use方法中,使用两个参数,其中path参数为可选参数,function参数为必须指定参数。path参数值为一个字符串,用于指定何种路径应用中间件,默认参数值为“/”。function参数值为一个函数,用于指定我们所要调用的中间件函数。

中间件的基本使用

// 引入 express 框架
const express = require('express')

// 创建网站服务器
const app = express();

app.get('/request', (req, res, next) => {
    req.name = 'zhangsan';
    next(); // 向下执行
})

app.get('/request', (req, res)=> {
    res.send(req.name);
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

app.use中间件用法

// 引入 express 框架
const express = require('express')

// 创建网站服务器
const app = express();

// 接收所有的请求的中间件
app.use((req, res, next) => {
    console.log('请求走了 app.use 中间件');
    //next();
})

// 当客户端访问 /request 请求的时候走当前中间件
app.use('/request', (req, res, next) => {
    console.log('请求走了 app.use /request 中间件 ');
    //next();
})

app.get('/request', (req, res, next) => {
    req.name = 'zhangsan';
    next();
})

app.get('/request', (req, res)=> {
    res.send(req.name);
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

中间件应用

路由保护

客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。

// 引入 express 框架
const express = require('express')

// 创建网站服务器
const app = express();

app.use('/admin', (req, res, next) => {
    // 用户没有登录
    let isLogin = false;
    // 如果用户登录,true则登录
    if (isLogin) {
        // 让请求继续向下执行
        next();
    } else {
        // 如果用户没有登录,直接对客户端作出响应
        res.send('您还没有登录,无法访问当前页面');
    }
})


app.get('/admin', (req, res) => {
    res.send('您已登录 可以访问当前页面')
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

网站维护公告

在所有路由最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中。

// 引入 express 框架
const express = require('express')

// 创建网站服务器
const app = express();

app.use((req, res, next) => {
    res.send('当前网站正在维护...');
})

app.use('/admin', (req, res, next) => {
    // 用户没有登录
    let isLogin = true;
    // 如果用户登录
    if (isLogin) {
        // 让请求继续向下执行
        next();
    } else {
        // 如果用户没有登录,直接对客户端作出响应
        res.send('您还没有登录,无法访问当前页面');
    }
})


app.get('/admin', (req, res) => {
    res.send('您已登录 可以访问当前页面')
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

自定义404页面
// 引入 express 框架
const express = require('express')

// 创建网站服务器
const app = express();

app.use('/admin', (req, res, next) => {
    // 用户没有登录
    let isLogin = true;
    // 如果用户登录
    if (isLogin) {
        // 让请求继续向下执行
        next();
    } else {
        // 如果用户没有登录,直接对客户端作出响应
        res.send('您还没有登录,无法访问当前页面');
    }
})


app.get('/admin', (req, res) => {
    res.send('您已登录 可以访问当前页面')
})


app.use((req, res, next) => {
    // 为客户端响应404状态码以及提示信息
    res.status(404).send('当前访问的页面是不存在的');
})
// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

错误中间件的处理

在程序执行的过程中,不可避免的会出现一些无法预料的错误,比如读取文件失败,数据库连接失败。

错误处理中间件是一个集中处理错误的地方

显示服务器错误

app.use((err, req, res, next) => {
    res.status(500).send('服务器发生错误');
})

举例

// 引入 express 框架
const express = require('express')

// 创建网站服务器
const app = express();

app.get('/index', (req, res) => {
    // 创建一个错误实例并抛出
    throw new Error('程序发生了未知错误');
})

// 错误处理中间件
app.use((err, req, res, next) => {
    res.status(500).send(err.message);
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

当程序出现错误时,调用 next()方法,并且将错误信息通过参数的形式传递给 next()方法,即可触发错误处理中间件。

// 引入 express 框架
const express = require('express')

// 引入文件模块
const fs = require('fs');

// 创建网站服务器
const app = express();

app.get('/index', (req, res, next) => {
    // 创建一个错误实例并抛出
    // throw new Error('程序发生了未知错误');
    fs.readFile('./demo.txt', 'utf8', (err, result) => {
        if (err != null) {
            // 文件读取失败 向下传递错误对象
            next(err);
        } else {
            // 文件读取成功
            res.send(result);
        }
    })
    // res.send('程序正常')
})

// 错误处理中间件
app.use((err, req, res, next) => {
    res.status(500).send(err.message);
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

//demo.txt 的文件时不存在的,读取失败后会将错误对象通过 next ()方法传递给错误处理中间件,显示相应的错误信息。
捕获错误

在 node.js中,异步API的错误信息都是通过回调函数获取的,支持 Promise 对象的异步API发生错误可以通过catch方法捕获。

try catch 可以捕获异步函数以及其他同步代码执行过程中发生的错误,但是不能捕获其他类型的错误(回调函数的错误、Promise对象的错误)。

// 引入 express 框架
const express = require('express')

// 引入文件模块
const fs = require('fs');

const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);

// 创建网站服务器
const app = express();

app.get('/index', async (req, res, next) => {
    try {
        await readFile('./aaa.js');
    } catch (ex) {
        // 传递错误信息
        next(ex);
    }
})

// 错误处理中间件
app.use((err, req, res, next) => {
    res.status(500).send(err.message);
})

// 监听端口
app.listen(3000);
console.log('网站服务器启动成功');

express请求处理

构建模块化路由

基础语法:

// 引入 express 框架
const express = require('express');
// 创建网站服务器
const app = express();
// 创建路由对象
const home = express.Router();
// 为路由对象匹配请求路径
app.use('/home', home);
// 创建二级路由
home.get('/index', (req, res) => {
    res.send('欢迎来到博客首页');
})

// 监听端口
app.listen(3000);
console.log('服务器开启成功');

模块化路由构建案例

在 入口文件 app.js 文件的同级目录新建 route 文件夹在 route 文件夹下 新建 home.js 文件 以及 admin.js 文件

  1. 在 route 文件夹中构建不同的路由模块,放在不同的文件中
  2. 通过module.exports 将不同路由模块的路由对象导出
  3. 在 app.js 文件中通过 require 将不同路由模块的路由对象导入,同时对导入的路由进行路由匹配
//home.js
const express = require('express');

const home = express.Router();

home.get('/index', (req, res) => {
    res.send('欢迎来到博客首页页面');
});

// 导出 home 这个路由对象
module.exports = home;

//admin.js
const express = require('express');

const admin = express.Router();

admin.get('/index', (req, res) => {
    res.send('欢迎来到博客管理页面');
});

// 导出 admin 这个路由对象
module.exports = admin;

//app.js
// 引入 express 框架
const express = require('express');

// 创建网站服务器
const app = express();

const home = require('./route/home');
const admin = require('./route/admin');

app.use('/home', home);
app.use('/admin', admin);

// 监听端口
app.listen(3000);
console.log('服务器开启成功');

GET参数的获取

Express 框架中使用 req.query 即可 获取GET参数 ,框架内部会将GET参数转换为对象并返回。

// 引入 express 框架
const express = require('express');
// 创建网站服务器
const app = express();

app.get('/index', (req, res) => {
    // req.query 获取请求参数
    res.send(req.query)
})

// 监听端口
app.listen(3000);
console.log('服务器开启成功');

// http://localhost:3000/index?name=kaige&age=20&gender=男

POST参数的请求

Express 中接收 POST请求参数 需要借助第三方包 body-parser


// 引入 express 框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();
// 拦截所有请求
// extended: false  方法内部使用 querystring 模块处理请求参数的格式
// extended: true   方法内部使用第三方模块 qs 来处理请求参数的格式
app.use(bodyParser.urlencoded({extended: false}));

app.post('/add', (req, res) => {
    res.send(req.body);
})

// 监听端口
app.listen(3000);
console.log('服务器开启成功');
<!--post.html-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="http://localhost:3000/add" method="post">
        <input type="text" name="username">
        <input type="password" name="password">
        <input type="submit">
    </form>
</body>
</html>

app.use()的进一步理解

app.use()调用外部定义函数:

// 引入 express 框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();

// 拦截所有的请求
app.use(fn ());

function fn () {
    return function (req, res, next) {
        console.log(req.method);
        next();
    }
}

app.get('/', (req, res) => {
    // 接收 post 请求参数
    res.send('ok');
})

app.post('/add', (req, res) => {
    res.send(req.body);
})

// 监听端口
app.listen(3000);
console.log('服务器开启成功');

优点

可以在调用 fn 函数的同时,向 fn 内部传递一些额外的参数,在请求处理函数内部可以根据这个参数改变请求处理函数的行为。

// 引入 express 框架
const express = require('express');
const bodyParser = require('body-parser');
// 创建网站服务器
const app = express();

app.use(fn ({a: 1}));

function fn (obj) {
    return function (req, res, next) {
        if (obj.a == 1) {
            console.log(req.url);
        } else {
            console.log(req.method);
        }
        next();
    }
}

app.get('/', (req, res) => {
    // 接收 post 请求参数
    res.send('ok');
})

app.post('/add', (req, res) => {
    res.send(req.body);
})


// 监听端口
app.listen(3000);
console.log('服务器开启成功');

SSR

(Server Side Rendering) :传统的渲染方式,由服务端把渲染的完整的页面吐给客户端。这样减少了一次客户端到服务端的一次http请求,加快相应速度,一般用于首屏的性能优化。

SSR优点

例如SEO–因为访问一个请求,返回的就是页面全部的HTML结构,包含所需要呈现的所有数据,于是例如搜索引擎或者爬虫的数据抓取;

目前使用MV*架构的项目,大都是前后端分离,数据都是动态生成,不利于SEO优化 利于首屏渲染性能高–首屏的页面加载来自于服务器,不依赖与服务端的接口请求再数据处理;

SSR缺点

性能全都依赖于服务器 前端界面开发可操作性不高

CSR

CSR(Client Side Rendering):是一种目前流行的渲染方式,它依赖的是运行在客户端的JS,用户首次发送请求只能得到小部分的指引性HTML代码。第二次请求将会请求更多包含HTML字符串的JS文件。

CSR优点

FP最快 客户端体验较好,因为在数据没更新之前,页面框架和元素是可以在dom生成的

FP:仅有一个 div 根节点。以VUE为例,div#app 注册一个空的div FCP:包含页面的基本框架,但没有数据内容。以VUE为例,每个template中的div框架,对应VUE生命周期的mounted FMP:包含页面所有元素及数据。以VUE为例,通过接口更新到页面的数据后完整的页面展示;对应VUE的生命周期中的updated

简而言之,就是数据拼接HTML字符串这件事放在服务端还是客户端造成了两者区别。

CSR缺点

不利于SEO–爬虫数据不好爬呀~~ 整体加载完速度慢

两者有何不同

服务器端渲染的优势在于首屏渲染速度块,简单来讲它不需要来回多次往返于客户端和服务端。但是其性能等众多因素会影响用户体验,比如说:网速,在线活跃人数,服务器的物理位置等等。而客户端渲染则和服务端渲染相反,因为多次和服务器的交互导致首屏加载速度慢。但一旦这些请求完成之后,用户和页面之间的交互时用户体验就会好很多。

用一个现实生活的例子来看:假如从超市买东西吃,以SSR的角度来看,你每次在超市买完随即吃完再走,每次饿了都需要出发去超市。而从CSR的角度来看,就是你从超市购买许多原材料再拿回家去自己煮,多了能放冰箱,这样每次肚子饿了就不需要每次都往超市跑,唯一麻烦一点在于你得花时间挑选食材。

优化方案

1、优化首屏加载,减少白屏时间,提升加载性能

2、加速或减少HTTP请求损耗:使用CDN加载公用库,使用强缓存和协商缓存,使用域名收敛,小图片使用Base64代替,使用Get请求代替Post请求,设置 Access-Control-Max-Age 减少预检请求,页面内跳转其他域名或请求其他域名的资源时使用浏览器prefetch预解析等;

(CDN:CDN(Content Delivery Network)是指内容分发网络,也称为内容传送网络)

3、延迟加载:非重要的库、非首屏图片延迟加载,SPA的组件懒加载等;

4、减少请求内容的体积:开启服务器Gzip压缩,JS、CSS文件压缩合并,减少cookies大小,SSR直接输出渲染后的HTML等;

5、浏览器渲染原理:优化关键渲染路径,尽可能减少阻塞渲染的JS、CSS;

6、优化用户等待体验:白屏使用加载进度条、loading图、骨架屏代替等;

强缓存和协商缓存

强缓存是利用http头中的Expires和Cache-Control两个字段来控制的,用来表示资源的缓存时间

协商缓存:浏览器第一次请求一个资源的时候,服务器返回的header中会加上Last-Modify,Last-modify是一个时间标识该资源的最后修改时间;当浏览器再次请求该资源时,request的请求头中会包含If-Modify-Since,该值为缓存之前返回的Last-Modify。服务器收到If-Modify-Since后,根据资源的最后修改时间判断是否命中缓存;其中Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识

Access-Control-Max-Age:缓存可以被缓存的时间

SPA 只有一张Web页面的应用,是加载单个HTML页面并在用户与应用程序交互时动态更新该页面的Web应用程序,是指在浏览器中运行的应用,在使用期间不会重新加载页面。像所有的应用一样,它旨在帮助用户完成任务,比如“编写文档”或者“管理Web服务器”

域名发散

域名发散就是为了突破浏览器对于同一域名并发请求数的限制,使用域名发散为同一个服务申请多个域名,从而可以一定程度上提高并发量;当然,由于建立新的请求需要一定的代价,因此需要在域名发散与域名收敛间进行trade off,通常发散的域名个数为2-4个;

域名收敛

域名收敛就是将静态资源放在一个域名下不进行发散,这主要是为了适应移动端的发展需求;通常DNS是一个开销较大的操作,而移动端由于网络带宽和实时性、资源等的限制,这些开销对移动端的用户体验是致命的,因此需要进行域名收敛;

域名发散是pc端为了利用浏览器的多线程并行下载能力。而域名收敛多用与移动端,提高性能,因为dns解析是是从后向前迭代解析,如果域名过多性能会下降,增加DNS的解析开销。

DNS 预解析:浏览器会在加载网页时对网页中的域名进行解析缓存,这样在你单击当前网页中的连接时就无需进行DNS的解析,减少用户等待时间,提高用户体验。

<link rel="dns-prefetch" href="www.ytuwlg.iteye.com" />

Gzip页面压缩,像服务器发送压缩文件,同时服务器需要设置解析

简而言之,SSR强在首屏渲染。而CSR强在用户和页面多交互的场景

nodejs - artTemplate模板

参考资料:

nodejs - artTemplate模板

1. 模板引擎的基础概念

1.1 模板引擎

模板引擎是第三方模块。

让开发者以更加友好的方式拼接字符串,使项目代码更加清晰、更加易于维护。

 // 未使用模板引擎的写法
 var ary = [{ name: '张三', age: 20 }];
 var str = '<ul>';
 for (var i = 0; i < ary.length; i++) { 
    str += '<li>\
        <span>'+ ary[i].name +'</span>\
        <span>'+ ary[i].age +'</span>\
    </li>';
 }
 str += '</ul>'; 
 <!-- 使用模板引擎的写法 --> 
 <ul>
    {{each ary}}
        <li>{{$value.name}}</li>
        <li>{{$value.age}}</li>
    {{/each}}
 </ul>
1.2 art-template模板引擎

1.在命令行工具中使用 npm install art-template 命令进行下载

2.使用const template = require**(‘art-template’)**引入模板引擎

3.告诉模板引擎要拼接的数据和模板在哪 const html = template(‘模板路径’, 数据);

4.使用模板语法告诉模板引擎,模板与数据应该如何进行拼接

1.3 art-template代码示例
const template = require('art-template');
const path = require('path');

const views = path.join(__dirname, 'views', '01.art');
const html = template(views, {
    name:'张三',
    age:'18'
});

console.log(html);
 <div>
    <span>{{data.name}}</span>
    <span>{{data.age}}</span>
 </div>

2. 模板引擎的语法

2.1 模板语法
  • art-template同时支持两种模板语法:标准语法原始语法
  • 标准语法可以让模板更容易读写,原始语法具有强大的逻辑处理能力。

标准语法: {{ 数据 }}

原始语法:<%=数据 %>

2.2 输出

将某项数据输出在模板中,标准语法和原始语法如下:

  • 标准语法:{{ 数据 }}
  • 原始语法:<%=数据 %>
  <!-- 标准语法 -->
 <h2>{{value}}</h2>
 <h2>{{a ? b : c}}</h2>
 <h2>{{a + b}}</h2>

  <!-- 原始语法 -->
 <h2><%= value %></h2>
 <h2><%= a ? b : c %></h2>
 <h2><%= a + b %></h2>
2.3 原文输出

如果数据中携带HTML标签,默认模板引擎不会解析标签,会将其转义后输出。

  • 标准语法:{{@ 数据 }}
  • 原始语法:<%-数据 %>
 <!-- 标准语法 -->
 <h2>{{@ value }}</h2>
 <!-- 原始语法 -->
 <h2><%- value %></h2>
2.4 条件判断
 <!-- 标准语法 --> 
 {{if 条件}} ... {{/if}}
 {{if v1}} ... {{else if v2}} ... {{/if}}
 <!-- 原始语法 -->
 <% if (value) { %> ... <% } %>
 <% if (v1) { %> ... <% } else if (v2) { %> ... <% } %>
2.5 循环
  • 标准语法:{{each 数据}} {{/each}}
  • 原始语法:<% for() { %> <% } %>
 <!-- 标准语法 -->
 {{each target}}
     {{$index}} {{$value}}
 {{/each}}
  <!-- 原始语法 -->
 <% for(var i = 0; i < target.length; i++){ %>
     <%= i %> <%= target[i] %>
 <% } %>

示例:

const template = require('art-template');
const path = require('path');

const views = path.join(__dirname, 'views', '03.art');
const html = template(views, {
    users:[
        {
            name:'li',
            age:18
            
        },{
            name:'wang',
            age:20
            
        },{
            name:'zhang',
            age:30
            
        }
    ]
});

console.log(html);
<ul>
    {{each users}}
    <li>
        {{$index}}
        {{$value.name}}
        {{$value.age}}
    </li>
    {{/each}}
</ul>
2.6 子模版

使用子模板可以将网站公共区块(头部、底部)抽离到单独的文件中。

  • 标准语法:{{include ‘模板’}}
  • 原始语法:<%include(‘模板’) %>
  <!-- 标准语法 -->
 {{include './header.art'}}
  <!-- 原始语法 -->
 <% include('./header.art') %>
2.7 模板继承

使用模板继承可以将网站HTML骨架抽离到单独的文件中,其他页面模板可以继承骨架文件。

img

模板继承示例
 <!doctype html>
 <html>
     <head>
         <meta charset="utf-8">
         <title>HTML骨架模板</title>
         {{block 'head'}}{{/block}}
     </head>
     <body>
         {{block 'content'}}{{/block}}
     </body>
 </html>
 <!--index.art 首页模板-->
 {{extend './layout.art'}}
 {{block 'head'}} <link rel="stylesheet" href="custom.css"> {{/block}}
 {{block 'content'}} <p>This is just an awesome page.</p> {{/block}}
2.8 模板配置
  1. 向模板中导入变量 template.defaults.imports.变量名 = 变量值;
  2. 设置模板根目录 template.defaults.root = 模板目录
  3. 设置模板默认后缀 template.defaults.extname = ‘.art’

CMS案例

<!--client-->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form>
       <div>
        <label>title:</label><input type="text" name="title">
       </div>
       <div>
        <label>icon:</label><input type="file" name="file">
       </div>
       <div>
        <label>price:</label><input type="text" name="price">
       </div>
       <div>
        <label>num:</label><input type="text" name="num">
       </div>
       <div>
        <label>production:</label><input type="text" name="production">
       </div>
       <button type="submit">提交</button>
    </form>
    <script>
var form;
init();
function init(){
   form=document.querySelector("form");
   form.addEventListener("submit",submitHandler);
}

function submitHandler(e){
    e.preventDefault();
    var xhr=new XMLHttpRequest()
    xhr.open("POST","http://10.9.23.91:4010");
    xhr.addEventListener("load",loadHandler);
    xhr.send(new FormData(form));
}

function loadHandler(e){

}
    </script>
</body>
</html>
//app.js
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var logger = require('morgan');
var multer=require("multer")();


var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');

var app = express();

// view engine setup
app.engine('art', require('express-art-template'));
app.set("views",path.join(__dirname,"./views"));
app.set("view engine","art");

app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);

// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

// error handler
app.use(function(err, req, res, next) {
  // set locals, only providing error in development
  res.locals.message = err.message;
  res.locals.error = req.app.get('env') === 'development' ? err : {};

  // render the error page
  res.status(err.status || 500);
  res.render('error');
});

module.exports = app;

//package.json
{
  "name": "server",
  "version": "0.0.0",
  "private": true,
  "main": "./bin/www",
  "scripts": {
    "start": "cross-env PORT=4010 nodemon"
  },
  "devDependencies": {
    "cross-env": "~7.0.3",
    "nodemon": "~2.0.12"
  },
  "dependencies": {
    "art-template": "~4.13.2",
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "ejs": "~2.6.1",
    "express": "~4.16.1",
    "express-art-template": "~1.0.1",
    "http-errors": "~1.6.3",
    "morgan": "~1.9.1",
    "multer": "^1.4.3",
    "multiparty": "^4.2.2"
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

菜鸟小胖砸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值