node.js中express框架的基本使用

`express是一个基于node.js平台的,快速,开放,极简的web开发框架。 一、安装 express npm install express --save 二、简单使用 express //引入express const express = require('express'); //创建一个应用 let app = express(); //匹配GET请求路径设置回调函数 app.get('/hello', function (req, res) { res.end('hello'); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 通过访问 localhost:8888/hello 我们就可以看到内容输出了。 当然 express 还支持其他的一些请求方法,比如 app.post(),app.put(),app.delete(),app.head() 等。 //引入express const express = require('express'); //创建一个应用 let app = express(); //匹配POST请求 app.post('/hello', function (req, res) { res.end('post hello'); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 如果我们想要匹配所有的请求路径,可以使用通配符 * 号。 //引入express const express = require('express'); //创建一个应用 let app = express(); app.get('/hello', function (req, res) { res.end('hello'); }); //*号匹配所有路径 app.get('*', function (req, res) { res.end('not found'); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); express 还提供了 all() 方法,可以匹配所有请求方法。 //引入express const express = require('express'); //创建一个应用 let app = express(); //匹配所有请求方法 app.all('/hello', function (req, res) { res.end('all hello'); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 三、express 中间件的概念 express中间件就是处理http请求的函数,用来完成一些特定的操作,比如登陆检查,权限控制等等。 1、一个中间件处理完请求和响应,可以把数据传递给下一个中间件。 2、在回调函数中使用 next(),就可以让请求继续向下传递。 3、通过不同路径,分别执行不同的中间件。 我们可以使用 use() ,在路由数组中添加一个中间件。注意我们设置的路由路径最终会存放在一个数组里,由上到下的把路径加入这个数组中。 //引入express const express = require('express'); //创建一个应用 let app = express(); //如果没有设置路径,则会匹配全部 app.use(function (req, res, next) { console.log('匹配全部路径'); //注意这里如果不调用next(),则请求并不会向下传递。 next(); }); app.use('/hello', function (req, res, next) { console.log('use hello'); next(); }); app.get('/hello', function (req, res, next) { console.log('get hello'); next(); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); next() 函数可以传入一个参数,表示错误信息,默认将执行错误中间件。 //引入express const express = require('express'); //创建一个应用 let app = express(); app.get('/hello', function (req, res, next) { console.log('get hello'); next('error!!!'); }); //注意错误处理中间件的参数是4个 app.use(function (err, req, res, next) { console.log(err); res.end(err); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 四、express中的request对象 在express中对原生的req请求对象进行了扩展,添加了一些属性和方法。 //引入express const express = require('express'); //创建一个应用 let app = express(); app.get('/hello', function (req, res, next) { //主机名 res.write('req.hostname : ' + req.hostname + '\r\n'); //请求URL的路径 res.write('req.path : ' + req.path + '\r\n'); //查询字符串对象 res.write('req.query : ' + JSON.stringify(req.query) + '\r\n'); //请求的远程IP地址 res.write('req.ip : ' + req.ip + '\r\n'); //请求方法 res.write('req.method : ' + req.method + '\r\n'); res.end(); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 通过 req.params 获取路径里的参数 //引入express const express = require('express'); //创建一个应用 let app = express(); app.get('/list/:key/:page/:size', function (req, res, next) { //注意,设置了多少参数,地址就需要传入多少参数 res.end(JSON.stringify(req.params)); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 五、express中的response对象 express中也对原生的res对象进行了扩展,添加了一些属性和方法。 const path = require('path'); //引入express const express = require('express'); //创建一个应用 let app = express(); app.get('/send/str', function (req, res, next) { //send方法会自动判断数据类型,并进行相应的head信息设置 //如果参数是字符串,则Content-Type为 text/html res.send('hello, world'); }); app.get('/send/arr', function (req, res, next) { //如果参数是一个数组,则返回json res.send([1, 2, 3]); }); app.get('/send/obj', function (req, res, next) { //如果参数是一个对象,则返回json res.send({name: 'xiaoxu', age: 24}); }); app.get('/send/number', function (req, res, next) { //如果是一个数字,则返回相应状态码短语 res.send(404); }); app.get('/download', function (req, res, next) { //提示下载文件 res.download('./1.txt'); }); app.get('/json', function (req, res, next) { //响应json对象 res.json({name: 'xiaoxu'}); }); app.get('/jsonp', function (req, res, next) { //客户端请求时,需要带上callback=test res.jsonp('hello'); }); app.get('/redirect', function (req, res, next) { //重定向到一个地址 res.redirect('http://www.baidu.com'); }); app.get('/sendfile', function (req, res, next) { //发送一个文件 res.sendFile(path.resolve('./1.txt')); }); app.get('/sendstatus', function (req, res, next) { //发送一个状态码 res.sendStatus(302); }); //监听端口 app.listen(8888, function () { console.log('port : 8888'); }); 六、ejs模板的使用 支持express的模板有很多种,这里我们使用ejs作为模板引擎。 安装ejs: npm install ejs 使用ejs模板 const path = require('path'); const express = require('express'); //创建一个应用 let app = express(); //设置模板引擎 app.set('view engine', 'ejs'); //设置模板目录 app.set('views', path.join(__dirname, 'views')); //监听 app.listen(8888); 如果我们希望,ejs能够渲染html页面,可以使用如下 const path = require('path'); const express = require('express'); let app = express(); //设置视图引擎为html引擎 app.set('view engine', 'html'); //设置视图的路径 app.set('views', path.join(__dirname, 'views')); //配置html引擎 app.engine('html', require('ejs').__express); app.listen(8888); 渲染视图,输出内容。 const path = require('path'); const express = require('express'); let app = express(); app.set('view engine', 'html'); app.set('views', path.join(__dirname, 'views')); app.engine('html', require('ejs').__express); app.get('/hello', function (req, res, next) { //参数一表示模板的名称,会在当前项目目录下的views目录查找 //参数二表示传入模板中的数据 res.render('hello', { name: 'xiaoxu', age: 24 }); }); app.listen(8888); hello.html的代码: <!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title>Document</title> </head> <body> <%= name %> <%= age %> </body> </html> 七、静态文件服务器 有些时候我们需要在页面上加载css,js,img等静态资源,指定存放静态资源的目录,浏览器发出非html文件请求时,服务器会到这个目录下找对应的资源。 const path = require('path'); const express = require('express'); let app = express(); app.set('view engine', 'html'); app.set('views', path.join(__dirname, 'views')); app.engine('html', require('ejs').__express); //注意express.static这个中间件是express内置的 app.use(express.static(path.join(__dirname, 'public'))); app.get('/hello', function (req, res, next) { //参数一表示模板的名称,会在当前项目目录下的views目录查找 //参数二表示传入模板中的数据 res.render('hello', { name: 'xiaoxu', age: 24 }); }); app.listen(8888); 八、使用body-parser中间件解析post过来的数据 安装body-parser npm install body-parser 使用body-parser const path = require('path'); const express = require('express'); const bodyParser = require('body-parser'); let app = express(); app.set('view engine', 'html'); app.set('views', path.join(__dirname, 'views')); app.engine('html', require('ejs').__express); //解析 application/json app.use(bodyParser.json()); //解析 application/x-www-form-urlencoded app.use(bodyParser.urlencoded({extended:false})); app.post('/form', function (req, res) { console.log(req.body); }); app.listen(8888);`

