koa

基于NodeJS平台的web开发框架

特点:轻量,语法新(es6)

安装

koa-generator 快速生成项目

  1. //2.x版本
  2. npm i -g koa-generator
  1. //生成项目
  2. //-e 增加ejs引擎支持
  3. koa2 -e koa2-demo
  1. //安装依赖
  2. npm i
  1. //项目启动
  2. npm start
  1. //测试是否启动成功
  2. http://localhost:3000/

ctx

该参数包含请求和响应的信息

  1. //测试
  2. //src > routes > index.js
  3. router.get('/asyncTest', async (ctx) => {
  4. console.log('start', new Date().getTime());
  5. const p = await new Promise((resolve, reject) => {
  6. setTimeout(() => {
  7. console.log('async test', new Date().getTime());
  8. resolve('test');
  9. }, 1000)
  10. });
  11. ctx.body = {
  12. p
  13. }
  14. });
  15. //打印
  16. { "p": "test" }

中间件

什么是洋葱模型?

koa&koa2&SSR - 图1

现在假想,你手里有一支牙签,横向穿过一个洋葱,是不是会层层穿透?从第一层进去、到第二层、第三次…然后到中间层后,再层层穿透的出,从第三层出、第二层、第一层…。其实我们的koa2中间件执行顺序也是这样的。抛开业务代码,用koa2官网的一个例子做实验

  1. const Koa = require('koa');
  2. const app = new Koa();
  3. // logger
  4. app.use(async (ctx, next) => {
  5. console.log('第一层洋葱 - 开始')
  6. await next();
  7. const rt = ctx.response.get('X-Response-Time');
  8. console.log(`${ctx.method} ${ctx.url} - ${rt}`);
  9. console.log('第一层洋葱 - 结束')
  10. });
  11. // x-response-time
  12. app.use(async (ctx, next) => {
  13. console.log('第二层洋葱 - 开始')
  14. const start = Date.now();
  15. await next();
  16. const ms = Date.now() - start;
  17. ctx.set('X-Response-Time', `${ms}ms`);
  18. console.log('第二层洋葱 - 结束')
  19. });
  20. // response
  21. app.use(async ctx => {
  22. console.log('第三层洋葱 - 开始')
  23. ctx.body = 'Hello World';
  24. console.log('第三层洋葱 - 结束')
  25. });
  26. app.listen(8000);

打印结果是:

  1. 第一层洋葱 - 开始
  2. 第二层洋葱 - 开始
  3. 第三层洋葱 - 开始
  4. 第三层洋葱 - 结束
  5. 第二层洋葱 - 结束
  6. 第一层洋葱 - 结束
  1. //src > app.js
  2. //中间件引入
  3. const views = require('koa-views')
  4. const json = require('koa-json')
  5. const onerror = require('koa-onerror')
  6. const bodyparser = require('koa-bodyparser')
  7. //中间件注册
  8. app.use(bodyparser({
  9. enableTypes:['json', 'form', 'text']
  10. }))
  11. app.use(json())
  12. app.use(logger())
  13. app.use(require('koa-static')(__dirname + '/public'))
  14. app.use(views(__dirname + '/views', {
  15. extension: 'ejs'
  16. }))

手写中间件

  1. //src > app.js
  2. //引入和注册
  3. const midtest = require('./middleware/koa-mid');
  4. app.use(midtest());
  1. //src > middleware > koa-mid.js
  2. //中间件会打印两次
  3. function mid(ctx) {
  4. //打印页面路径
  5. console.log('midtest', ctx.path);
  6. }
  7. module.exports = function () {
  8. return async function (ctx, next) {
  9. mid(ctx);
  10. await next();
  11. }
  12. }

路由

  1. //src > routes > users.js
  2. //前缀 prefix()
  3. router.prefix('/users')
  4. router.get('/', function (ctx, next) {
  5. ctx.body = 'this is a users response!'
  6. })
  7. //访问方法:
  8. //http://localhost:3000/users/

cookie/session

  1. router.get('/', async (ctx, next) => {
  2. ctx.cookies.set('testid', Math.random());
  3. await ctx.render('index', {
  4. title: 'Hello Koa 2!'
  5. })
  6. })
  7. //浏览器application里cookies显示字段name和值

案例

美团网页版项目

技术:vue + koa2 + ElementUI + Redis + MongoDB + SSR + NuxtJS

