精华内容
下载资源
问答
  • Egg的集群管理器 安装 $ npm i egg-cluster --save 用法 const startCluster = require ( 'egg-cluster' ) . startCluster ; startCluster ( { baseDir : '/path/to/app' , framework : '/path/to/framework' , }...
  • 内置过程管理 插件系统 框架定制 很多 快速开始 请遵循下面列出的命令。 $ mkdir showcase && cd showcase $ npm init egg --type=simple $ npm install $ npm run dev $ open http://localhost:7001 需要Node.js>...
  • 作为一名前端开发者,在选择 Nodejs 后端服务框架时,第一时间会想到 Egg.js,不得不说 Egg.js 是...正因为如此,第一次接触它,我便喜欢上了它,之后也用它开发过不少应用。 有了如此优秀的框架,那么如何将一个 E...

    作为一名前端开发者,在选择 Nodejs 后端服务框架时,第一时间会想到 Egg.js,不得不说 Egg.js 是一个非常优秀的企业级框架,它的高扩展性和丰富的插件,极大的提高了开发效率。开发者只需要关注业务就好,比如要使用 redis,引入 egg-redis 插件,然后简单配置就可以了。正因为如此,第一次接触它,我便喜欢上了它,之后也用它开发过不少应用。

    有了如此优秀的框架,那么如何将一个 Egg.js 的服务迁移到 Serverless 架构上呢?

    背景

    我在文章 基于 Serverless Component 的全栈解决方案 中讲述了,如何将一个基于 Vue.js 的前端应用和基于 Express 的后端服务,快速部署到腾讯云上。虽然受到不少开发者的喜爱,但是很多开发者私信问我,这还是一个 Demo 性质的项目而已,有没有更加实用性的解决方案。而且他们实际开发中,很多使用的正是 Egg.js 框架,能不能提供一个 Egg.js 的解决方案?

    本文将手把手教你结合 Egg.jsServerless 实现一个后台管理系统。

    读完此文你将学到:

    1. Egg.js 基本使用
    2. 如何使用 Sequelize ORM 模块进行 Mysql 操作
    3. 如何使用 Redis
    4. 如何使用 JWT 进行用户登录验证
    5. Serverless Framework 的基本使用
    6. 如何将本地开发好的 Egg.js 应用部署到腾讯云云函数上
    7. 如何基于云端对象存储快速部署静态网站

    Egg.js 入门

    初始化 Egg.js 项目:

    $ mkdir egg-example && cd egg-example
    $ npm init egg --type=simple
    $ npm i

    启动项目:

    $ npm run dev

    然后浏览器访问 http://localhost:7001,就可以看到亲切的 hi, egg 了。

    关于 Egg.js 的框架更多知识,建议阅读 官方文档

    准备

    对 Egg.js 有了简单了解,接下来我们来初始化我们的后台管理系统,新建一个项目目录 admin-system:

    $ mkdir admin-system

    将上面创建的 Egg.js 项目复制到 admin-system 目录下,重命名为 backend。然后将前端模板项目复制到 frontend 文件夹中:

    $ git clone https://github.com/PanJiaChen/vue-admin-template.git frontend

    说明: vue-admin-template 是基于 Vue2.0 的管理系统模板,是一个非常优秀的项目,建议对 Vue.js 感兴趣的开发者可以去学习下,当然如果你对 Vue.js 还不是太了解,这里有个基础入门学习教程 Vuejs 从入门到精通系列文章

    之后你的项目目录结构如下:

    .
    ├── README.md
    ├── backend     // 创建的 Egg.js 项目
    └── frontend    // 克隆的 Vue.js 前端项目模板

    启动前端项目熟悉下界面:

    $ cd frontend
    $ npm install
    $ npm run dev

    然后访问 http://localhost:9528 就可以看到登录界面了。

    开发后端服务

    对于一个后台管理系统服务,我们这里只实现登录鉴权和文章管理功能,剩下的其他功能大同小异,读者可以之后自由补充扩展。

    1. 添加 Sequelize 插件

    在正式开发之前,我们需要引入数据库插件,这里本人偏向于使用 Sequelize ORM 工具进行数据库操作,正好 Egg.js 提供了 egg-sequelize 插件,于是直接拿来用,需要先安装:

    $ cd frontend
    # 因为需要通过 sequelize 链接 mysql 所以这也同时安装 mysql2 模块
    $ npm install egg-sequelize mysql2 --save

    然后在 backend/config/plugin.js 中引入该插件:

    module.exports = {
      // ....
      sequelize: {
        enable: true,
        package: "egg-sequelize"
      }
      // ....
    };

    backend/config/config.default.js 中配置数据库连接参数:

    // ...
    const userConfig = {
      // ...
      sequelize: {
        dialect: "mysql",
    
        // 这里也可以通过 .env 文件注入环境变量,然后通过 process.env 获取
        host: "xxx",
        port: "xxx",
        database: "xxx",
        username: "xxx",
        password: "xxx"
      }
      // ...
    };
    // ...

    2. 添加 JWT 插件

    系统将使用 JWT token 方式进行登录鉴权,安装配置参考官方文档,egg-jwt

    3. 添加 Redis 插件

    系统将使用 redis 来存储和管理用户 token,安装配置参考官方文档,egg-redis

    4. 角色 API

    定义用户模型,创建 backend/app/model/role.js 文件如下:

    module.exports = app => {
      const { STRING, INTEGER, DATE } = app.Sequelize;
    
      const Role = app.model.define("role", {
        id: { type: INTEGER, primaryKey: true, autoIncrement: true },
        name: STRING(30),
        created_at: DATE,
        updated_at: DATE
      });
    
      // 这里定义与 users 表的关系,一个角色可以含有多个用户,外键相关
      Role.associate = () => {
        app.model.Role.hasMany(app.model.User, { as: "users" });
      };
    
      return Role;
    };

    实现 Role 相关服务,创建 backend/app/service/role.js 文件如下:

    const { Service } = require("egg");
    
    class RoleService extends Service {
      // 获取角色列表
      async list(options) {
        const {
          ctx: { model }
        } = this;
        return model.Role.findAndCountAll({
          ...options,
          order: [
            ["created_at", "desc"],
            ["id", "desc"]
          ]
        });
      }
    
      // 通过 id 获取角色
      async find(id) {
        const {
          ctx: { model }
        } = this;
        const role = await model.Role.findByPk(id);
        if (!role) {
          this.ctx.throw(404, "role not found");
        }
        return role;
      }
    
      // 创建角色
      async create(role) {
        const {
          ctx: { model }
        } = this;
        return model.Role.create(role);
      }
    
      // 更新角色
      async update({ id, updates }) {
        const role = await this.ctx.model.Role.findByPk(id);
        if (!role) {
          this.ctx.throw(404, "role not found");
        }
        return role.update(updates);
      }
    
      // 删除角色
      async destroy(id) {
        const role = await this.ctx.model.Role.findByPk(id);
        if (!role) {
          this.ctx.throw(404, "role not found");
        }
        return role.destroy();
      }
    }
    
    module.exports = RoleService;

    一个完整的 RESTful API 就该包括以上五个方法,然后实现 RoleController, 创建 backend/app/controller/role.js:

    const { Controller } = require("egg");
    
    class RoleController extends Controller {
      async index() {
        const { ctx } = this;
        const { query, service, helper } = ctx;
        const options = {
          limit: helper.parseInt(query.limit),
          offset: helper.parseInt(query.offset)
        };
        const data = await service.role.list(options);
        ctx.body = {
          code: 0,
          data: {
            count: data.count,
            items: data.rows
          }
        };
      }
    
      async show() {
        const { ctx } = this;
        const { params, service, helper } = ctx;
        const id = helper.parseInt(params.id);
        ctx.body = await service.role.find(id);
      }
    
      async create() {
        const { ctx } = this;
        const { service } = ctx;
        const body = ctx.request.body;
        const role = await service.role.create(body);
        ctx.status = 201;
        ctx.body = role;
      }
    
      async update() {
        const { ctx } = this;
        const { params, service, helper } = ctx;
        const body = ctx.request.body;
        const id = helper.parseInt(params.id);
        ctx.body = await service.role.update({
          id,
          updates: body
        });
      }
    
      async destroy() {
        const { ctx } = this;
        const { params, service, helper } = ctx;
        const id = helper.parseInt(params.id);
        await service.role.destroy(id);
        ctx.status = 200;
      }
    }
    
    module.exports = RoleController;

    之后在 backend/app/route.js 路由配置文件中定义 role 的 RESTful API:

    router.resources("roles", "/roles", controller.role);

    通过 router.resources 方法,我们将 roles 这个资源的增删改查接口映射到了 app/controller/roles.js 文件。详细说明参考 官方文档

    5. 用户 API

    同 Role 一样定义我们的用户 API,这里就不复制粘贴了,可以参考项目实例源码 admin-system

    6. 同步数据库表格

    上面只是定义好了 RoleUser 两个 Schema,那么如何同步到数据库呢?这里先借助 Egg.js 启动的 hooks 来实现,Egg.js 框架提供了统一的入口文件(app.js)进行启动过程自定义,这个文件返回一个 Boot 类,我们可以通过定义 Boot 类中的生命周期方法来执行启动应用过程中的初始化工作。

    我们在 backend 目录中创建 app.js 文件,如下:

    "use strict";
    
    class AppBootHook {
      constructor(app) {
        this.app = app;
      }
    
      async willReady() {
        // 这里只能在开发模式下同步数据库表格
        const isDev = process.env.NODE_ENV === "development";
        if (isDev) {
          try {
            console.log("Start syncing database models...");
            await this.app.model.sync({ logging: console.log, force: isDev });
            console.log("Start init database data...");
            await this.app.model.query(
              "INSERT INTO roles (id, name, created_at, updated_at) VALUES (1, 'admin', '2020-02-04 09:54:25', '2020-02-04 09:54:25'),(2, 'editor', '2020-02-04 09:54:30', '2020-02-04 09:54:30');"
            );
            await this.app.model.query(
              "INSERT INTO users (id, name, password, age, avatar, introduction, created_at, updated_at, role_id) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 20, 'https://yugasun.com/static/avatar.jpg', 'Fullstack Engineer', '2020-02-04 09:55:23', '2020-02-04 09:55:23', 1);"
            );
            await this.app.model.query(
              "INSERT INTO posts (id, title, content, created_at, updated_at, user_id) VALUES (2, 'Awesome Egg.js', 'Egg.js is a awesome framework', '2020-02-04 09:57:24', '2020-02-04 09:57:24', 1),(3, 'Awesome Serverless', 'Build web, mobile and IoT applications using Tencent Cloud and API Gateway, Tencent Cloud Functions, and more.', '2020-02-04 10:00:23', '2020-02-04 10:00:23', 1);"
            );
            console.log("Successfully init database data.");
            console.log("Successfully sync database models.");
          } catch (e) {
            console.log(e);
            throw new Error("Database migration failed.");
          }
        }
      }
    }
    
    module.exports = AppBootHook;

    通过 willReady 生命周期函数,我们可以执行 this.app.model.sync() 函数来同步数据表,当然这里同时初始化了角色和用户数据记录,用来做为演示用。

    注意:这的数据库同步只是本地调试用,如果想要腾讯云的 Mysql 数据库,建议开启远程连接,通过 sequelize db:migrate 实现,而不是每次启动 Egg 应用时同步,示例代码已经完成此功能,参考 Egg Sequelize 文档

    这里本人为了省事,直接开启腾讯云 Mysql 公网连接,然后修改 config.default.js 中的 sequelize 配置,运行 npm run dev 进行开发模式同步。

    到这里,我们的用户和角色的 API 都已经定义好了,启动服务 npm run dev,访问 https://127.0.0.1:7001/users 可以获取所有用户列表了。

    7. 用户登录/注销 API

    这里登录逻辑比较简单,客户端发送 用户名密码/login 路由,后端通过 login 函数接受,然后从数据库中查询该用户名,同时比对密码是否正确。如果正确则调用 app.jwt.sign() 函数生成 token,并将 token 存入到 redis 中,同时返回该 token,之后客户端需要鉴权的请求都会携带 token,进行鉴权验证。思路很简单,我们就开始实现了。

    流程图如下:

    Login Process

    首先,在 backend/app/controller/home.js 中新增登录处理 login 方法:

    class HomeController extends Controller {
      // ...
      async login() {
        const { ctx, app, config } = this;
        const { service, helper } = ctx;
        const { username, password } = ctx.request.body;
        const user = await service.user.findByName(username);
        if (!user) {
          ctx.status = 403;
          ctx.body = {
            code: 403,
            message: "Username or password wrong"
          };
        } else {
          if (user.password === helper.encryptPwd(password)) {
            ctx.status = 200;
            const token = app.jwt.sign(
              {
                id: user.id,
                name: user.name,
                role: user.role.name,
                avatar: user.avatar
              },
              config.jwt.secret,
              {
                expiresIn: "1h"
              }
            );
            try {
              await app.redis.set(`token_${user.id}`, token);
              ctx.body = {
                code: 0,
                message: "Get token success",
                token
              };
            } catch (e) {
              console.error(e);
              ctx.body = {
                code: 500,
                message: "Server busy, please try again"
              };
            }
          } else {
            ctx.status = 403;
            ctx.body = {
              code: 403,
              message: "Username or password wrong"
            };
          }
        }
      }
    }

    注释:这里有个密码存储逻辑,用户在注册时,密码都是通过 helper 函数 encryptPwd() 进行加密的(这里用到最简单的 md5 加密方式,实际开发中建议使用更加高级加密方式),所以在校验密码正确性时,也需要先加密一次。至于如何在 Egg.js 框架中新增 helper 函数,只需要在 backend/app/extend 文件夹中新增 helper.js 文件,然后 modole.exports 一个包含该函数的对象就行,参考 Egg 框架扩展文档

    然后,在 backend/app/controller/home.js 中新增 userInfo 方法,获取用户信息:

    async userInfo() {
      const { ctx } = this;
      const { user } = ctx.state;
      ctx.status = 200;
      ctx.body = {
        code: 0,
        data: user,
      };
    }

    egg-jwt 插件,在鉴权通过的路由对应 controller 函数中,会将 app.jwt.sign(user, secrete) 加密的用户信息,添加到 ctx.state.user 中,所以 userInfo 函数只需要将它返回就行。

    之后,在 backend/app/controller/home.js 中新增 logout 方法:

    async logout() {
      const { ctx } = this;
      ctx.status = 200;
      ctx.body = {
        code: 0,
        message: 'Logout success',
      };
    }

    userInfologout 函数非常简单,重点是路由中间件如何处理。

    接下来,我们来定义登录相关路由,修改 backend/app/router.js 文件,新增 /login, /user-info, /logout 三个路由:

    const koajwt = require("koa-jwt2");
    
    module.exports = app => {
      const { router, controller, jwt } = app;
      router.get("/", controller.home.index);
    
      router.post("/login", controller.home.login);
      router.get("/user-info", jwt, controller.home.userInfo);
      const isRevokedAsync = function(req, payload) {
        return new Promise(resolve => {
          try {
            const userId = payload.id;
            const tokenKey = `token_${userId}`;
            const token = app.redis.get(tokenKey);
            if (token) {
              app.redis.del(tokenKey);
            }
            resolve(false);
          } catch (e) {
            resolve(true);
          }
        });
      };
      router.post(
        "/logout",
        koajwt({
          secret: app.config.jwt.secret,
          credentialsRequired: false,
          isRevoked: isRevokedAsync
        }),
        controller.home.logout
      );
    
      router.resources("roles", "/roles", controller.role);
      router.resources("users", "/users", controller.user);
      router.resources("posts", "/posts", controller.post);
    };

    Egg.js 框架定义路由时,router.post() 函数可以接受中间件函数,用来处理一些路由相关的特殊逻辑。

    比如 /user-info,路由添加了 app.jwt 作为 JWT 鉴权中间件函数,至于为什么这么用,egg-jwt 插件有明确说明。

    这里稍微复杂的是 /logout 路由,因为我们在注销登录时,需要将用户的 tokenredis 中移除,所以这里借助了 koa-jwt2isRevokded 参数,来进行 token 删除。

    后端服务部署

    到这里,后端服务的登录和注销逻辑基本完成了。那么如何部署到云函数呢?可以直接使用 tencent-egg 组件,它是专门为 Egg.js 框架打造的 Serverless Component,使用它可以快速将我们的 Egg.js 项目部署到腾讯云云函数上。

    1. 准备

    我们先创建一个 backend/sls.js 入口文件:

    const { Application } = require("egg");
    const app = new Application();
    module.exports = app;

    然后修改 backend/config/config.default.js 文件:

    const config = (exports = {
      env: "prod", // 推荐云函数的 egg 运行环境变量修改为 prod
      rundir: "/tmp",
      logger: {
        dir: "/tmp"
      }
    });

    注释:这里之所有需要修改运行和日志目录,是因为云函数运行时,只有 /tmp 才有写权限。

    全局安装 serverless 命令:

    $ npm install serverless -g

    2. 配置 Serverless

    在项目根目录下创建 serverless.yml 文件,同时新增 backend 配置:

    backend:
      component: "@serverless/tencent-egg"
      inputs:
        code: ./backend
        functionName: admin-system
        # 这里必须指定一个具有操作 mysql 和 redis 的角色,具体角色创建,可访问 https://console.cloud.tencent.com/cam/role
        role: QCS_SCFFull
        functionConf:
          timeout: 120
          # 这里的私有网络必须和 mysql、redis 实例一致
          vpcConfig:
            vpcId: vpc-xxx
            subnetId: subnet-xxx
        apigatewayConf:
          protocols:
            - https

    此时你的项目目录结构如下:

    .
    ├── README.md         // 项目说明文件
    ├── serverless.yml    // serverless yml 配合文件
    ├── backend           // 创建的 Egg.js 项目
    └── frontend          // 克隆的 Vue.js 前端项目模板

    3. 执行部署

    执行部署命令:

    $ serverless --debug

    之后控制台需要进行扫码登录验证腾讯云账号,扫码登录就好。等部署成功会发挥如下信息:

      backend:
        region:              ap-guangzhou
        functionName:        admin-system
        apiGatewayServiceId: service-f1bhmhk4
        url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/

    这里输出的 url 就是部署成功的 API 网关接口,可以直接访问测试。

    注释:云函数部署时,会自动在腾讯云的 API 网关创建一个服务,同时创建一个 API,通过该 API 就可以触发云函数执行了。

    4. 账号配置(可选)

    当前默认支持 Serverless cli 扫描二维码登录,如果希望配置持久的环境变量/秘钥信息,也可以在项目根目录创建 .env 文件

    .env 文件中配置腾讯云的 SecretId 和 SecretKey 信息并保存,密钥可以在 API 密钥管理 中获取或者创建.

    # .env
    TENCENT_SECRET_ID=123
    TENCENT_SECRET_KEY=123

    5. 文章 API

    跟用户 API 类似,只需要复制粘贴上面用户相关模块,修改名称为 posts, 并修改数据模型就行,这里就不粘贴代码了。

    前端开发

    本实例直接使用的 vue-admin-template 的前端模板。

    我们需要做如下几部分修改:

    1. 删除接口模拟:更换为真实的后端服务接口
    2. 修改接口函数:包括用户相关的 frontend/src/api/user.js 和文章相关接口 frontend/src/api/post.js
    3. 修改接口工具函数:主要是修改 frontend/src/utils/request.js 文件,包括 axios请求的 baseURL 和请求的 header。
    4. UI 界面修改:主要是新增文章管理页面,包括列表页和新增页。

    1. 删除接口模拟

    首先删除 frontend/mock 文件夹。然后修改前端入口文件 frontend/src/main.js

    // 1. 引入接口变量文件,这个会依赖 @serverless/tencent-website 组件自动生成
    import "./env.js";
    
    import Vue from "vue";
    
    import "normalize.css/normalize.css";
    import ElementUI from "element-ui";
    import "element-ui/lib/theme-chalk/index.css";
    import locale from "element-ui/lib/locale/lang/en";
    import "@/styles/index.scss";
    import App from "./App";
    import store from "./store";
    import router from "./router";
    import "@/icons";
    import "@/permission";
    
    // 2. 下面这段就是 mock server 引入,删除就好
    // if (process.env.NODE_ENV === 'production') {
    //   const { mockXHR } = require('../mock')
    //   mockXHR()
    // }
    
    Vue.use(ElementUI, { locale });
    Vue.config.productionTip = false;
    
    new Vue({
      el: "#app",
      router,
      store,
      render: h => h(App)
    });

    2. 修改接口函数

    修改 frontend/src/api/user.js 文件,包括登录、注销、获取用户信息和获取用户列表函数如下:

    import request from "@/utils/request";
    
    // 登录
    export function login(data) {
      return request({
        url: "/login",
        method: "post",
        data
      });
    }
    
    // 获取用户信息
    export function getInfo(token) {
      return request({
        url: "/user-info",
        method: "get"
      });
    }
    
    // 注销登录
    export function logout() {
      return request({
        url: "/logout",
        method: "post"
      });
    }
    
    // 获取用户列表
    export function getList() {
      return request({
        url: "/users",
        method: "get"
      });
    }

    新增 frontend/src/api/post.js 文件如下:

    import request from "@/utils/request";
    
    // 获取文章列表
    export function getList(params) {
      return request({
        url: "/posts",
        method: "get",
        params
      });
    }
    
    // 创建文章
    export function create(data) {
      return request({
        url: "/posts",
        method: "post",
        data
      });
    }
    
    // 删除文章
    export function destroy(id) {
      return request({
        url: `/posts/${id}`,
        method: "delete"
      });
    }

    3. 修改接口工具函数

    因为 @serverless/tencent-website 组件可以定义 env 参数,执行成功后它会在指定 root 目录自动生成 env.js,然后在 frontend/src/main.js 中引入使用。它会挂载 env 中定义的接口变量到 window 对象上。比如这生成的 env.js 文件如下:

    window.env = {};
    window.env.apiUrl =
      "https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/";

    根据此文件我们来修改 frontend/src/utils/request.js 文件:

    import axios from "axios";
    import { MessageBox, Message } from "element-ui";
    import store from "@/store";
    import { getToken } from "@/utils/auth";
    
    // 创建 axios 实例
    const service = axios.create({
      // 1. 这里设置为 `env.js` 中的变量 `window.env.apiUrl`
      baseURL: window.env.apiUrl || "/", // url = base url + request url
      timeout: 5000 // request timeout
    });
    
    // request 注入
    service.interceptors.request.use(
      config => {
        // 2. 添加鉴权token
        if (store.getters.token) {
          config.headers["Authorization"] = `Bearer ${getToken()}`;
        }
        return config;
      },
      error => {
        console.log(error); // for debug
        return Promise.reject(error);
      }
    );
    
    // 请求 response 注入
    service.interceptors.response.use(
      response => {
        const res = response.data;
    
        // 只有请求code为0,才是正常返回,否则需要提示接口错误
        if (res.code !== 0) {
          Message({
            message: res.message || "Error",
            type: "error",
            duration: 5 * 1000
          });
    
          if (res.code === 50008 || res.code === 50012 || res.code === 50014) {
            // to re-login
            MessageBox.confirm(
              "You have been logged out, you can cancel to stay on this page, or log in again",
              "Confirm logout",
              {
                confirmButtonText: "Re-Login",
                cancelButtonText: "Cancel",
                type: "warning"
              }
            ).then(() => {
              store.dispatch("user/resetToken").then(() => {
                location.reload();
              });
            });
          }
          return Promise.reject(new Error(res.message || "Error"));
        } else {
          return res;
        }
      },
      error => {
        console.log("err" + error);
        Message({
          message: error.message,
          type: "error",
          duration: 5 * 1000
        });
        return Promise.reject(error);
      }
    );
    
    export default service;

    4. UI 界面修改

    关于 UI 界面修改,这里就不做说明了,因为涉及到 Vue.js 的基础使用,如果还不会使用 Vue.js,建议先复制示例代码就好。如果对 Vue.js 感兴趣,可以到 Vue.js 官网 学习。也可以阅读本人的 Vuejs 从入门到精通系列文章,喜欢的话,可以送上您宝贵的 Star (*^▽^*)

    这里只需要复制 Demo 源码frontend/routerfrontend/views 两个文件夹就好。

    前端部署

    因为前端编译后都是静态文件,我们需要将静态文件上传到腾讯云的 COS(对象存储) 服务,然后开启 COS 的静态网站功能就可以了,这些都不需要你手动操作,使用 @serverless/tencent-website 组件就可以轻松搞定。

    1. 修改 Serverless 配置文件

    修改项目根目录下 serverless.yml 文件,新增前端相关配置:

    name: admin-system
    
    # 前端配置
    frontend:
      component: "@serverless/tencent-website"
      inputs:
        code:
          src: dist
          root: frontend
          envPath: src # 相对于 root 指定目录,这里实际就是 frontend/src
          hook: npm run build
        env:
          # 依赖后端部署成功后生成的 url
          apiUrl: ${backend.url}
        protocol: https
        # TODO: CDN 配置,请修改!!!
        hosts:
          - host: sls-admin.yugasun.com # CDN 加速域名
            https:
              certId: abcdedg # 为加速域名在腾讯云平台申请的免费证书 ID
              http2: off
              httpsType: 4
              forceSwitch: -2
    
    # 后端配置
    backend:
      component: "@serverless/tencent-egg"
      inputs:
        code: ./backend
        functionName: admin-system
        role: QCS_SCFFull
        functionConf:
          timeout: 120
          vpcConfig:
            vpcId: vpc-6n5x55kb
            subnetId: subnet-4cvr91js
        apigatewayConf:
          protocols:
            - https

    2. 执行部署

    执行部署命令:

    $ serverless --debug

    输出如下成功结果:

      frontend:
        url:  https://dtnu69vl-470dpfh-1251556596.cos-website.ap-guangzhou.myqcloud.com
        env:
          apiUrl: https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/
        host:
          - https://sls-admin.yugasun.com (CNAME: sls-admin.yugasun.com.cdn.dnsv1.com)
      backend:
        region:              ap-guangzhou
        functionName:        admin-system
        apiGatewayServiceId: service-f1bhmhk4
        url:                 https://service-f1bhmhk4-1251556596.gz.apigw.tencentcs.com/release/

    注释:这里 frontend 中多输出了 host,是我们的 CDN 加速域名,可以通过配置 @serverless/tencent-website 组件的 inputs.hosts 来实现。有关 CDN 相关配置说明可以阅读 基于 Serverless Component 的全栈解决方案 - 续集。当然,如果你不想配置 CDN,直接删除,然后访问 COS 生成的静态网站 url。

    部署成功后,我们就可以访问 https://sls-admin.yugasun.com 登录体验了。

    源码

    本篇涉及到所有源码都维护在开源项目 tencent-serverless-demoadmin-system

    总结

    本文基于腾讯云的无服务器框架 Serverless Framework 实现,涉及到内容较多,推荐在阅读时,边看边开发,跟着文章节奏一步一步实现。

    如果遇到问题,可以参考本文源码。如果你成功实现了,可以到官网进一步熟悉 Egg.js 框架,以便今后可以实现更加复杂的应用。虽然本文使用的是 Vue.js 前端框架,但是你也可以将 frontend 更换为任何你喜欢的前端框架项目,开发时只需要将接口请求前缀使用 @serverless/tencent-website 组件生成的 env.js 文件就行。

    传送门:

    - GitHub: github.com/serverless

    - 官网:serverless.com

    欢迎访问:Serverless 中文网,您可以在 最佳实践 里体验更多关于 Serverless 应用的开发!

    推荐阅读:《Serverless 架构:从原理、设计到项目实战》

    展开全文
  • 函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码...

    gabriel-garcia-marengo-ht9aLtovtSo-unsplash.jpg

    首先介绍下在本文出现的几个比较重要的概念:

    函数计算(Function Compute): 函数计算是一个事件驱动的服务,通过函数计算,用户无需管理服务器等运行情况,只需编写代码并上传。函数计算准备计算资源,并以弹性伸缩的方式运行用户代码,而用户只需根据实际代码运行所消耗的资源进行付费。函数计算更多信息 参考
    Fun: Fun 是一个用于支持 Serverless 应用部署的工具,能帮助您便捷地管理函数计算、API 网关、日志服务等资源。它通过一个资源配置文件(template.yml),协助您进行开发、构建、部署操作。Fun 的更多文档 参考

    备注: 本文介绍的技巧需要 Fun 版本大于等于 3.6.9。

    Egg.js 是什么?

    Egg.js 官方描述为:

    Egg.js 为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。
    Egg 奉行『约定优于配置』,按照一套统一的约定进行应用开发,团队内部采用这种方式可以减少开发人员的学习成本,开发人员不再是『钉子』,可以流动起来。
    Egg 的插件机制有很高的可扩展性,一个插件只做一件事。Egg 通过框架聚合这些插件,并根据自己的业务场景定制配置,这样应用的开发成本就变得很低。

    Egg 特性:

    • 提供基于 Egg 定制上层框架的能力
    • 高度可扩展的插件机制
    • 内置多进程管理
    • 基于 Koa 开发,性能优异
    • 框架稳定,测试覆盖率高
    • 渐进式开发

    示例效果预览

    本文涉及到的应用示例部署后的链接,点击预览效果:

    Egg.js 应用示例:http://mtime.functioncompute.com/hot

    环境准备

    首先按照 Fun 的安装文档里介绍的方法将 Fun 安装到本机。

    PS: 本文介绍的方法,不需要安装 Docker,仅仅安装 Fun 即可,最简单的方式就是直接下载可执行的二进制文件。

    安装完成后,可以执行 fun --version 检查 Fun 是否安装成功。

    第一个示例:快速初始化并部署一个 Egg.js 示例应用

    根据官方快速入门文档描述的步骤依次如下操作。

    初始化一个 egg.js 示例:

    mkdir egg-example && cd egg-example
    npm init egg --type=simple
    npm i
    

    将该示例在本地运行测试:

    npm run dev
    

    然后可以在浏览器访问 http://127.0.0.1:7001 查看效果。

    本地开发完成后,可以直接使用如下命令进行一键部署:

    fun deploy -y 
    

    第二个示例:快速迁移一个已有的 Egg.js 应用

    这里我们拿一个开源的 Egg.js web 应用做示例:https://github.com/OrangeXC/mtime

    首先我们需要将该应用克隆下来:

    git clone https://github.com/OrangeXC/mtime
    

    进入到 mtime 目录,使用 npm 安装依赖:

    npm install
    

    本地启动应用:

    npm run dev
    

    本地启动应用时,会使用 config/config.default.js 这个配置,需要将该配置中的 mysql 的 username、password 等属性配置为正确的值,才能将应用启动起来。

    启动完成后,本地通过浏览器打开网址 http://127.0.0.1:7001 预览效果即可。

    当本地测试完成后,我们就要考虑如何将其部署到线上了。部署到线上,Egg.js 默认会优先使用 config/config.prod.js 的配置,我们可以配置线上的数据库到这个文件里,这样就可以实现本地开发与线上部署使用不同的数据库的目的了。

    当然,在本地也可以验证下配置是否正确,直接使用 npm run start 就可以将应用以生产的方式运行起来,使用 npm run stop 可以将应用停止。

    最后,我们还需要修改下 Egg.js 的缓存与日志目录,我们添加下面的内容到 config/config.prod.js 中:

      config.rundir = '/tmp/run',
      config.logger = {
        dir: '/tmp/log',
      }
    

    上面的配置表示将 Egg.js 缓存以及日志写到函数计算的可写目录内(日志输出到 console 也是可以的)。

    最后,我们使用 Fun 一键部署:

    fun deploy -y
    

    部署完成后,可以在终端输出看到临时域名 13500180-1986114430573743.test.functioncompute.com,通过浏览器打开临时域名可以看到与本地运行时一样的效果。

    总结

    本文主要介绍了如何将 Egg.js 应用部署到函数计算。相比较与传统的部署方法,不仅没有更复杂,还省略了购买机器、安装配置 Node、Npm 的步骤。可以实现,将传统的 Egg.js 应用在本地开发完成后,一键部署到远端直接用于生产,并拥有了弹性伸缩、按量付费、免运维等特性。

    如果您在迁移您的应用过程中遇到了问题,欢迎加入我们的 dingding 群 11721331 反馈。

    阿里巴巴云原生关注微服务、Serverless、容器、Service Mesh 等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,做最懂云原生开发者的技术圈。”

    展开全文
  • Egg简介

    千次阅读 2020-05-28 20:00:00
    基于Koa开发封装,性能优异,内置多进程管理,具有高扩展性,且提供了基于Egg定制上层框架的能力,帮助开发团队降低了开发维护成本。 约定先于配置,相较于express更加灵活可配。 Koa Koa是Express原班人马导致的...

    Egg

    前言

    Egg.js 为企业级框架和应用而生。基于Koa开发封装,性能优异,内置多进程管理,具有高扩展性,且提供了基于Egg定制上层框架的能力,帮助开发团队降低了开发维护成本。

    约定先于配置,相较于express更加灵活可配。

    Koa

    Koa是Express原班人马导致的,致力于web应用和API领域更小,更丰富的web框架。其和express设计风格类似,底层采用同一套HTTP基础库。

    • koa采用中间件洋葱图,所有请求经过一个中间件时均会被执行2次,可以方便进行后置逻辑处理,而express中间件只会执行一次
    • koa新增了Context对象,其作为上下文贯穿整个请求过程,其上也挂载了ResquestResponse对象,而express只有ResquestResponse挂载在中间件上。
    • 使用async await,可以同步的方式书写异步代码,书写便捷,异常捕获错误。

    使用

    安装egg

    npm install -g egg
    

    使用脚手架搭建项目

    $ mkdir egg-example && cd egg-example
    $ npm init egg --type=simple
    $ npm i
    

    npm run dev 启动项目,默认可访问http://localhost:7001

    约定目录

    egg-project
    ├── package.json
    ├── app.js (可选)							//项目入口文件
    ├── agent.js (可选)						//进程处理,例如日志打印等
    ├── app
    |   ├── router.js							//路由定义
    │   ├── controller						//处理用户输入,返回所需结果
    │   |   └── home.js
    │   ├── service (可选)				 //处理业务层逻辑,调用后端api
    │   |   └── user.js
    │   ├── middleware (可选)			 //中间件
    │   |   └── response_time.js
    │   ├── schedule (可选)				 //定时任务
    │   |   └── my_task.js
    │   ├── public (可选)					 //静态资源
    │   |   └── reset.css
    │   ├── view (可选)						 //前端页面,模板文件
    │   |   └── home.tpl
    │   └── extend (可选)					 //扩展配置
    │       ├── helper.js (可选)
    │       ├── request.js (可选)
    │       ├── response.js (可选)
    │       ├── context.js (可选)
    │       ├── application.js (可选)
    │       └── agent.js (可选)
    ├── config									 //配置文件
    |   ├── plugin.js						 //插件配置
    |   ├── config.default.js
    │   ├── config.prod.js
    |   ├── config.test.js (可选)
    |   ├── config.local.js (可选)
    |   └── config.unittest.js (可选)
    └── test									  //单元测试
        ├── middleware
        |   └── response_time.test.js
        └── controller
            └── home.test.js
    

    文件加载顺序

    • 加载 plugin,找到应用和框架,加载 config/plugin.js
    • 加载 config,遍历 loadUnit 加载 config/config.{env}.js
    • 加载 extend,遍历 loadUnit 加载 app/extend/xx.js
    • 自定义初始化,遍历 loadUnit 加载 app.jsagent.js
    • 加载 service,遍历 loadUnit 加载 app/service 目录
    • 加载 middleware,遍历 loadUnit 加载 app/middleware 目录
    • 加载 controller,加载应用的 app/controller 目录
    • 加载 router,加载应用的 app/router.js

    优先级:插件>框架>应用,被依赖的优先加载,越底层的越先加载

    生命周期

    app.js

    // 项目启动时执行
    class AppBootHook {
        constructor(app) {
          this.app = app;
        }
      
        configWillLoad() {
          console.log("配置文件即将加载,这是最后动态修改配置的时机")
        }
      
        configDidLoad() {
            console.log("配置文加载完成,可在此处获取配置文件信息")
        }
      
        async didLoad() {
            console.log("所有文件加载完成")
        }
      
        async willReady() {
            console.log("插件启动完成")
        }
      
        async didReady() {
            console.log("worker准备就绪")
        }
      
        async serverDidReady() {
            console.log("应用启动完成,服务已在监听状态")
        }
      
        async beforeClose() {
            console.log("应用即将关闭")
        }
      }
      
      module.exports = AppBootHook;
    

    路由

    路由配置在:app/router.js

    router.get(routerName, path,middleware,controller);
    
    • routerName:路由别名
    • path:路由可访问的路径
    • middleware:中间件(可选)
    • controller:映射到具体的controller逻辑方法

    get为路由请求方式,controller会以.为区分,查找app/controller目录下对用的文件,最后一个标识表示controller中的method名,如果不存在则默认访问index()

    例如:

      router.post('/admin', isAdmin, app.controller.admin);
    

    isAdmin是中间件,访问/admin时,将执行app/controller/admin文件下的index()

    请求参数获取

    //get请求
    let name = ctx.query.name
    
    //post请求
    let name = ctx.body.name
    
    //路由上动态参数
    let name = ctx.params.name
    

    内置对象

    • Application:全局唯一实例对象,在应用任意地方均可以获取。获取方式:this.app
    • Context:请求级别对象,包含了用户请求信息响应信息,一般存在于MiddlewareControllerService中。获取方式:this.ctx
    • Config:应用配置信息,存在于ControllerServiceHelper获取方式:app.configthis.config
    • Logger:日志对象。使用方式:app.logger

    ControllerServiceHelper:这三个类,均内置了如下属性:

    • ctx - 当前请求的 Context 实例。
    • app - 应用的 Application 实例。
    • config - 应用的配置
    • service - 应用所有的 service
    • logger - 为当前 controller 封装的 logger 对象。
    • helper-公共函数辅助对象

    中间件

    中间件配置在:app/middleware下。

    写法

    module.exports = (options,app)=>{
        //每个中间件都接收2个参数,options:中间件属性,app:应用实例对象
        //可获取配置信息
        return async (ctx, next) => {
            //中间件返回一个异步的方法,方法接收2个参数,ctx:请求上下文,next:下一次执行的函数
            //中间件逻辑处理
            await next()
            
        }
    }
    

    每个中间件都接收2个参数,options:中间件自定义配置,app:应用实例

    其需要导出一个异步函数。

    使用

    定义好的中间件需要我们手动进行加载配置。

    config/config.default.js中定义需要加载的中间件,然后配置其所需属性

      config.middleware = [
        "test"	//注册test中间件需要加载
      ];
    
      config.test = {	//test中间件属性配置
        name: "中间件测试"
      }
    

    每个中间件都有3个默认属性:

    • enable:控制中间件是否开启。
    • match:设置只有符合某些规则的请求才会经过这个中间件。
    • ignore:设置符合某些规则的请求不经过这个中间件。

    控制器(Controller)

    所有的controller文件均应放在app/controller目录下,因为应用约定在该目录下查找路由对应的逻辑处理,支持多级目录。

    写法

    // app/controller/post.js
    const Controller = require('egg').Controller;
    class PostController extends Controller {
      async create() {
        const { ctx, service } = this;
        const createRule = {
          title: { type: 'string' },
          content: { type: 'string' },
        };
        // 校验参数
        ctx.validate(createRule);
        // 组装参数
        const author = ctx.session.userId;
        const req = Object.assign(ctx.request.body, { author });
        // 调用 Service 进行业务处理
        const res = await service.post.create(req);
        // 设置响应内容和响应状态码
        ctx.body = { id: res.id };
        ctx.status = 201;
      }
    }
    module.exports = PostController;
    

    每个自定义控制器都应该继承eggController类,这样才能将内置属性挂载到自定义控制器上。

    每个方法都应该是一个async await函数

    每个文件只能存在一个导出类,必须用module.exports导出。

    模板渲染

    Context提供了3个Promise接口进行模板渲染。

    • render(name, locals) 渲染模板文件, 并赋值给 ctx.body
    • renderView(name, locals) 渲染模板文件, 仅返回不赋值
    • renderString(tpl, locals) 渲染模板字符串, 仅返回不赋值

    locals为需要传递给前端的数据,是一个对象

    数据返回

    ctx.body=data
    

    服务(Service)

    所有的controller文件均应放在app/service目录下。支持多级目录

    // app/service/user.js
    const Service = require('egg').Service;
    
    class UserService extends Service {
      async find(uid) {
        const user = await this.ctx.db.query('select * from user where uid = ?', uid);
        return user;
      }
    }
    
    module.exports = UserService;
    

    每个方法都应该是一个async await函数,且必须继承egg.Service

    每个文件只能存在一个导出类,必须用module.exports导出。

    service的ctx可以发起请求,也可以访问数据库。

    • this.ctx.curl 发起网络调用。
    • this.ctx.service.otherService 调用其他 Service。
    • this.ctx.db 发起数据库调用等, db 可能是其他插件提前挂载到 app 上的模块。

    模板渲染

    egg支持egg-view-nunjucks模板引擎,静态资源模板采用egg-view-assets

    插件下载

    npm i egg-view-nunjucks --save
    npm i egg-view-assets --save
    

    启动插件

    // config/plugin.js
    module.exports = {
      assets: {
        enable: true,
        package: 'egg-view-assets',
      },
      nunjucks: {
        enable: true,
        package: 'egg-view-nunjucks',
      }
    };
    

    插件配置

    // config/config.default.js
    //配置模板根目录,这样在controller中render时则可省略根目录直接在该目录下查找指定文件
    config.view = {
        root: path.join(appInfo.baseDir, 'app/view/entry'),
        mapping: {
          ".jsx": "assets"
        }
      }
    
    //指定模板位置,编译后输出位置,本地测试打包配置devServer
      config.assets={
        templatePath:path.join(appInfo.baseDir, 'app/view/template/index.html'),
        templateViewEngine: 'nunjucks',
        devServer: {
          command: 'roadhog dev',
          debug: true,
          port: 8000,
          timeout:180*1000,
          env: {
            BROWSER: 'none',
            ESLINT: 'none',
            SOCKET_SERVER: 'http://127.0.0.1:8000',
            PUBLIC_PATH: 'http://127.0.0.1:8000',
          },
        },
    

    使用
    模板文件app/view/template/index.html

    <!doctype html>
    <html>
    
    <head>
        <meta charset="utf-8" />
        <title>模板渲染</title>
        <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
        <meta name="renderer" content="webkit">
        {{ helper.assets.getStyle() | safe }}
    </head>
    
    <body>
        <div id="ReactApp"></div>
        {{ helper.assets.getScript() | safe }}
    </body>
    
    </html>
    

    helper.assets可动态的获取render参数,并且生成可访问的映射路径,例如:home.js -> http://127.0.0.1:8000/home.js
    默认情况下publicPath:"/",如果配置publicPath:"public,则映射关系也将改变,home.js -> http://127.0.0.1:8000/public/home.js

    webpack配置,.webpackrc

    {
      "entry": "app/view/entry/*.jsx",
      "extraBabelPlugins": [
        [ "import", { "libraryName": "antd", "style": true } ]
      ],
      "outputPath": "app/public",
      "disableCSSModules": true,
      "hash": true,
      "manifest": {
        "fileName": "../../../config/manifest.json"
      }
    }
    

    这里引入了antd,所以package.json需含有 @babel/corebabel-plugin-import这两个依赖。
    roadhog在开发环境下publicPath永远为/

    入口文件,app/view/entry/home.jsx

    import React from "react";
    import ReactDom from "react-dom";
    
    const Home = () => {
        return (
            <div>我是入口模板</div>
        )
    }
    
    ReactDom.render(<Home />, document.getElementById('ReactApp'));
    

    模板渲染,app/controller/home.js

    const Controller = require('egg').Controller;
    
    class HomeController extends Controller {
      async index() {
        const { ctx } = this;
        await ctx.render("home.jsx");
      }
    }
    module.exports = HomeController;
    

    参考地址

    egg官网:https://eggjs.org/zh-cn/basics/structure.html

    展开全文
  • 1、Egg.js是基于Node.js和Koa的一个企业级应用开发框架,可以帮助团队降低开发成本和维护成本。 二、express,koa.js 上手比较简单,但是没有严格的开发规范 三、Egg.js特性 1、提供基于Egg的定制上层框架的能力 2、...

    20210226-20210227:《Egg.js框架入门与实战》

    课程地址:https://www.imooc.com/learn/1185

    第一章 课程导学

    01-01 课程介绍

    一、Egg.js框架介绍
    1、Egg.js是基于Node.js和Koa的一个企业级应用开发框架,可以帮助团队降低开发成本和维护成本。
    二、express,koa.js
    上手比较简单,但是没有严格的开发规范
    三、Egg.js特性
    1、提供基于Egg的定制上层框架的能力
    2、高度可扩展的插件机制
    3、内置多进程管理
    4、基于Koa开发性能优异
    5、框架稳定,测试覆盖率高
    6、渐进式开发
    四、涉及技术点
    vant ui框架
    vue-cli3
    moment.js
    Egg.js基础
    mysql基础
    前后端联调

    第02章 基础功能讲解

    02-01 搭建Egg.js的开发环境

    一、官网
    1、https://eggjs.org/zh-cn/intro/quickstart.html
    二、建议使用node的LTS版本
    LTS:长期稳定版本
    current:含目前nodejs最新的特性,相对而言没那么稳定
    三、
    1、脚手架生成项目

    mkdir egg-demo && cd egg-demo
    npm init egg --type=simple
    

    2、安装相关依赖包

    npm install
    

    3、命令启动

    npm run dev:开发中
    npm run start:实际生产项目中使用
    

    四、目录
    1、app:项目核心目录。业务逻辑、数据库方面的操作
    2、config:针对egg.js的插件进行配置
    (1)config.default.js:
    (2)plugin.js:
    3、test:单元测试的时候使用的
    4、autod.conf.js:autod的配置文件
    5、.eslintrc、.eslintrc:eslint配置文件

    02-02 初识Egg.js的路由

    一、Router主要用来描述请求URL和具体承担执行动作的Controller的对应关系,框架约定了app/router.js文件用于同一所有路由规则。
    二、通过同一的配置,我们可以避免路由规则逻辑散落在多个地方,从而出现未知的冲突,集中在一起我们可以更方便的来查看全局的路由规则。
    三、const { ctx } = this
    每次用户请求的时候,框架就会实例化一个context上下文
    context主要用来存放用户请求的一些信息
    四、Controller:负责解析用户的输入,处理后返回相应的结果

    02-03 GET方式请求中的两种传参方式

    一、两种传参方式
    1、键值对的模式:?id=123&acb=456
    路由:http://127.0.0.1:7000/product/detail?id=123

        const { ctx } = this
        console.log(ctx.query)
        ctx.body = 'id == ctx.query.id'
    

    2、基于/:/123/456
    路由:http://127.0.0.1:7000/product/detail2/100
    ctx.params.id获取到的id,也就是这个100,是字符串类型。

    const { ctx } = this
    console.log(ctx.params)
    ctx.body = `id == ctx.params.id`
    

    02-04 POST等其他方式请求方法

    一、请求方法
    router.head - HEAD
    router.options - OPTIONS
    router.get - GET
    router.put - PUT
    router.post - POST
    router.patch - PATCH
    router.delete - DELETE
    router.del - delete是保留字,所以提供了一个delete方法的别名
    router.redirect - 可以对URL进行重定向处理
    二、POST

    const { ctx } = this
    console.log(ctx.request.body)
    ctx.body = {
      id: 123
    }
    

    这时无法在浏览器请求了,这时候用一个工具:postman
    1、postman发送请求,报错。终端提示了报错信息
    在这里插入图片描述
    在这里插入图片描述
    2、解决方案
    config/config.default.js中关闭csrf防御方案。

    config.security = {
        csrf: {
          enable: false,
        }
      }
    

    三、常见的安全漏洞
    1、XSS攻击:对Web页面注入脚本,使用JavaScript窃取用户信息,诱导用户操作。
    2、CSRF攻击:伪造用户请求向网站发起恶意请求。
    3、钓鱼攻击:利用网站的跳转链接或者图片制造钓鱼陷阱。
    4、HTTP参数污染:利用对参数格式验证的不完善,对服务器进行参数注入攻击。
    5、远程代码执行:用户通过浏览器提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令。
    四、egg框架针对web常见的安全风险内置了丰富的解决方案
    1、利用extend机制扩展了Helper API,提供了各种模板过滤函数,防止钓鱼或XSS攻击。
    2、常见的Web安全头的支持。
    3、CSRF的防御方案。
    4、灵活的安全配置,可以匹配不同的请求url
    5、可定制的白名单,用于安全跳转和url过滤。
    6、各种模板相关的工作函数做预处理。
    在框架中内置了安全差价egg-security提供了默认的安全实践。
    五、PUT,更新数据
    路由:http://127.0.0.1:7000/product/update/100

    const { ctx } = this
    console.log(ctx.params)
    ctx.body = `id == ctx.params.id`
    

    六、DELETE
    路由:http://127.0.0.1:7000/product/delete/100

    const { ctx } = this
    console.log(ctx.params)
    ctx.body = `id == ctx.params.id`
    

    02-04 Egg.js中的Service服务

    一、服务(Service)
    简单来说,Service就是在复杂业务场景下用于做业务逻辑封装的一个抽象层,提供这个抽象有以下几个好处:
    1、保持Controller中的逻辑更加简洁
    2、保持业务逻辑的独立性,抽irc象出来的Service可以被多个Controller重复调用。
    3、将逻辑和展现分离,更容易编写测试用例。
    二、使用场景
    1、复杂数据的处理,比如要展现的信息需要从数据库获取,还要经过一定的规则计算,才能返回一用户显示。或者计算完成后,更新到数据库。
    2、第三方服务的调用,比如GitHub信息获取等。
    三、Service服务一般放在app/service文件夹下

    02-05 Egg.js中模板引擎的使用

    一、View模板渲染
    绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。故我们需要引入对应的模板引擎。
    框架内置egg-view-ejs作为模板解决方案,并支持多模板渲染,每个模板引擎都以插件的方式引入,但保持渲染的API一致。
    二、默认情况下,模板引擎文件会放在app/view文件夹下
    三、plugin.js

    exports.ejs = {
      enable: true,
      package: 'egg-view-ejs',
    }
    

    .config.default.js

    const path = require('path'); // Nodejs内置模块,可解析文件路径
      config.view = {
        mapping: {
          '.html': 'ejs'
        },
        root: path.join(appInfo.baseDir, 'app/html'); // 修改index.html等html文件存放的文件夹目录,这是存放在app/html文件夹下
        root: [ // 多目录配置
        	path.join(appInfo.baseDir, 'app/view'),
        	path.join(appInfo.baseDir, 'app/html')
    	]
      }
      config.ejs = {
      	delimiter: "$", // 全局修改分隔符, index.html中用<$= id $>代替<%= id %>
    	}
    

    四、
    1、刷新页面,会报错404 not found
    因为egg.js中每个方法几乎都是使用同步模式,返回的是promise,所以需要使用await

    const { ctx } = this;
    ctx.render('index.html')
    

    修改成:

    const { ctx } = this;
    await ctx.render('index.html')
    

    五、
    controller/home.js

    await ctx.render('index.html', {
          res,
          lists: ['a', 'b', 'c']
        }, {
    		delimiter: '$', // 支持对单个文件进行分隔符修改
    	})
    

    view/index.html

    <!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>
      <link rel="stylesheet" href="/public/css/main.css">
    </head>
    <body>
      <div>这是测试</div>
      <img src="/public/img/img.jpg">
      <h1>测试样式</h1>
      <p><%=res.id%></p>
      <%# 这是注释 %>
      <ul>
        <%for(var i=0; i<lists.length; i++){%>
        <li><%=lists[i]%></li>
        <%}%>
      </ul>
    </body>
    </html>
    

    六、为什么要学后端的模板引擎
    1、后端渲染由来以及,渲染性能得到业界认可
    2、利于SEO优化,对纯展示类网站体验较好(如公司官网、某些运营页面)
    3、对前后端分离开发模式的补充(单点登录的登录页面)
    七、Ejs模板引擎中静态资源的使用和配置
    view/user.html

    <% include user-header.html %>
    

    八、egg项目中,静态资源默认是放在public的
    九、egg项目会默认引入egg-static,egg-static是引用了koajs的static-cache这个插件,然后进行二次封装。
    config.default.js

    config.static = {
    	perfix: "/assets/", // 将静态资源存放在assets目录下,默认是public文件夹
    	dir: path.join(appInfo.baseDir, "app/assets"),
    }
    

    index.html

    <link rel="stylesheet" type="text/css" href="assets/css/user.css">
    
    <script src="assets/js/user.js"></script>
    

    02-07 Egg.js中静态资源的使用

    一、默认情况下,静态资源会放在app/public文件夹下
    1、public/img,存放图片资源
    2、public/css,存放css资源
    3、public/css,存放css资源

    第03章 项目实战-前端开发

    03-01 搭建前端系统,引入vant组件库

    一、vue-cli

    npm install -g @vue/cli
    vue --version
    vue --help
    vue create client
    mkdir client & cd client
    npm run serve
    
    

    二、h5移动端组件库vant
    1、vant:轻量、可靠的移动端Vue组件库

    npm i --save vant
    

    2、引入组件的方式
    自动按需引入组件(推荐):用到babel-plugin-import这个babel插件
    手动按需引入组件
    导入所有组件
    通过CDN方式
    三、.babelrc中

    {
    	"plugins": [
    		[
    			"import", {
    				"libraryName": "vant",
    				"libraryDirectory": "es",
    				"style": true
    			}
    		]
    	]
    }
    

    引入vue-router

    一、npm i vue-router
    二、App.vue

    <div id="app">
    	<router-view></router-view>
    </div>
    

    1、src/router/index.js,引入vue-router,配置routes

    Vue.use(VueRouter)
    const router = new VueRouter({
    	mode: 'hash',
    	routes: []
    })
    

    2、main.js

    import router from './router'
    new Vue({
    	router,
    	render: h => h(App)
    }).$mount('#app')
    

    03-05 开发文章详情页面

    一、this.$router.push()进行路由的跳转

    第04章 项目实战-后端开发

    04-01 创建mysql表结构

    一、数据库
    终端操作

    mysql -uroot -p;
    
    show databases;
    
    create database egg_article;
    
    use egg_article;
    create table article(
      id int(10) not null auto_increment,
      img text default null comment '缩略图',
      title varchar(80) default null comment '文章标题',
      summary varchar(300) default null comment '文章简介',
      content text default null comment '文章内容',
      createTime timestamp default null comment '发布时间',
      primary key(id)
    )engine=InnoDB AUTO_INCREMENT=1 comment '文章表';
    
    insert into article(img, title, summary, content, createTime) values('编程必备', 'https://img2.mukewang.com/szimg/5d1032ab08719e0906000338.jpg', '介绍编程必备基础知识', '快速、系统补足您所需要的知识内容', '2019-08-10 10:20:20');
    
    use egg_article;
    
    show tables
    

    在这里插入图片描述

    desc article;
    

    在这里插入图片描述

    select * from article;
    

    在这里插入图片描述

    04-02 egg.js连接mysql数据库

    一、egg-mysql
    框架提供了egg-mysql插件来访问MySQL数据库。这个插件既可以访问普通的MySQL数据库,也可以访问基于MySQL协议的在线数据库服务。
    二、

    npm i egg-mysql
    

    04-04 引入moment.js处理时间问题

    一、

    npm i moment
    

    04-05 egg.js开发文章首页和详情页接口

    一、插入数据

    const result = await app.mysql.insert('article', params);
    

    二、查询文章列表

    const result = await app.mysql.select('article');
    

    三、查询文章详情
    1、查询单个表,使用get方法

    const result = await app.mysql.get('article', { id })
    

    04-06 前后端接口联调

    一、proxy的changeOrigin:开启虚拟服务器,让虚拟服务器去请求代理服务器,这样就相当于是两台服务器之间进行数据的交互,就不用担心跨域的问题。

    20210228~:《用 React+React Hook+Egg 造轮子 全栈开发旅游电商应用》

    课程地址:https://coding.imooc.com/class/452.html

    第01章 关于这门课,你需要知道的

    01-01 课前须知,这里有你需要了解的一切

    一、课程涉及框架
    1、react
    umiJS:react工具集锦,路由管理、数据mock,其他相关插件
    egg.js:是nodejs的一个框架,有比较严格的项目管理规范,支持路由配置,controller配置器,service服务,模板渲染,默认支持很多实用的插件
    2、
    自定义hook:提高研发效率
    数据流工具think-react-store: react数据流解决方案
    Project-libs:常用函数集锦
    IntersectionObserver:滚动加载,图片懒加载
    二、后端核心技术
    egg.js:主框架
    jwt:用户验证
    Mysql:数据存储
    Sequelize:ORM框架,操作mysql
    扩展egg框架:提交研发效率
    自定义中间件和插件:拦截请求等处理
    三、技术架构图
    在这里插入图片描述
    四、前端干货
    1、自定义hook
    useTitleHook:动态修改浏览器title
    useHttpHook: 发送http请求,对某些状态值进行监听
    useObserverHook: 监听dom元素是否进入展示区域
    useImgHook: 实现图片懒加载
    2、自定义组件
    createPortal:createPortal在根节点之外创建新的节点
    Modal:
    ErrorBoundary:捕获错误,展示友好信息
    MenuBar:底部Menu
    LazyLoad: 懒加载
    ShowLoading:底部加载
    五、后端干货(手把手开发)
    1、中间件
    httpLog: 日志中间件
    userExist:判断用户是否存在
    2、插件
    egg-auth:验证用户
    egg-info:获取系统信息
    3、框架扩展
    application:egg.js的应用实例
    helper:帮助函数,将要使用的函数全都挂载到egg.js中,这样使用时无需引入,直接用helper即可
    requst:对请求来进行扩展
    context:对上下文扩展
    response: 对response扩展
    六、课程安排
    react基础进阶 -> 开发组件 -> 开发自定义hook -> egg.js基础 -> egg.js高级 -> 前端界面开发 -> 后端接口开发 -> 系统安全处理 -> 项目发布
    七、学习基础
    熟悉react.js基础语法
    了解node.js基础语法
    对全栈开发感兴趣

    第02章 React核心内容梳理及自定义组件开发

    02-01 本章概览

    一、组件经历的不同阶段在这里插入图片描述
    二、组件之间的通信
    1、父子组件通信
    2、兄弟组件通信
    3、组件跨页面通信
    三、Lazy和Suspence实现懒加载
    三、组件报错了怎么办:ErrorBoundary
    可将错误信息上传,便于错误的排查
    四、CreatePortal创建自定义弹窗
    五、章节介绍
    UmiJs入门介绍
    React组件生命周期
    React组件通信方式
    Dva数据处理和mock
    Context实现数据流管理
    组件懒加载
    ErrorBoundary错误边界
    createPortal自定义弹窗
    Ref api操作dom和组件

    02-02 前端开发环境搭建

    一、vscode设置
    command + shift + p:选择将”code“命令添加到PATH
    在这里插入图片描述
    这样就在终端输入code . 就可以在终端打开vscode编辑器了。
    二、依赖包管理工具:npm、yarn

    02-03 (章节缺失)

    02-08 React组件通信

    一、父组件向子组件传值
    二、子组件向父组件传值
    三、兄弟组件之间的传值
    1、父组件作为中间层
    2、如果嵌组件套得非常多,【父组件作为中间层】的方式就不可行了。
    (1)dva
    (2)context api

    02-09 Dva数据处理及数据mock

    一、models下的namespace是可选的,如果有,就取namespce,否则取文件名。
    二、reducers:同步的方法
    effects:异步的方法
    call:调用异步函数
    put: 事件派发

    // 同步
    reducers: {
    	getLists(state, action) {
    		return {
    			...state,
    			lists: Array(10).fill(action.payload)
    		}
    	},
    },
    // 异步
    effects: {
    	*getListsAsync({ payload }, { call, put }) {
    		yield put({
    			type: 'getLists',
    			payload,
    		})
    	}
    }
    

    三、umi的mock功能是对express的封装。

    02-10 基于react context api实现数据流管理

    一、子组件(消费者组件),订阅Provider的属性,Provider组件值改变,消费者组件会被重新渲染。
    1、contextType
    2、consumer

    02-11 LazyLoad组件开发【基于lazy与suspense实现的懒加载组件】

    一、.umirc.js 中dynamicImport: true, // 按需加载
    1、页面级别:每个页面内所有组件打包成1个js
    二、希望对每个页面的每个组件实现按需加载。

    const Demo = lazy(() => import('./demo'))
    
    <Suspense fallback=(<div>loading...</div>)
    	<Demo />
    </Suspense>
    

    三、components/LazyLoad/index.js

    _renderLay = () => {
    	let lazy;
    	const { component, delay, ...other } = this.props;
    	if (!component || component.constructor.name != 'Promise') {
    		Lazy = import('./error')
    	}
    	Lazy = lazy(() => {
    		return new Promise(resolve) => {
    			setTimeout(() => {
    				resolve(component)
    			}, delay || 300)
    		}
    	})
    	return <Lazy {...other} />
    }
    

    02-12 ErrorBoundary组件开发[基于React错误边界技术实现的组件]

    一、错误处理:react提供的2个构造函数
    getDerivedStateFromError()
    componentDidCatch()
    二、src/components/ErrorBoundary/index.js

    import React, { Component } from 'react';
    export default class ErrorBoundary extends Component {
    	constructor(props) {
    		super(props);
    		this.state = {
    			flag: false,
    		}
    	}
    	static getDerivedStateFromError(error) {
    		return {
    			flag: true,
    		}
    	}
    	/* error: 抛出的错误
    	* info: 带有componentStack key的对象,其中包含有关组件引发错误的栈信息
    	*/
    	copmponentDidCatch(error, info) {
    		
    	}
    	render() {
    		return (
    			<div>
    				{this.state.flag ? <h1>发生错误,请稍后再试!</h1> : this.props.children}
    			</div>
    		)
    	}
    }
    

    1、layouts/index.js中引入ErrorBoundary组件
    错误边界是在父组件监测子组件的错误,并不能监测本身发生的错误。
    三、可选技术
    house?.info?.activity

    02-13 Modal组件开发【基于createPortal创建自定义弹窗组件】

    一、ErrorBoundary,无法处理组件点击事件内部的错误,setTimeout内部的错误。
    二、src/CreatePortal/index.js

    import React, { Component } from 'react';
    import ReactDOM from 'react-dom';
    
    export default class CreatePortal extends Component {
    	constructor(props) {
    		super(props);
    		this.body = document.querySelector('body');
    		this.el = document.createElement('div');
    	}
    	
    	componentDidMount() {
    		this.el.setAttribute('id', 'protal-root');
    		this.body.appendChild(this.el);
    	}
    
    	componentWullUnmount() {
    		this.body.removeChild(this.el);
    	}
    	render() {
    		return ReactDOM.createPortal(this.props.children, this.el)
    	}
    }
    

    createPortal(react node节点,希望插入的dom节点)

    02-14 使用ref api来操作dom和组件

    一、数据流之外,父组件操作子组件,或操作内部组件dom元素,则可以用ref

    第03章 React Hooks开发模式详解及自定义hook开发

    03-01 react hook api - 新的组件开发模式

    一、hook api主要用于function 类型的组件
    二、useEffect方法虽然可以执行异步函数,但是不支持async, await

    // 错误写法
    useEffect(async() => {
    
    })
    

    1、async方法写在useEffect方法里面

    useEffect(() => {
    	async function demo() {
    		console.log('use')
    	}
    	demo
    }, [count])
    

    2、async写在useEffect外面

    async function demo() {
    	console.log('demo')
    }
    useEffect(() => {
    	console.log('use')
    	demo()
    }, [count])
    

    三、useLayoutEffect是在所有dom渲染完后,才同步执行useLayoutEffect。
    一般在这个方法内做dom操作。

    03-03 useTitleHook【根据url修改页面title的自定义hook】

    一、useTitleHook.js

    import { useLayoutHook, useState } from 'react';
    
    export default function useTitleHook(title) {
    	const [state, setState] = useState();
    	useLayoutEffect(() => {
    		document.title = title;
    		setState(title)
    	}, [title])
    	return state;
    }
    

    二、jsconfig.json
    1、配置vscode相关的配置

    {
    	"compilerOptions": {
    		"baseUrl": "src",
    		"paths": {
    			
    "@/hooks": ["hooks/index"]
    		}
    	}
    }
    

    2、这样import { useTitleHook } from ‘@/hooks’;,鼠标放到@/hooks上,就能看到是从哪里引入的

    03-05 使用think-react-store实现数据处理(基于react hooks和context实现的数据流工具)

    一、think-react-store:基于react hooks和context实现的数据流工具
    二、
    store/stores/user.js

    export default {
    	state: {
    		id: undefined,
    		username: undefined,
    	}
    	reducers: {
    		getUser(state, payload) {
    			return {
    				...state,
    				...payload,
    			}
    		}
    	},
    	effects: {
    		async getUserAsync(dispatch, rootState, payload) {
    			await new Promise(resolve => {
    				setTimeout(() => {
    					resolve();
    				}, 1000)
    			})
    			dispatch({
    				type: 'getUser',
    				payload,
    			})
    		}
    	}
    }
    

    store/stores/index.js

    import { default as User } from './user';
    

    store/index.js

    import React, { useState, useEffect } from 'react';
    import { StoreProvider } from 'think-react-store';
    import * as store from './stores';
    import log from 'think-react-store/middleware/log';
    import User from './user';
    
    export default function(props) {
    	const [state, setState] = useState();
    	useEffect(() => {
    		
    	}, [])
    	return (
    		<StoreProvider store={store} middleware={[log]}>
    			<User />
    		</StoreProvider>
    	)
    }
    

    store/user.js

    import React, { useState, useEffect } from 'react';
    import { useStoreHook, useStateHook, useDispatchHook } from 'think-react-store';
    
    export default function(props) {
    	const [state, setState] = useState();
    	const { user: { id, usename, getUserAsync } } = useStoreHook();
    	const states = useStateHook(); // const states = useStateHook('user'); // 仅返回用户相关的全局state
    	console.log(states);
    	const dispatch = useDispatchHook(); //const dispatch = useDispatchHook('user'); // 这样dispatch()里就不用写key
    	
    	
    	const handleClick = () => {
    		getUserAsync({
    			id: 20,
    			usename: 'admin2',
    		})
    	}
    	const handleClick2 = () => {
    		dispatch({
    			key: 'user',
    			type: 'getUserAsync',
    			payload: {
    				id: 20,
    				username: 'admin2'
    			}
    		})
    	}
    
    	useEffect(() => {
    		getUserAsync({
    			id: 10,
    			usename: 'admin',
    		})
    	})
    	return (
    		<div>
    			user-id: {id}
    			<br/>
    			username: {username}
    			<br/>
    			<button onClick={handleClick}>修改</button>
    		</div>
    	)
    }
    

    03-05 Fiber架构解析

    一、为什么需要Fiber架构
    react16之前的渲染流程
    在这里插入图片描述
    Fiber架构:渲染阶段分成调度阶段、提交阶段
    在这里插入图片描述
    二、Fiber的执行流程
    在这里插入图片描述

    在这里插入图片描述
    Fiber是一个js对象数据结构。

    三、Fiber对React生命周期API的影响
    调度阶段是可以执行多次的。所以比如发送请求等不适合放在调度阶段。
    在这里插入图片描述

    第04章 为什么应用Egg.js

    04-01 Egg.js企业及开发的利器概述

    一、企业级应用的特点
    功能完善
    规范性高
    便于扩展(插件方面的扩展,帮助函数方面的扩展)、升级
    二、Egg.js的特点
    提供基于Egg定制上层框架的能力
    高度可扩展的插件机制(有别于中间件模式)
    内置多进程管理
    基于koa开发,性能优异
    框架稳定,测试覆盖率高
    渐进式开发
    三、egg.js与koa/express对比

    特性对比 Egg.js Express/Koa
    代码的规范性(是否提供明确的MVC架构开发模式) 三层架构:Controller/Service/View,具有明确的开发和命名规范 可以灵活编写代码,没有明确规范
    学习成本
    插件机制/框架扩展机制 无(有中间件机制)
    多进程管理 无(实现:引用第三方插件,或自己手动封装)
    HttpClient集成 无(实现:引用第三方插件资源)

    04-02 Egg.js初体验

    app是Egg的一个实例
    在这里插入图片描述

    04-03 Node.js中近程

    一、nodejs单线程,单进程
    二、有时需要子进程中执行某些shell命令
    三、进程
    1、child_process模块
    2、cluster模块
    nodejs只能用cpu中的某一个内核,这样会造成极大的浪费
    3、master进程与cluster进程的通信
    四、const { exec, spawn } = require('child_process');
    exec、spawn都是用来创建子进程的。
    exec:创建子进程,并且将进程执行的结果缓存起来,之后将缓存的结果返回给回调函数。
    spawn:返回的是一个stream流
    五、child_process.js

    const { exec, spawn } = require('child_process');
    exec('cat a.js', (error, stdout, stderr) => {
    });
    
    const ls = spawn('ls', ['-a'], { encoding: 'utf8' });
    ls.stdout.on('data', (data) => {
    
    });
    
    ls.stderr.on('data', (data) => {
    
    });
    
    ls.on('close', (code) => {
    	
    })
    
    

    六、cluster.js

    const cluster = require('cluster');
    const http = require('http');
    const os = require('os');
    
    const cpus = os.cpus().length;
    console.log(cpus)
    
    if (cluster.isMaster) {
    	console.log('主进程 ${process.pid} 正在运行');
    	
    	// 	衍生工作进程
    	for (let index = 0; index < cpus; index++) {
    		cluster.fork();
    	}
    } else {
    	// 工作进程可以共享任何tcp连接
    	// 这里我们共享的是一个http服务器
    	http.createServer((req, res) => {
    		res.writeHead(200, {'Content-type': 'text/html; charset=utf-8'});
    		res.write('你好');
    		res.end()
    	}).listen(8000)
    	console.log(`工作进程 ${process.id} 已经启动`)
    }
    

    第05章 Egg.js基础-路由/控制器/服务/模板引擎等

    05-01 Egg.js中Controller的使用和单元测试

    一、Controller
    Controller中的方法可以是同步的,也可以是异步的。但是egg.js规定Controller里的方法是异步的。
    二、测试文件是以.test.js为后缀的。
    三、user.test.js

    'use strict';
    
    const { app } = require('egg-mock/bootstrap');
    
    describe('user test', () => { // arguments:测试用例的名称,回调函数
    	it('user index', () => {
    		return app.httpRequest()
    			.get('/user')
    			.expect(200)
    			.expect('user index')
    	});
    
    	it('user lists', async () => {
    		await app.httpRequest()
    			.get('/user/lists')
    			.expect(200)
    			.expect('[{"id":123}]')
    	})
    }); 
    

    终端执行yarn test

    05-03 Egg.js路由中post, put, delete等请求的处理及参数校验

    一、参数校验:egg-validator
    plugin.js

    exports.validate = {
    	enable: true,
    	package: 'egg-validate'
    }
    

    app/user.js

    const rule = {
    	name: { type: 'string' },
    	age: { type: 'number' }
    }
    ctx.validate(rule)
    

    05-04 Egg.js中的Service服务和单元测试

    test/app/service/user.test.js

    'use strict';
    const { app, assert } = require(''egg-mock/bootstrap');
    
    deacribe('service user test', () => {
    	it.only('test detail', async () => {
    		const ctx = app.mockContext();
    		const user = await ctx.service.user.detail(10);
    		assert(user);
    		assert(user.id === 10);
    	})
    })
    

    05-07 Egg.js中的cookie设置和使用以及如何设置中文cookie

    一、Cookie
    HTTP请求都是无状态的,但是我们的Web应用通常需要知道发起请求的人是谁。为了解决这个问题,HTTP协议涉及了一个特殊的请求头:Cookie。服务端可以通过响应头(set-cookie)将少量数据响应给客户端,浏览器会遵循协议将数据保存,并在下次请求同一个服务的时候带上(浏览器也会遵循协议,只在访问符合Cookie指定规则的网站时带上对应的Cookie来保证安全性)
    1、Cookie是运行在浏览器上的
    二、通过ctx.cookies,可以在controller中便捷、安全地设置和读取Cookie
    三、egg.js中默认对Cookie进行了集成,封装在ctx上下文。
    ctx.cookies

    const cookies = ctx.cookies.get('user');
    ctx.cookies.set('user', JSON.stringify(body));
    ctx.cookies.set('user', JSON.stringify(body), {
    	maxAge: 1000 * 60 * 10, // 过期时间
    	httpOnly: true, // 只允许服务端操作cookie, document.cookie获取不到cookie的值
    })
    

    四、egg.js无法直接设置中文cookie。解决方法
    1、加密

    ctx.cookie.set('zh', '测试', {
    	encrypt: true,
    });
    const zh = ctx.cookies.get('zh', {
    	encrypt: true,
    });
    

    2、base64
    app/controller/user.js

    encode(str = '') {
    	return new Buffer(str).toString('base64');
    }
    decode(str = '') {
    	return new Buffer(str, 'base64').toString();
    }
    
    ctx.cookies.set('base64', this.encode('中文base64'));
    const base64 = this.decode(ctx.cookies.get('base64'));
    

    05-08 Egg.js中session的配置和使用

    一、session
    Session的实现是基于Cookie的,默认配置下,用户Session的内容加密后直接存储在Cookie中的一个字段中,用户每次请求我们网站的时候都会带上这个Cookie,我们在服务端解密后使用。
    二、Session、Cookie的区别

    异同 Cookie Session
    存储数据 是,存储在浏览器 是,存储在浏览器、内存或redis等其他数据源中
    操作环境 客户端和服务端均可操作 服务端操作session
    大小限制 有大小限制并且不同浏览器存储cookie的个数也有不同 没有大小限制单核服务器的内存大小有关;但如果session保存到cookie里会受到浏览器限制
    是否唯一 是,不同用户cookie存放在各自的客户端 是,服务端会给每个用户创建唯一的session对象
    安全问题 有安全隐患,通过拦截本地文件找到cookie后可以进行攻击 安全性高,浏览器可以获取session但难以解析
    常用场景 判断用户是否登录 保存用户登录信息
    四、
    async index() {
    	const session = ctx.session.user;
    	console.log(session);
    }
    // 保存session
    ctx.session.user = body;
    

    五、session是可以直接支持中文的
    六、config.default.js

    config.session = {
    	key: 'IMOOC',
    	httpOnly: true, // 实际项目中都是用true,来提高安全性
    	maxAge: 1000 * 5,
    	renew: true,
    }
    

    2、对session进行扩展
    app.js

    
    module.exports = app => {
    	const store = {}
    	app.sessionStore = {
    		async get(key) {
    			console.log('--store--', store);
    			return store[key]
    		}
    		async set(key, value, maxAge) {
    			store[key] = value
    		}
    		async destroy(key) {
    			store[key] = null;
    		}
    	}
    }
    

    05-09 Egg.js中使用HttpClient请求其他接口

    一、User server, Article server, Order server, Other server
    二、
    contoller/home.js

    class HomeController extends Controller {
    	async index() {
    		const { ctx } = this;
    		const res = await ctx.service.user.detail(20);
    		console.log(res);
    		ctx.body = 'hi, egg';
    	}
    }
    

    contoller/curl.js

    class CurlController extends Controller {
    	async curlGet() {
    		const { ctx, app } = this
    		const res = await ctx.curl('http://localhost:7001/', {
    			dataType: 'json',
    		})
    		console.log(res);
    		ctx.body = {
    			status: 200,
    			data: res.data,
    		}
    	}
    }
    

    第06章 Egg.js高阶-插件中间件扩展等

    06-01 中间件

    一、中间件是按照顺序,由外而内,一层层地执行,并且每个中间件都会执行2次。
    实际项目中,我们一般使用中间件对请求进行拦截。
    在这里插入图片描述
    二、app/middleware
    m1.js

    module.exports = options => {
    	return async(ctx, next) => {
    		console.log('m1 start');
    		await next();
    		console.log('m1 end');	
    	}
    }
    

    m2.js

    module.exports = options => {
    	return async(ctx, next) => {
    		console.log('m2 start');
    		await next();
    		console.log('m2 end');	
    	}
    }
    

    httpLog.js

    const dayjs = require('dayjs');
    const fs = require('fs'); // 引入文件处理模块
    
    module.exports = options => {
    	console.log(options) // config.default.js中的 config.httpLog对象就是它的参数
    	return async (ctx, next) {
    		const sTime = Date.now();
    		const startTime = dayjs( Date.now()).format('YYYY-MM-DD HH:mm:ss');
    		await next(); // 如果没有这行,浏览器会报错:404 not found
    		const log = {
    			method: req.method,
    			url: req.url,
    			data: req.body,
    			startTime,
    			endTime: dayjs( Date.now()).format('YYYY-MM-DD HH:mm:ss'),
    			timeLength: Date.now() - sTime
    		}
    		// console.log(log)
    		const data = dayjs( Date.now()).format('YYYY-MM-DD HH:mm:ss') + ' [httpLog] ' + JSON.stringify(log) + '\r\n';
    		fs.appendFileSync(ctx.app.baseDir + 'httpLog.log', data)
    	}
    }
    

    三、config.default.js

    // config.middleware = ['m1', 'm2'];
    config.middleware = ['httpLog'];
    config.httpLog = { // 中间件的参数
    	type: 'all',
    }
    

    06-02 丰富的扩展方式

    一、扩展方式

    扩展点 说明 this指向 使用方式
    application 全局应用对象 app对象 this.app
    context 请求上下文 ctx对象 this.ctx
    request 请求级别的对象,提供了请求相关的属性和方法 ctx.request对象 this.ctx.request
    response 请求级别的对象,提供了响应相关的属性和方法 ctx.response对象 this.ctx.response
    helper 帮助函数 ctx.helper对象 this.ctx.helper

    二、扩展一般放在app/extend文件夹下
    三、对application的扩展有2个,1是方法层面的扩展,2是属性的扩展
    四、app/extend/application.js

    const path = require('path');
    module.exports = {
    	// 方法扩展
    	package(key) {
    		const pack = getPack();
    		return key ? pack[key] : pack;
    	}
    	// 属性扩展
    	get allPackage() {
    		return getPack();
    	}
    }
    function getPack() {
    	const filePath = path.join(process.cwd(), 'package.json');
    	const pack = require(filePath);
    	return pack;
    }
    

    app/controller/home.js

    class HomeController extends Controller {
    	async newApplication() {
    		const { ctx, app } = this;
    		const packageInfo = app.package('scripts');
    		const allPack = app.allPackage;
    		ctx.body = 'newApplication';
    	}
    	// 对context上下文进行扩展
    	async newContext() {
    		const { ctx } = this;
    		const params = ctx.params();
    		ctx.body = 'newContext';
    	}
    	async newRequest() {
    		const { ctx } = this;
    		const token = ctx.request.token
    		ctx.body = token;
    	}
    	async newResponse() {
    		const { ctx } = this;
    		ctx.response.token = 'abc123';
    		const base64Parse = ctx.helper.base64Encode('newResponse');
    		
    		ctx.body = base64Parse;	
    	}
    }
    

    app/extend/context.js
    获取get/post的参数是使用不同的方式,现在我们希望用的是同一种方式
    context.js

    module.exports = {
    	params(key) {
    		const method = this.request.method;
    		if (method === 'GET') {
    			return key ? this.query[key] : this.query;
    		} else {
    			return key ? this.request.body[key] : this.request.body;
    		}
    	}
    }
    

    app/extend/request.js
    一般情况下,对request, response的扩展一般都是对属性的扩展

    module.exports = {
    	// 获取相关的token
    	get token() {
    		console.log('header', this.header);
    		return this.get('token');
    	}
    }
    

    app/extend/response.js

    module.exports = {
    	// 希望能设置相关的token
    	set token(token) {
    		this.set('token', token);
    	}
    }
    

    app/extend/helper.js

    module.exports = {
    	base64Encode(str = '') {
    		return new Buffer(str).toString('base64');
    	}
    }
    

    06-04 插件机制

    一、插件
    1、中间件更适合处理请求,插件不仅可以包含中间件所有功能,还可以处理业务逻辑。
    2、Egg.js中的插件相当于一个微型应用。
    3、插件不包括router.js和controller控制器(可能会与主项目中的路由产生冲突)
    二、项目目录下新建lib文件夹,插件都会放在lib文件夹下的plugin文件夹下,plugin里的文件一般以egg-开头
    lib/plugin/egg-auth/app/package.json

    {
    	"name": "egg-auth",
    	"eggPlugin": {
    		"name": "auth"
    	}
    }
    

    lib/plugin/egg-auth/app/middleware/auth.js

    module.exports = options => {
    	return async (ctx, next) => {
    		const url = ctx.request.url;
    		const user = ctx.session.user;
    		if (!user && !options.exclude.includes(ctx.request.url.split('?')[0])) {
    			ctx.body = {
    				status: 1001,
    				errMsg: '用户未登录',
    			}
    		} else {
    			await next();
    		}
    	}
    }
    

    config/plugin.js
    这里用的是本地的插件,不能用pacakge属性,package属性一般是指线上安装的依赖包
    path与package属性是互斥的。

    exports.auth = {
    	enable: true,
    	path: path.join(__dirname, '../lib/plugin/egg-auth')
    }
    

    app.js

    module.exports = app => {
    	app.config.corMiddleware.push('auth');
    }
    

    config/config.default.js

    config.auth = {
    	exclude: ['/home', '/user', '/login', '/logout']
    }
    

    06-05 Egg.js定时任务

    一、定时任务
    1、定时上报应用状态,便于系统监控
    2、定时从远程接口更新数据
    3、定时处理文件(清除过期日志文件)
    二、
    lib/plugin/egg-info/app/extend
    三、app/schedule文件夹下存放的都是定时任务
    app/schedule/get_info.js

    require('egg').Subscription;
    
    class getInfo extends Subscription {
    	static get schedule() {
    		return {
    			interval: 3000,
    			cron: '*/3 * * * *', // 每隔3秒钟
    			type: 'worker' // 类型:'all' 、 ’worker‘, all:每个worker进程都会执行这个定时任务, worker:master进程会指定一个进程,来单独执行这个任务
    		}
    	}
    	async subscribe() {
    		const info = this.ctx.info;
    		console.log(Date.now(), info)
    	}
    }
    
    module.exports = getInfo;
    

    第07章 Egg.js操作Mysql数据库

    07-01 安装Mysql数据库

    一、
    show database;
    create database egg;
    二、可视化数据图工具
    mysql workbench

    07-02 mysql入门,基础增删改查操作

    一、show databases;
    二、demo.sql

    -- 删除数据库
    drop database egg;
    
    -- 创建数据库
    create database egg;
    
    -- 创建表
    use egg;
    
    create table user(
    	id int(10) not null auto_increment,
    	name varchar(20) not null default 'admin' comment '用户名',
    	pwd varchar(50) not null comment '密码',
    	primary key(id)
    )engine=InnoDB charset=utf8;
    
    -- 查看表
    show tables;
    
    -- 查看表结构
    desc user;
    
    -- 删除表
    drop table user;
    
    -- 插入表数据
    insert into user values(1, 'user1', '123');
    insert into user(name, pwd) values('user2', '123');
    
    -- 查询表数据
    select * from user;
    select id, name from  user;
    select id, name from user where id = 1;
    
    -- 修改表数据
    update user set pwd = '123456' where id = 1;
    
    -- 删除表数据
    delete from user where id = 2;
    

    07-03 使用egg-mysql操作数据库

    一、

    yarn add egg-mysql
    
    yarn dev
    

    config/plugin.js

    exports.mysql = {
    	enable: true,
    	package: 'egg-mysql'
    }
    

    config/config.default.js

    config.mysql = {
    	app: true, // 是否将mysql挂载到app下
    	agent: false,
    	client: {
    	 host: '127.0.0.1',
    	 port: '3306',
    	 user: 'root',
    	 password: 'abc123456',
    	 database: 'egg',
    	}
    }
    

    二、egg-mysql的使用方式比较简单,比较适合中小型项目

    07-04 Egg.js中使用Sequelize操作mysql

    一、sequelize是一个crm框架
    二、

    yarn add egg-sequelize mysql2
    
    yarn dev
    
    exports.sequelize = {
    	enable: true,
    	package: 'egg-sequelize'
    }
    
    config.sequelize = {
    	dialect: 'mysql', // 数据源
    	host: '127.0.0.1',
    	port: '3306',
    	user: 'root',
    	password: 'abc123456',
    	database: 'egg',
    	define: {
    		timestamps: false, // 在使用sequelize时,不需要sequelize这个框架为我们自动添加时间相关的字段
    		freezeTabelName: true, // 冻结表名称,使用sequelize的时候,使用原始的表名称,而不需要sequelize框架额外地处理表名称
    	}
    }
    

    三、app/model,里面是我们的模型文件
    app/model/user.js

    module.exports = app => {
    	const { STRING, INTEGER } = app.Sequelize;
    	const User = app.model.define('user', { // 模型名称,一般是表名称, 'user'
    		id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    		name: STRING(20),
    		pwd: STRING(50)
    	}); 
    	
    }
    

    app/controller/user.js

    const res = await ctx.model.User.findAll({
    	// where: {
    		// id: 2,
    	// }
    	limit: 1,
    	offset: 1
    })
    
    const res = await ctx.model.User.findByPk(ctx.query.id);
    
    // 更新之前判断数据是否存在
    const user  = await ctx.model.User.findByPk(ctx.request.body.id);
    if (!user) {
    	ctx.body = {
    		status: 404,
    		errMsg: 'id不存在',
    	}
    	return;
    }
    const res = user.update(ctx.request.body);
    ctx.body = {
    	status: 200,
    	data: res,
    }
    

    第08章 前端界面开发及功能优化

    08-01 本章概览

    一、章节目标
    1、完成前端界面开发
    2、实现列表滚动加载、图片懒加载效果
    3、使用mock数据模拟接口(umijs)
    二、系统模块
    在这里插入图片描述
    三、技术要点
    1、IntersectionObserver,元素是否进入到可视区域
    在这里插入图片描述
    四、学习收获
    1、可以学习到前端系统的开发流程
    2、了解并实现滚动加载和图片懒加载的思路
    3、前端项目的优化思路(公共组件、缓存、骨架屏)

    08-02 实现网站的底部导航功能

    一、vscode用func命令生成新的组件
    二、react-icons,是针对react项目封装的icon

    yarn add react-icons
    
    // bootstrap的icons
    import { BsHouseDoorFill, BsHouseDoor} from 'react-icons/bs'
    

    08-04 为首页添加数据

    一、如果父组件内的子组件没有数据交互,数据请求就放在父组件中

    08-06 初识IntersectionObserver,实现UseObserverHook

    一、IntersectionObserver提供了一种异步观察目标元素与其祖先元素及顶级文档视窗(viewport)交叉状态的方法。
    二、
    在这里插入图片描述
    三、使用这个特性会比较消耗性能,一般我们会在页面初始化的时候观察这个dom节点,离开页面时取消观察

    let observer;
    useEffect(() => {
    	console.log('进入页面');
    	observer = new IntersectionObserver(entries => {
    		console.log(entries); // 重点关注intersectionRadio, isIntersecting属性
    	});
    	observer.observe(document.querySelector('#loading'));
    	return () => {
    		console.log('离开页面')
    		if(observer) {
    			// 解绑元素
    			observer.unobserve(document.querySelector('#loading'));
    			// 停止监听
    			observer.disconnect();
    		}
    	}
    	
    }, [])
    

    08-07 使用useObserverHook实现滚动加载(上)

    一、

    /**
    * 1、监听loading是否展示出来(loading:请求的节点)
    * 2、修改分页数据
    * 3、监听分页数据的修改,发送接口,请求下一页的数据
    * 4、监听loading变化,拼装数据(loading:数据是否变化的状态)
    */
    useObserverHook('#loading'), (entries) => {
    
    }, null)
    

    08-08 使用useObserverHook实现滚动加载(下)

    一、获取url参数,umi

    import { useLocation } from 'umi'
    const { query } = useLocation()
    
    body = {
    	code: query?.code
    }
    
    

    08-09 使用useImgHook实现图片懒加载

    一、useImageHook.js

    /**
    * 1、监听图片是否进入可视区域
    * 2、将src属性的值替换为真是的图片地址,data-src
    * 3、停止监听当前的节点
    */
    const useImgHook(ele, callback, watch = []) => {
    
    }
    
    const dataSrc = item.target.getAttribute('data-src');
    item.target.setAttribute('src', dataSrc);
    observer.unobserve(item.target);
    

    08-10 优化-提取公共组件,使用枚举,引入project-libs

    一、id,loading在search页面是唯一的
    二、antd-mobile的日历组件的svg标签中的元素有id是loading,所以search页面的loading这个id得改一下。
    三、enums/common.js,可导出一些常量

    export const LOADING_ID = 'mk-loading';
    
    

    enums/index.js

    import * as CommonEnum from './common';
    export {
    	CommonEnum
    }
    

    使用

    import { CommonEnum } from '@/enums'
    

    四、工具函数库
    project-libs(文档:https://cpagejs.github.io/project-libs/)

    06-11 民宿详情页面开发

    一、banner滑动
    react-awesome-swiper

    06-12 为民宿详情页添加数据流管理(上)

    一、不同组件之间的交互,useHttpHook就不适用了。
    二、数据流:think-react-store
    effects:异步方法,可以在其中发送请求
    三、滚动加载
    search页面:useHttpHook方式
    民宿详情页:数据流方式

    /**
    * 1、监听loading是否展示出来
    * 2、触发reload, 修改分页
    * 3、监听reload变化,重新请求接口
    * 4、拼装数据
    */
    

    08-13 为民宿详情页添加数据流管理(下)

    一、

    import { history } from 'umi';
    
    history.push({
    	pathname: '',
    	query: {
    		id: '',
    	}
    })
    
    import { useLocation } from 'umi';
    
    const { query } = useLocation();
    

    08-14 为订单页面添加滚动加载效果(使用onObserverHook,但不监听数据)

    一、不用进行数据监听,

    /**
    * 1、页面初始化时候请求接口,useEffect
    * 2、监听loading组件是否展示出来,useObserverHook
    * 3、修改page, pageNum+1,再次重新请求接口
    * 4、拼装数据,然后page
    */
    

    08-19 通过umi运行时配置,对页面进行登录验证

    一、cookie
    1、右上角登录/注册,如果已经登录了,就显示用户名。
    2、点击“我的”,如果未登录,则跳转到登录页面
    umi运行时配置
    src/appp.js:可以修改路由,复写render

    import { cookie } from 'project-libs';
    import { history } from 'umi';
    
    // 初始加载,路由切换的时候进行响应的逻辑
    export function onRouteChange(route) {
    	const nowPath = routes.routes[0].routes.filter(item => item.path === route.location.pathname);
    	const isLogin = cookie.get('user');
    
    	if(nowPath.length === 1 && nowPath[0].auth && !isLogin) {
    		history.push({
    			pathname: '/login',
    			query: {
    				from: route.location.pathname
    			}
    		})
    	}
    }
    

    08-20 使用React.memo减少渲染次数

    一、
    header组件多次渲染,用Memo

    import { memo } from 'react';
    
    function areEqual(prevProps, nextProps) {
    	if (prevProps.citys === nextProps.citys && prevProps.cityLoading) {
    		return true;  // 允许组件重新渲染
    	} else {
    		 return false;
    	}
    }
    export default memo(Search, areEqual);
    

    08-21 优化-订单页面添加骨架屏

    一、思路
    1、通过伪元素实现骨架样式
    (1)用伪元素是因为骨架屏只展示区块,区块不包含文字、图片
    2、制作布局组件,添加骨架样式
    3、替换默认Loading效果
    二、这章是针对单独的页面写单独的骨架屏的
    三、、global.css

    .skeletons {
    	position: relative;
    	display: block;
    	overflow: hidden;
    	width: 100%;
    	min-height: 20px;
    	background-color: #ededed;
    }
    .skeletons:empty::after {
    	display: block;
    	content: ' ';
    	position: absolute;
    	width: 100%;
    	height: 100%;
    	transform: translateX(-100%);
    	background: linear-gradient(90deg, transparent, rgba(216, 216, 216, 0.6), transparent);
    	animation: loading 1.5s infinite;
    }
    @keyframes loading { /* 骨架屏动画*/
    	from {
    		left: -100%;
    	}
    	to {
    		left: 120%;
    	}
    }
    

    四、src/skeletons
    src/skeletons/OrderSkeletons

    第09章 服务端用户模块实现及优化

    09-01 本章概览

    一、后端
    二、章节目标
    完成用户模块的接口开发
    使用JWT技术验证用户:用户信息加密,生成字符串,之后对字符串解密
    提取公共逻辑,优化系统
    三、技术要点
    redis主要保存核心数据
    mysql主要保存业务数据
    在这里插入图片描述
    四、学习收获
    1、学习如何开发登录、注册接口以及注意事项
    2、学习到如何使用JWT技术进行用户验证
    3、如何根据项目需求进行优化(框架扩展、中间件、公共类)

    09-02 创建用户表,建基于Seuelize编写用户模型

    一、创建数据库
    app.sql

    create database egg_house;
    
    use egg_house;
    
    -- 用户表
    create table `user`(
    	`id` int not null auto_increment,
    	`username` varchar(20) default null comment '密码',
    	primary key(`id`)
    )engine=InnoDB auto_increment=1 default charset=utf8 comment=‘用户表’;
    

    二、
    app/model/user.js

    module.exports = app => {
    	const { STRING, INTEGER, TEXT } = app.Sequelize;
    
    	const User = app.modeldefine('user',  {
    		id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    		username; STRING(20),
    	});
    
    	return User;
    }
    

    09-03 开发用户注册接口

    一、配置项
    config/config.default.js

    const userConfig = {
    	salt: 'muke'
    }
    

    app/controller/user.js

    const { app } = this;
    const salt = app.config.salt
    

    09-04 扩展Egg.js的帮助函数

    一、存在的问题
    1、返回给前端的数据,有些是不需要展示的(如密码)
    2、dayjsd多处使用,推荐将dayjs写成eggjs中的帮助函数
    二、app/extend/helper.js

    time() {
    	return dayjs().format('YYYY-MM-DD HH:mm:ss')
    }
    
    ctx.helper.time();
    

    三、返回的数据都有dataValuse

    ctx.session.userId = user.id;
    ctx.body = {
    	status: 200,
    	data: {
    		...ctx.helper.unPick(user.dataValues, ['password']),
    		createTime: ctx.helper.timestamp(user.createTime);
    	}
    }
    

    09-05

    展开全文
  • Egg.js实现一个简单完整的后台管理系统 最近在学习Egg.js,它是阿里推出的基于Koa的node开发框架,是为企业级框架和应用而生;它简单且扩展性强,非常适合做个人项目。 一、egg创建项目 1.要求nodejs版本必须大于...
  • 作为一名前端开发者,在...正因为如此,第一次接触它,我便喜欢上了它,之后也用它开发过不少应用。 有了如此优秀的框架,那么如何将一个Egg.js的服务迁移到Serverless架构上呢? 背景 如何将一个基于Vue.js的前...
  • 支持服务器和客户端端代码修改,Webpack时时编译和热更新, npm run dev一键启动应用 基于vue + vuex + vue-router + axios单页面服务器客户端同构实现 支持开发环境,测试环境,正式环境Webpack编译 支持js / cs
  • Egg.js快速入门

    2019-10-14 00:22:00
    基于Node.js和Koa企业级应用开发框架 特性 提供基于Egg的定制上层框架的能力 高度可扩展的插件机制 内置多进程管理 基于Koa开发性能优异 框架稳定,测试覆盖率搞 渐进式开发 涉及内容 vant ui vue-cli3 moment.js ...
  • 作为一名前端开发者,在选择 Nodejs 后端服务框架时,第一时间会想到 Egg.js,不得不说 Egg.js 是...正因为如此,第一次接触它,我便喜欢上了它,之后也用它开发过不少应用。 有了如此优秀的框架,那么如何将一个 ...
  • 近来公司需要构建一套 EMM(Enterprise Mobility Management)的管理平台,就这种面向企业的应用管理本身需要考虑的需求是十分复杂的,技术层面管理端和服务端构建是架构核心,客户端本身初期倒不需要那么复杂,作为...
  • The Egg Basket-crx插件

    2021-04-02 05:17:09
    Egg Basket是由开发人员为开发人员创建的书签应用程序。 用Stackoverflow为主题添加书签可能会很有用。 但是,当提示您为该主题添加书签的代码段被淹没在大量评论中时,可能要花一些时间才能回忆起几个月后您对哪个...
  • Egg Timer-开源

    2021-05-09 21:50:47
    一个非常简单但友好的时间跟踪桌面应用程序,它是用C#(.NET 2.0)开发的。 该项目可以用作较大项目的示例以进行时间管理
  • 功能内置过程管理插件系统框架自定义大量插件快速入门请遵循以下命令。 $ mkdir展示&& cd展示$ npm init功能内置过程管理插件系统框架自定义很多插件快速入门请遵循以下命令。 $ mkdir展示&& cd展示$ npm初始化鸡蛋-...
  • egg.js学习感想一

    2019-03-01 21:56:32
    1.Egg.js 为企业级框架和应用而生 egg特性: 1.提供基于 Egg 定制上层框架的能力 2.高度可扩展的插件机制 3.内置多进程管理 4.基于 Koa 开发,性能优异 5.框架稳定,测试覆盖率高 6.渐进式开发 egg使用场景 BFF层...
  • 同构应用:实现服务端渲染首屏优化,客户端渲染组件动态加载,按需自由切换 状态管理:、 、,服务端与客户端共享store,优化性能,action/reducer/saga 统一管理、扩展方便 样式管理:为style-jsx集成nested、lost...
  • Egg.js 1.0.0 正式发布了,egg是阿里开源的企业级 Node.js 框架,为企业级框架和应用而生。通过 egg,团队的架构师和技术负责人可以非常容易地基于自身的技术架构在 egg 基础上扩展出适合自身业务场景的框架。 特性...
  • Egg.js 是一个基于nodejs开发的,为企业级框架和应用而生,我们希望由 Egg.js 孕育出更多上层框架,帮助开发团队和开发人员降低开发和维护成本。 github地址: https://github.com/eggjs/egg 特性 提供基于 Egg...
  • 应用截图 后台截图 目录结构 ├── app // app源码 ├── client // 中后台源码 ├── serve // 服务端源码 ├── README . md // 项目说明 ├── fsxy . postman_collection . json // postman json └── ...
  • [drozer (Python .egg)- drozer-2.3.4.tar.gz ] drozer是一款针对Android系统的安全测试框架。Drozer可以通过与Dalivik VM,其它应用程序的IPC端点以及底层操作系统的交互,避免正处于开发阶段,或者部署于你的组织的...
  • 后端egg和前端vue分离开发接口验签

    千次阅读 2019-01-30 11:29:37
    在做前后台分离开发管理系统的时候,碰到了接口加密的问题,接口加密什么鬼?大概网上看了一下验签,md5加密,慢慢有思路了。 前端 前后台协商接口提供出appid(用户标识)和appsecret(应用秘钥) 前端根据appid和...
  • xlong网站cms 介绍 xlong-website-cms是一套基于Egg + Vue + Webpack开发的多页和单页服务端客户端渲染的企业CMS系统,可用于企业...企业网站:Egg + Nunjucks,服务端渲染,多页面响应式应用开发 管理后台:Vue + We
  • 即时通讯应用, 包含、和 现已部署上线,欢迎体验和 请不要随意更改默认角色和权限,请有点爱心,别整一些很不文明的名字 介绍 使用 egg 框架,IM 服务的服务端 功能简介 注册,登录,个人、群组聊天,个人信息编辑等...
  • 这是一款非常适合管理模板外壳和快速原型制作的应用程序。 已测试并与 Djago 1.3 一起使用。 安装 使用安装: $ sudo pip install -e git+https://github.com/mhulse/django-dox.git#egg=django-dox 将'dox',...
  • 即时通讯应用服务,整套包含服务端、管理端和客户端,欢迎Star支持和查看源码。 现已部署上线,欢迎体验客户端和管理端 咱们书接上文,继续完成完整的即时通讯服务,这篇着重讲下Server端项目中我认为几个重要的点,...

空空如也

空空如也

1 2 3
收藏数 57
精华内容 22
关键字:

egg应用管理