2020年4月14日 · 4 分钟 · 天边的星星

如何用 RSA生成生成公钥私钥(非对称加密)

Java版本 `import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.Signature; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Date; import java.util.HashMap; import java.util.Map; import javax.crypto.Cipher; import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; public class CreateSecretKey { public static final String KEY_ALGORITHM = "RSA"; private static final String PUBLIC_KEY = "RSAPublicKey"; private static final String PRIVATE_KEY = "RSAPrivateKey"; public static final String SIGNATURE_ALGORITHM="MD5withRSA"; /** * RSA最大加密明文大小 */ private static final int MAX_ENCRYPT_BLOCK = 117; /** * RSA最大解密密文大小 */ private static final int MAX_DECRYPT_BLOCK = 128; //获得公钥字符串 public static String getPublicKeyStr(Map<String, Object> keyMap) throws Exception { //获得map中的公钥对象 转为key对象 Key key = (Key) keyMap.get(PUBLIC_KEY); //编码返回字符串 return encryptBASE64(key.getEncoded()); } //获得私钥字符串 public static String getPrivateKeyStr(Map<String, Object> keyMap) throws Exception { //获得map中的私钥对象 转为key对象 Key key = (Key) keyMap.get(PRIVATE_KEY); //编码返回字符串 return encryptBASE64(key.getEncoded()); } //获取公钥 public static PublicKey getPublicKey(String key) throws Exception { byte[] keyBytes; keyBytes = (new BASE64Decoder()).decodeBuffer(key); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); PublicKey publicKey = keyFactory.generatePublic(keySpec); return publicKey; } //获取私钥 public static PrivateKey getPrivateKey(String key) throws Exception { byte[] keyBytes; keyBytes = (new BASE64Decoder()).decodeBuffer(key); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM); PrivateKey privateKey = keyFactory.generatePrivate(keySpec); return privateKey; } //解码返回byte public static byte[] decryptBASE64(String key) throws Exception { return (new BASE64Decoder()).decodeBuffer(key); } //编码返回字符串 public static String encryptBASE64(byte[] key) throws Exception { return (new BASE64Encoder()).encodeBuffer(key); } //***************************签名和验证******************************* public static byte[] sign(byte[] data,String privateKeyStr) throws Exception{ PrivateKey priK = getPrivateKey(privateKeyStr); Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); sig.initSign(priK); sig.update(data); return sig.sign(); } public static boolean verify(byte[] data,byte[] sign,String publicKeyStr) throws Exception{ PublicKey pubK = getPublicKey(publicKeyStr); Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM); sig.initVerify(pubK); sig.update(data); return sig.verify(sign); } //************************加密解密************************** public static byte[] encrypt(byte[] plainText,String publicKeyStr)throws Exception{ PublicKey publicKey = getPublicKey(publicKeyStr); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.ENCRYPT_MODE, publicKey); int inputLen = plainText.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; int i = 0; byte[] cache; while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_ENCRYPT_BLOCK) { cache = cipher.doFinal(plainText, offSet, MAX_ENCRYPT_BLOCK); } else { cache = cipher.doFinal(plainText, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_ENCRYPT_BLOCK; } byte[] encryptText = out.toByteArray(); out.close(); return encryptText; } public static byte[] decrypt(byte[] encryptText,String privateKeyStr)throws Exception{ PrivateKey privateKey = getPrivateKey(privateKeyStr); Cipher cipher = Cipher.getInstance(KEY_ALGORITHM); cipher.init(Cipher.DECRYPT_MODE, privateKey); int inputLen = encryptText.length; ByteArrayOutputStream out = new ByteArrayOutputStream(); int offSet = 0; byte[] cache; int i = 0; // 对数据分段解密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK); } else { cache = cipher.doFinal(encryptText, offSet, inputLen - offSet); } out.write(cache, 0, cache.length); i++; offSet = i * MAX_DECRYPT_BLOCK; } byte[] plainText = out.toByteArray(); out.close(); return plainText; } public static void main(String[] args) { Map<String, Object> keyMap; byte[] cipherText; String input = "Hello World!"; try { keyMap = initKey(); String publicKey = getPublicKeyStr(keyMap); System.out.println("公钥------------------"); System.out.println(publicKey); String privateKey = getPrivateKeyStr(keyMap); System.out.println("私钥------------------"); System.out.println(privateKey); System.out.println("测试可行性-------------------"); System.out.println("明文======="+input); cipherText = encrypt(input.getBytes(),publicKey); //加密后的东西 System.out.println("密文======="+new String(cipherText)); //开始解密 byte[] plainText = decrypt(cipherText,privateKey); System.out.println("解密后明文===== " + new String(plainText)); System.out.println("验证签名-----------"); String str="被签名的内容"; System.out.println("\n原文:"+str); byte[] signature=sign(str.getBytes(),privateKey); boolean status=verify(str.getBytes(), signature,publicKey); System.out.println("验证情况:"+status); } catch (Exception e) { e.printStackTrace(); } } }` Python `import rsa # 生成密钥 (pubkey, privkey) = rsa.newkeys(1024) # 保存密钥 with open('public.pem','w+') as f: f.write(pubkey.save_pkcs1().decode()) with open('private.pem','w+') as f: f.write(privkey.save_pkcs1().decode()) # 导入密钥 with open('public.pem','r') as f: pubkey = rsa.PublicKey.load_pkcs1(f.read().encode()) with open('private.pem','r') as f: privkey = rsa.PrivateKey.load_pkcs1(f.read().encode()) # 明文 message = 'hello' # 公钥加密 crypto = rsa.encrypt(message.encode(), pubkey) # 私钥解密 message = rsa.decrypt(crypto, privkey).decode() print(message) # 私钥签名 signature = rsa.sign(message.encode(), privkey, 'SHA-1') # 公钥验证 rsa.verify(message.encode(), signature, pubkey)`