项目创建

  1. npx create-nuxt-app@2 meituan-demo

项目选择配置

  1. UI: element
  2. server: koa
  3. modules: axios
  4. render: universal(ssr)

启动项目

  1. npm run dev
  2. //访问
  3. http://localhost:3000/

目录结构

  1. ├─nuxt.config.js -配置文件
  2. ├─package-lock.json
  3. ├─package.json
  4. ├─README.md
  5. ├─store -管理vuex状态
  6. ├─static -管理图标(非必须)
  7. | ├─favicon.ico
  8. ├─server -管理服务端代码
  9. | index.js
  10. ├─plugins -管理插件
  11. | ├─element-ui.js
  12. ├─pages
  13. | ├─about.vue
  14. | ├─index.vue
  15. ├─middleware -管理中间件
  16. ├─layouts -管理默认模板
  17. | ├─default.vue
  18. ├─components -管理组件
  19. | ├─Logo.vue
  20. ├─assets -管理静态资源文件
  21. | ├─css

创建.babelrc配置文件

  1. //配置babel
  2. {
  3. "presets": ["es2015"]
  4. }

安装babel处理es6语法

  1. npm i -D babel-preset-es2015@6.24.1
  2. npm i -D babel-cli@6.26.0
  3. npm i -D babel-core@6.26.3

安装scss处理器

  1. npm i -D sass-loader@7.1.0
  2. npm i -D node-sass@4.11.0

配置nuxt.config.js

  1. //引入初始化css文件
  2. css: [
  3. 'element-ui/lib/theme-chalk/reset.css',
  4. 'element-ui/lib/theme-chalk/index.css'
  5. '@/assets/css/main.css'
  6. ],

编写后台接口

  1. //server目录新建dbs数据库目录
  2. //dbs数据库目录里创建models模型目录
  3. //server > dbs > models模型目录创建users.js文件
  4. //server > dbs目录下创建config.js配置文件(数据库相关信息)
  5. //server目录新建interface接口目录
  6. //interface目录新建utils工具目录
  7. //server > interface新建users.js文件
  8. //server > interface > utils新建axios.js文件(封装全局使用axios)
  9. //server > interface > utils新建passport.js文件(验证相关权限)
  1. //POP3邮箱开启授权码
  2. adgkisdouidsbhda
  3. //IMAP/SMTP服务授权码
  4. pthqrkenxqzdbhji
  1. //server > dbs > config.js
  2. //数据库相关信息
  3. export default {
  4. dbs: 'mongodb://127.0.0.1:27017/meituan/users',
  5. redis: {
  6. //主机地址,只读
  7. get host() {
  8. return '127.0.0.1';
  9. },
  10. get port() {
  11. //端口号
  12. return 6379;
  13. }
  14. },
  15. //腾讯提供的服务 邮箱
  16. smtp: {
  17. get host() {
  18. return 'smtp.qq.com'
  19. },
  20. get user() {
  21. return '273122188@qq.com'
  22. },
  23. get pass() {
  24. return '保密字符';
  25. },
  26. get code() {
  27. return () => {
  28. //返回随机16进制字符
  29. return Math.random().toString(16).slice(2, 6).toUpperCase();
  30. }
  31. },
  32. //过期时间
  33. get expire() {
  34. return () => {
  35. return new Date().getTime() + 60 * 1000
  36. }
  37. }
  38. }
  39. }

关于nodemailer

https://nodemailer.com/about/

是一个用于 Node.js 应用程序的模块(中间件),它轻松地发送电子邮件

  1. //安装nodemailer
  2. npm i -S nodemailer@6.1.0
  3. //引入
  4. import nodeMailer from 'nodemailer';
  5. //接口内部使用
  6. let transporter = nodeMailer.createTransport({
  7. host: EmailConfig.smtp.host,
  8. port: 587,
  9. secure: false,
  10. auth: {
  11. user: EmailConfig.smtp.user,
  12. pass: EmailConfig.smtp.pass,
  13. },
  14. });
  1. //安装依赖
  2. npm i -S koa-redis@3.1.3
  3. npm i -S koa-router@7.4.0
  4. npm i -S axios@0.18.0
  5. npm i -S koa-generic-session@2.0.1
  6. npm i -S koa-bodyparser@4.2.1
  7. npm i -S koa-json@2.0.2
  8. npm i -S koa-redis@3.1.2
  9. npm i -S mongoose@5.5.2
  10. npm i -S koa-passport@4.1.1
  11. npm i -S passport-local@1.0.0
  1. //编写后台接口
  2. //server > interface > users.js