2020年4月13日 · 3 分钟 · 天边的星星

Koa2 和 Express 中间件对比

koa2 中间件 koa2的中间件是通过 async await 实现的,中间件执行顺序是“洋葱圈”模型。 中间件之间通过next函数联系,当一个中间件调用 next() 后,会将控制权交给下一个中间件, 直到下一个中间件不再执行 next() 后, 将会沿路折返,将控制权依次交换给前一个中间件。 如图: koa2 中间件实例 app.js: `const Koa = require('koa'); const app = new Koa(); // logger app.use(async (ctx, next) => { console.log('第一层 - 开始') await next(); const rt = ctx.response.get('X-Response-Time'); console.log(`<span class="katex math inline">{ctx.method} -----------</span>{ctx.url} ----------- <span class="katex math inline">{rt}`); console.log('第一层 - 结束') }); // x-response-time app.use(async (ctx, next) => { console.log('第二层 - 开始') const start = Date.now(); await next(); const ms = Date.now() - start; ctx.set('X-Response-Time', `</span>{ms}ms`); console.log('第二层 - 结束') }); // response app.use(async ctx => { console.log('第三层 - 开始') ctx.body = 'Hello World'; console.log('第三层 - 结束') }); app.listen(3000); ` 执行app.js后,浏览器访问 http://localhost:3000/text , 控制台输出结果: ...

2020年3月26日 · 3 分钟 · 天边的星星

nohup不起作用?

刚刚出现了一个奇怪的问题,我执行以下命令 后,程序在后台开始执行,但是当我直接关闭终端后,程序在后台停止执行了。网上查了查,以下方法试了试,成功了 nohup命令执行后,不要直接关闭终端,使用exit命令退出会话 mark下。 如果还是解决不了使用 Screen是一款由GNU计划开发的用于命令行终端切换的自由软件 Screen参考:https://blog.csdn.net/han0373/article/details/81352663 https://www.jianshu.com/p/0702a451dd0c

2020年2月13日 · 1 分钟 · 天边的星星

adb常用命令总结

针对移动端 Android 的测试, adb 命令是很重要的一个点,必须将常用的 adb 命令熟记于心, 将会为 Android 测试带来很大的方便,其中很多命令将会用于自动化测试的脚本当中。 Android Debug Bridge adb 其实就是 Android Debug Bridge, Android 调试桥的缩写,adb 是一个 C/S 架构的命令行工具,用于通过电脑端与模拟器或者真实设备交互。在某些特殊的情况下进入不了系统,adb就派上用场啦!主要由 3 部分组成: · 运行在 PC 端的 Client : 可以通过它对 Android 应用进行安装、卸载及调试 · 运行在 PC 端的 Service : 其管理客户端到 Android 设备上 adb 后台进程的连接 adb 服务启动后,Windows 可以在任务管理器中找到 adb.exe 这个进程 · 运行在 Android 设备上的 adb 后台进程 执行 adb shell ps | grep adbd ,可以找到该后台进程,windows 请使用 findstr 替代 grep ...