关于PassportJs

http://www.passportjs.org/docs/

Node.js 的身份验证中间件。Passport 非常灵活和模块化,可以不显眼地插入任何 基于Express的 Web 应用程序。一套全面策略支持认证使用的用户名和密码

关于CryptoJS

密码加密的前端库

  1. //安装
  2. npm i -S crypto-js@3.1.9-1
  3. //引入
  4. import CryptoJS from 'crypto-js';
  5. //使用
  6. this.$axios.pist("/users/signup", {
  7. username: encodeURIComponent(this.ruleForm.name),
  8. password: CryptoJS.MD5(this.ruleForm.pwd).toString(),
  9. });

由于后端数据接口失效,请求功能不完善。

源码地址:https://gitee.com/kevinleeeee/meituan-web

koa2

介绍

web框架,express幕后打造

特点:

更小,更灵活,利用async函数丢掉回调函数,增强错误处理,没有中间件,书写优雅

区别:

  • koa1:generator/yeild
  • koa2: async/await, 小而精, 且提供执行期上下文ctx
  • express: 回调函数,大而全, 不提供执行期上下文ctx,手动处理

搭建

  1. //安装koa生成器依赖
  2. npm i -g koa-generator
  3. //创建项目
  4. koa2 xiaomi_mobile_pro
  5. //安装依赖
  6. npm install
  7. //运行
  8. npm run dev
  9. //访问
  10. http://localhost:3000/

脚本

koa2项目的默认脚本命令

  1. "scripts": {
  2. "start": "node bin/www", //默认启动node
  3. "dev": "./node_modules/.bin/nodemon bin/www", //启动nodemon(log)
  4. "prd": "pm2 start bin/www", //通过pm2管理进程
  5. "test": "echo \"Error: no test specified\" && exit 1", //测试
  6. }

打包

问题:如何结合前端的代码,样式,脚本,图片进行打包处理?

通过webpack构建工具进行打包

问题:如何实现边开发边打包,页面实时渲染?

通过koa服务器,和webpack-dev-server服务器协调运作,前者用来解析网站,后者用来打包

koa项目改造:

koa&koa2&SSR - 图2

  • 打包后目录为:/public目录(静态样式,脚本,图片)

配置webpack

dev脚本命令时,使其自动打包,并显示代码效果,实现边开发边打包,页面实时渲染

  1. const path = require('path'),
  2. //压缩混淆JS
  3. Uglify = require('uglifyjs-webpack-plugin'),
  4. //处理CSS前缀
  5. Autoprefixer = require('autoprefixer'),
  6. //将CSS提取为独立的文件
  7. MiniCssExtractPlugin = require('mini-css-extract-plugin'),
  8. //用于优化或者压缩CSS资源
  9. OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
  10. const config = {
  11. mode: 'production',
  12. entry: {
  13. index: path.resolve(__dirname, './src/js/index.js'),
  14. list: path.resolve(__dirname, './src/js/list.js'),
  15. },
  16. output: {
  17. path: path.resolve(__dirname + '/public'),
  18. filename: 'js/[name].js',
  19. publicPath: '/'
  20. },
  21. module: {
  22. rules: rules: [
  23. {
  24. test: /\.js$/,
  25. loader: 'babel-loader',
  26. exclude: [path.resolve(__dirname, 'node_modules')],
  27. },
  28. {
  29. test: /\.tpl$/,
  30. loader: 'ejs-loader',
  31. },
  32. {
  33. test: /\.scss$/,
  34. use: [
  35. {
  36. loader: MiniCssExtractPlugin.loader,
  37. options: {
  38. hmr: process.env.NODE_ENV === 'development',
  39. },
  40. },
  41. 'css-loader',
  42. {
  43. loader: 'postcss-loader',
  44. options: {
  45. plugin() {
  46. return [autoprefixer('last 5 versions')];
  47. },
  48. },
  49. },
  50. 'sass-loader',
  51. ],
  52. },
  53. {
  54. test: /\.css$/,
  55. use: [
  56. {
  57. loader: MiniCssExtractPlugin.loader,
  58. options: {
  59. hmr: process.env.NODE_ENV === 'development',
  60. },
  61. },
  62. 'css-loader',
  63. {
  64. loader: 'postcss-loader',
  65. options: {
  66. plugin() {
  67. return [autoprefixer('last 5 versions')];
  68. },
  69. },
  70. },
  71. ],
  72. },
  73. {
  74. test: /\.(png|jpg|jpeg|gif|ico)$/i,
  75. loader: ['url-loader?limit=2048&name=img/[name]-[hash:16].[ext]'],
  76. },
  77. {
  78. test: /\.(woff2?|eot|ttf|oft|svg)(\?.*)?$/i,
  79. loader: ['url-loader?name=fonts/[name].[ext]'],
  80. },
  81. ]
  82. },
  83. plugins: [
  84. new Uglify(),
  85. new OptimizeCssAssetsPlugin({}),
  86. new MiniCssExtractPlugin({
  87. filename: 'css/[name].css'
  88. })
  89. ],
  90. devServer: {
  91. watchOptions: {
  92. ignoreed: /node_modules/
  93. },
  94. host: 'localhost',
  95. port: 3300
  96. }
  97. }
  98. module.exports = config;