2020年2月4日 · 2 分钟 · 天边的星星

Android shape/layer-list实现(渐变阴影)效果

知识点: layer-list : 简单来说layer-list就是图层列表的意思,是用来创建LayerDrawable的,LayerDrawable是DrawableResource的一种,所以,layer-list创建出来的是”图层列表”,也就是一个drawable图形 shape:这个老哥说的挺仔细的(https://www.jianshu.com/p/d97fcdde1fc6) 上效果: image.png 直接在drawable文件夹下面创建文件夹gradual.xml__ `<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item> <shape android:shape="rectangle"> <padding android:bottom="3dp" android:left="3dp" android:right="3dp" android:top="3dp" /> <solid android:color="#0DCCCCCC" /> <corners android:radius="10dp" /> </shape> </item> <item> <shape android:shape="rectangle"> <padding android:bottom="3dp" android:left="3dp" android:right="3dp" android:top="3dp" /> <solid android:color="#10CCCCCC" /> <corners android:radius="10dp" /> </shape> </item> <item> <shape android:shape="rectangle"> <padding android:bottom="3dp" android:left="3dp" android:right="3dp" android:top="3dp" /> <solid android:color="#15CCCCCC" /> <corners android:radius="10dp" /> </shape> </item> <item> <shape android:shape="rectangle"> <padding android:bottom="3dp" android:left="3dp" android:right="3dp" android:top="3dp" /> <solid android:color="#20CCCCCC" /> <corners android:radius="10dp" /> </shape> </item> <item> <shape android:shape="rectangle"> <padding android:bottom="3dp" android:left="3dp" android:right="3dp" android:top="3dp" /> <solid android:color="#30CCCCCC" /> <corners android:radius="10dp" /> </shape> </item> <item> <shape> <solid android:color="#FFFFFF" /> <corners android:radius="6dp" /> </shape> </item> </layer-list> ` 在上面代码大概可以理解到,就是用若干个渐浅色的图层叠加在一起,实现渐变的阴影效果,想要效果更好可以把间隔缩小,图层再增加几个就可以了,我觉得上面代码效果就挺好的了,刚好 ...

2020年2月3日 · 1 分钟 · 天边的星星

PHPmailer发送邮件demo

开发系统的时候,我们避免不了要发送邮件,在php中使用phpmailer可以快速的实现邮件的发送 `请自行引用phpmailer模块,放下面这个文件和phpmailer同级 配置号里面的发送邮箱内容,执行php sendMail.php文件就可以测试发送邮件了 这个文件名称为sendMail.php <?php use phpmailer\phpmailer\PHPMailer; use phpmailer\phpmailer\Exception; //引入项目 require './phpmailer/src/Exception.php'; require './phpmailer/src/PHPMailer.php'; require './phpmailer/src/SMTP.php'; //实例化PHPMail类 <span class="katex math inline">mail = new PHPMailer(true); try { //Server settings</span>mail->SMTPDebug = 2; <span class="katex math inline">mail->isSMTP();</span>mail->Host = 'smtp.163.com'; <span class="katex math inline">mail->SMTPAuth = true;</span>mail->Username = 'hn_zhangdl@163.com'; <span class="katex math inline">mail->Password = 'xxxx';#跟上边一样的授权码</span>mail->SMTPSecure = 'ssl'; <span class="katex math inline">mail->Port = 994;</span>mail->CharSet='UTF-8'; //发件人 <span class="katex math inline">mail->setFrom('hn_zhangdl@163.com', 'Mailer'); //收件人。多收件人可设置多个addAddress</span>mail->addAddress('411437734@qq.com', 'zhangdl');//收件人邮箱地址,收件人姓名(选填) //发送附件 //#<span class="katex math inline">mail->addAttachment('/var/tmp/file.tar.gz'); // 添加附件 //#</span>mail->addAttachment('/tmp/image.jpg', 'new.jpg'); // 设置附件以及附件名称 //邮件内容 <span class="katex math inline">mail->isHTML(true); // 发送html格式邮件</span>mail->Subject = '标题'; //邮件标题 <span class="katex math inline">mail->Body = '邮件测试内容 hello.';</span>mail->send(); echo 'Message has been sent'; } catch (Exception <span class="katex math inline">e) { echo 'Message could not be sent. Mailer Error: ',</span>mail->ErrorInfo; } ?>`

2020年2月2日 · 1 分钟 · 天边的星星

thymeleaf 解决字符串太长显示问题(截取显示部分字符串)

下面的文章有thymeleaf 拼接字符串的写法和处理字符串太长显示部分的方法 下面的方法是显示指定的字符: ` th:text="${#strings.abbreviate(t.signTitle,25)}" ` 参考:[https://blog.csdn.net/qq_37599827/article/details/93362132](https://blog.csdn.net/qq_37599827/article/details/93362132)

2020年1月28日 · 1 分钟 · 天边的星星

SpringBoot Controller接收参数的几种常用方式

 第一类:请求路径参数 1、@PathVariable 获取路径参数。即url/{id}这种形式。 2、@RequestParam 获取查询参数。即url?name=这种形式 例子 GET http://localhost:8080/demo/123?name=suki_rong 对应的java代码: @GetMapping("/demo/{id}") public void demo(@PathVariable(name = "id") String id, @RequestParam(name = "name") String name) { System.out.println("id="+id); System.out.println("name="+name); } 输出结果: id=123 name=suki_rong 第二类:Body参数 因为是POST请求,这里用Postman的截图结合代码说明 1、@RequestBody 例子: 对应的java代码: @PostMapping(path = "/demo1") public void demo1(@RequestBody Person person) { System.out.println(person.toString()); } 输出结果: name:suki_rong;age=18;hobby:programing 也可以是这样 @PostMapping(path = "/demo1") public void demo1(@RequestBody Map<String, String> person) { System.out.println(person.get("name")); } 输出结果: suki_rong ...

2019年12月15日 · 3 分钟 · 天边的星星

Element-UI 入门使用

Element-UI 入门使用 Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库 [Element UI官网][1] 如何在Html文件中使用ElementUI 在网页中引入element ui css和js ,由于Element是具有Vue的,所以需引入Vue的js > > - > > > <script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script> > > > <!-- 引入组件库 --> > > 在Script中创建Vue对象管理界面元素 > ``` &lt;script&gt; new Vue({ el: &#39;#app&#39;, data: function () { return { visible: false } }, }); &lt;/script&gt; 3. 接下来就可以使用Element中的元素了 > ``` `&lt;div id="app"&gt; &lt;el-button @click="visible = true"&gt;Button&lt;/el-button&gt; &lt;el-dialog :visible.sync="visible" title="Hello World"&gt; &lt;p&gt;Try Element&lt;/p&gt; &lt;/el-dialog&gt; &lt;/div&gt; ` 完整示例 > ``` `<!DOCTYPE html> <html lang=“en”> ...

2019年11月8日 · 3 分钟 · 天边的星星