安装webpack相关的依赖:

  1. "autoprefixer": "^9.5.1",
  2. "babel-core": "^6.26.3",
  3. "babel-loader": "^7.1.5",
  4. "babel-plugin-transform-decorators": "^6.24.1",
  5. "babel-plugin-transform-decorators-legacy": "^1.3.5",
  6. "babel-plugin-transform-runtime": "^6.23.0",
  7. "babel-preset-env": "^1.7.0",
  8. "babel-preset-latest": "^6.24.1",
  9. "css-loader": "^2.1.1",
  10. "ejs-loader": "^0.3.5",
  11. "file-loader": "^3.0.1",
  12. "html-webpack-plugin": "^3.2.0",
  13. "mini-css-extract-plugin": "^0.8.0",
  14. "node-sass": "^4.11.0",
  15. "nodemon": "^1.19.1",
  16. "optimize-css-assets-webpack-plugin": "^5.0.3",
  17. "postcss-loader": "^3.0.0",
  18. "sass-loader": "^7.1.0",
  19. "style-loader": "^0.23.1",
  20. "uglifyjs-webpack-plugin": "^2.1.2",
  21. "url-loader": "^1.1.2",
  22. "webpack": "^4.30.0",
  23. "webpack-cli": "^3.3.7",
  24. "webpack-dev-server": "^3.7.2"

node-fetch@2.6.0:向纯后端再次发起数据请求功能

关于html-webpack-plugin

SSR渲染的项目中,并不需要使用html-webpack-plugin,它是在生产环境下做的,因为koa2环境下,页面是EJS页面显示的,每次线上都会解析EJS模板形成最终的HTML返回给前端,而webpack打包出来的是静态资源

配置.babelrc文件:

  1. {
  2. "presets": ["env"],
  3. "plugins": [
  4. //装饰器
  5. "transform-decorators-legacy",
  6. //async await迭代器适用es7 es8写法
  7. "babel-plugin-transform-runtime"
  8. ]
  9. }

配置脚本命令:

  1. "scripts": {
  2. "start": "node bin/www", //默认启动node
  3. "dev": "./node_modules/.bin/nodemon bin/www", //启动nodemon(log)
  4. "prd": "pm2 start bin/www", //通过pm2管理进程
  5. //同时也要启动webpack服务器
  6. "webpack": "webpack-dev-server --host localhost --content-base app/public/ --hot --config webpack.config.js --progress --display-modules --colors --display-reasons",
  7. "build":"webpack --config webpack.config.js"
  8. }

关键引入:

koaview模板html文件里引入webpack-dev-server启动的服务器地址

  1. <script src="http://localhost:3400/js/index.js"></script>

分别同时启动服务器:

  1. //koa2
  2. npm run dev
  3. //webpack 该命令打包css,js后缓存到/piblic目录 需要在index.html引入
  4. npm run webpack
  5. //此时可以在koa2项目中, view页面正常显示, 并且可以看到实时更新逻辑代码

源码地址:

这是一个基于koa2webpack热更新搭建好的脚手架

https://gitee.com/kevinleeeee/webpack-koa2-cli

案例

案例:小米网页官网前后端案例

koa2+ejs+ nodejs+ webpack

项目结构:

  1. ├─app.js --- 应用入口文件
  2. ├─package.json
  3. ├─views --- 后端页面渲染模板
  4. | ├─error.pug
  5. | ├─index.pug
  6. | layout.pug
  7. ├─routes --- 后端路由系统
  8. | ├─index.js
  9. | users.js
  10. ├─public --- 前端静态文件
  11. /打包后的静态样式文件,脚本,图片
  12. | ├─stylesheets
  13. | | style.css
  14. | ├─javascripts
  15. | ├─images
  16. ├─bin --- 系统必备执行文件
  17. | www
  18. ├─config --- 开发配置文件
  19. ├─controllers --- 控制器,类方式管理路由的回调
  20. ├─lib --- 自定义库文件
  21. ├─middlewares --- 自定义中间件
  22. ├─models --- 数据请求模型
  23. ├─src --- 前端相应文件夹
  24. ├─views --- 后端页面渲染模板
  25. ├─.babelrc --- babel兼容性配置文件
  26. ├─deploy.yaml --- pm2线上部署与发布脚本
  27. ├─webpack.config.js --- 打包配置文件

前后端渲染过程:

前端 -> Node层(请求数据/组装HTML/model层) -> 纯后端(数据接口)

源码地址: https://gitee.com/kevinleeeee/xiaomi-web-koa2-project

EJS

ejs语法:

  1. //html标签作为变量
  2. <%= title =%>
  3. //JavaScript语句
  4. <% for(var i = 0; i < arr.length; i++){
  5. var item = arr[i];
  6. %>
  7. <h1><%= item.name %></h1>
  8. <% } %>
  9. //引入ejs文件时, 且可以传入参数
  10. <%- include('xxx.ejs', params) %>

SSR

案例:腾讯课堂官网网页版

这是一个基于爬虫数据 制作的前端页面

功能:

  • 首页搜索栏搜索课程
  • 首页轮播图
  • 首页课程老师学生列表渲染
  • 首页底部雪碧图链接
  • 列表页tab栏切换渲染列表
  • 错误页渲染

技术:

  • 原生JS
  • webpack工程化
  • koa2
  • SSR服务端渲染
  • EJS模板系统
  • 前端数据库:MySQL
  • 后端

案例展示图:

koa&koa2&SSR - 图3

koa&koa2&SSR - 图4

koa&koa2&SSR - 图5

koa&koa2&SSR - 图6

项目启动注意:

  1. //1.先打包
  2. npm run build
  3. //2.再测试
  4. npm run dev

项目目录:

  1. ├─.babelrc
  2. ├─app.js - 后端服务器程序
  3. ├─package.json
  4. ├─webpack.config.js
  5. ├─src
  6. | ├─views - 模板ejs文件/引入子模板/传参
  7. | | ├─error.ejs - 入口文件/错误页
  8. | | ├─index.ejs - 入口文件
  9. | | ├─list.ejs - 入口文件/列表页
  10. | | ├─templates - 被引入的模板
  11. | | | ├─list
  12. | | | | ├─noDataTip
  13. | | | | | index.ejs
  14. | | | | ├─nav
  15. | | | | | ├─index.ejs
  16. | | | | | item.ejs
  17. | | | | ├─courseList
  18. | | | | | index.ejs
  19. | | | ├─index
  20. | | | | ├─teacher
  21. | | | | | ├─index.ejs
  22. | | | | | item.ejs
  23. | | | | ├─student
  24. | | | | | ├─index.ejs
  25. | | | | | item.ejs
  26. | | | | ├─recomCourse
  27. | | | | | ├─index.ejs
  28. | | | | | item.ejs
  29. | | | | ├─collection
  30. | | | | | ├─index.ejs
  31. | | | | | item.ejs
  32. | | | | ├─carousel
  33. | | | | | ├─director.ejs
  34. | | | | | ├─index.ejs
  35. | | | | | ├─indicator.ejs
  36. | | | | | ├─item.ejs
  37. | | | | | slider.ejs
  38. | | | ├─error
  39. | | | | index.ejs
  40. | | | ├─common - 公共复用的文件
  41. | | | | ├─mainTitle
  42. | | | | | index.ejs
  43. | | | | ├─header
  44. | | | | | ├─index.ejs
  45. | | | | | ├─logo.ejs
  46. | | | | | ├─nav.ejs
  47. | | | | | search.ejs
  48. | | | | ├─footer
  49. | | | | | ├─bottom.ejs
  50. | | | | | ├─index.ejs
  51. | | | | | info.ejs
  52. | | | | ├─courseItem
  53. | | | | | index.ejs
  54. | | ├─layout - 布局复用的文件
  55. | | | ├─foot.ejs
  56. | | | head.ejs
  57. | | ├─datas - 隐藏的div保存字符串数据显示在html dom节点
  58. | | | courseData.ejs
  59. | ├─utils
  60. | | tools.js - 前端工具/模板解析/数据筛选/去空格
  61. | ├─templates
  62. | | courseItem.tpl - 用于列表页navpage页渲染模板
  63. | ├─styles
  64. | | ├─carousel.scss
  65. | | ├─collection.scss
  66. | | ├─common.css
  67. | | ├─courseItem.scss
  68. | | ├─courseList.scss
  69. | | ├─courseNav.scss
  70. | | ├─error.scss
  71. | | ├─footer.scss
  72. | | ├─header.scss
  73. | | ├─iconfont.css
  74. | | ├─mainTitle.scss
  75. | | ├─noDataTip.scss
  76. | | ├─recomCourse.scss
  77. | | ├─resets.css
  78. | | ├─teacher.scss
  79. | | ├─ui.scss
  80. | | ├─variable.scss
  81. | | ├─fonts
  82. | | | ├─iconfont.eot
  83. | | | ├─iconfont.svg
  84. | | | ├─iconfont.ttf
  85. | | | ├─iconfont.woff
  86. | | | iconfont.woff2
  87. | ├─modules - 逻辑功能模块
  88. | | ├─Carousel.js - 轮播图
  89. | | ├─CourseNav.js - 导航栏
  90. | | HeaderSearch.js - 搜索栏
  91. | ├─js - 入口文件/引入网页样式文件和逻辑功能模块
  92. | | ├─error.js
  93. | | ├─index.js
  94. | | list.js
  95. | ├─img
  96. | | logo.png
  97. | ├─config - 前端配置信息
  98. | | config.js - 轮播图/域名配置
  99. ├─services - 后端操作数据库的服务
  100. | ├─Collection.js
  101. | ├─Course.js
  102. | ├─CourseTab.js
  103. | ├─RecomCourse.js
  104. | ├─Slider.js
  105. | ├─Student.js
  106. | Teacher.js
  107. ├─routes - 路由管理
  108. | index.js
  109. ├─public - 静态资源
  110. | ├─js
  111. | | ├─error.js
  112. | | ├─index.js
  113. | | ├─list.js
  114. | | main.js
  115. | ├─img
  116. | | ├─logo-c872049b5cd29849.png
  117. | | logo108.png
  118. | ├─css
  119. | | ├─error.css
  120. | | ├─index.css
  121. | | list.css
  122. ├─libs - 后端工具
  123. | utils.js - 搜索筛选
  124. ├─db - 数据库
  125. | ├─db_connect.js - 连接数据库
  126. | ├─models - 表模型
  127. | | ├─aboutus.js
  128. | | ├─agencyInfo.js
  129. | | ├─collection.js
  130. | | ├─course.js
  131. | | ├─courseTab.js
  132. | | ├─recomCourse.js
  133. | | ├─slider.js
  134. | | ├─student.js
  135. | | teacher.js
  136. ├─controllers - 控制器
  137. | Home.js - 关联路由/对响应首页/列表页/错误页的管理和传值
  138. ├─configs - 后端配置信息
  139. | ├─db.js - 启动服务器配置
  140. | ├─db_type.js - 数据库表字段
  141. | ├─env.js - 环境
  142. | ├─link.js - 首页底栏合作平台链接信息
  143. | ├─manual.js - 首页底栏官方手册集合信息(基于一张雪碧图)
  144. | ├─nav.js - 首页顶部nav栏信息
  145. | ├─page.js - head标签配置信息
  146. | ├─qr.js - 底部二维码信息
  147. | url.js - 域名地址
  148. ├─bin
  149. | www

项目地址: https://gitee.com/kevinleeeee/crawler-txcourse-website-demo