koa+react搭建前后端

山水有轻音 2021-06-08 PM 288℃ 0条

自从19年以来,基本开发都是用vue进行前端开发的,react荒废了许久了,应该说自从react学趴了,就一直在那趴着的,再加上最近学习了koa+sequelize,也想用这个做后端,写一下接口,于是就开始了这个项目

一、上效果图

1、前端展示

web.gif

2、后端展示

login.gif

二、后端使用koa+sequelize为框架,mysql作为后端数据库类型的

1、如何创建项目

1.1安装依赖

npm install -g koa-generator 

1.2 在当前路径,使用koa-generator生成koa2项目(cmd命令行)

koa2 koa-test-sequelize-auto

1.3 进入koa-test-sequelize-auto目录(如果没有下载yarn的话,请全局下载yarn。 命令行: npm install -g yarn)

yarn install 

生成的文档目录

├── bin
├── public
├── routes
├── view
├── package.json
└── app.js

1.4 启动项目

npm run dev

2、建立orm库

在当前项目中安装下面这几个依赖(cmd命令行)

yarn add sequelize
yarn add sequelize-auto
yarn add mysql
yarn add mysql2

3、开始项目搭建:采用MVC模式

在根目录下创建controllers,modules,schema,config文件夹

  • controllers:写控制逻辑部分
  • modules:写sql部分
  • schema:写数据表部分
  • config:写数据库配置部分
    如图所示

微信图片_20210608112310.png

4、使用 Sequelize 初始化连接池

在根目录创建 config/db.js 文件:(注意: 所连接的数据库必须存在,若不存在先创建数据库,创建数据库命令: CREATE DATABASE blogs DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;)

配置数据库

const Sequelize = require('sequelize')
    const sequelize = new Sequelize('koa','root','123456',{
        host:'localhost',
        dialect:'mysql',
        operatorsAliases:false,
        dialectOptions:{
            //字符集
            charset:'utf8mb4',
            collate:'utf8mb4_unicode_ci',
            supportBigNumbers: true,
            bigNumberStrings: true
        },
        pool:{
            max: 5,
            min: 0,
            acquire: 30000,
            idle: 10000
        },
        timezone: '+08:00'  //东八时区
    })
    
    module.exports = {
        sequelize
    }

5、模型创建

在schema目录下创建tags.js

const Sequelize = require("sequelize");
const moment = require("moment");
const tag = (sequelize, DataTypes) => {
  return sequelize.define(
    "tag",
    {
      id: {
        type: DataTypes.INTEGER(11),
        primaryKey: true,
        autoIncrement: true,
      },
      name: {
        type: Sequelize.STRING,
        unique: {
          msg: "已添加",
        },
        field: "name"
      },
      createdAt: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.NOW,
        get() {
          return moment(this.getDataValue("createdAt")).format(
            "YYYY-MM-DD HH:mm"
          );
        },
      },
      updatedAt: {
        type: Sequelize.DATE,
        defaultValue: Sequelize.NOW,
        get() {
          return moment(this.getDataValue("updatedAt")).format(
            "YYYY-MM-DD HH:mm"
          );
        },
      },
    },
    {
      freezeTableName: true,
    }
  );
}
module.exports = tag;

5、模型使用

在modules目录下创建tags.js

/*
 * @Author: Booble 
 * @Date: 2021-05-Th 03:45:47 
 * @Last Modified by:   Booble 
 * @Last Modified time: 2021-05-Th 03:45:47 
 */
const db = require("../config/db");
const Sequelize = db.sequelize;
const Tags = Sequelize.import("../schema/tags.js");
Tags.sync({ alter: true });

class TagsModule {
  static async gettags(query, limit) { --分页列表
    return await Tags.findAndCountAll({
      where: {
        ...query,
      },
      order: [["id", "DESC"]],
      offset: limit.offset,
      limit: limit.limit,
    });
  }
  static async gettagsAll() { --总条数
    const list = await Tags.findAll(
    {attributes: ['id', 'name']} --只显示该字段
    );
    return list
  }
  static async delTag(id) { --删除标签
    return await Tags.destroy({
      where: {
        id,
      },
    });
  }
  static async updateTag(data) { --更新便签
    const { id, name } = data;
    return await Tags.update(
      {
        id,
        name,
      },
      {
        where: {
          id,
        },
      }
    );
  }
  static async addTag(query) { --添加标签
    return await Tags.create({
      name: query.name,
    });
  }
}

module.exports = TagsModule;

6、控制层写逻辑

在controllers目录下创建tags.js

/*
 * @Author: Booble 
 * @Date: 2021-05-Th 04:36:42 
 * @Last Modified by:   Booble 
 * @Last Modified time: 2021-05-Th 04:36:42 
 */
const Sequelize = require("sequelize");
const Op = Sequelize.Op;
const TagModule = require("../modules/tags");
class TagControler {
  static async taglist(ctx) {     //查询列表
    let ctx_query = ctx.request.body;
    try {
      let pageSize = ctx_query.pageSize;
      let page = ctx_query.pageNo ? (ctx_query.pageNo - 1) * pageSize : 0;
      let sql = {
        name: {
          [Op.like]: "%" + ctx_query.name + "%",
        },
      };
      let limitpage = {
        offset: page,
        limit: pageSize,
      };
       const { rows: result, count: total } = await TagModule.gettags(
         sql,
         limitpage
       );
      ctx.response.status = 200;
      if (result) {
        ctx.body = {
          code: 200,
          data: result,
          total: total ? total: 0,
        };
      } else {
        ctx.body = {
          code: 400,
          message: "请求连接错误",
        };
      }
    } catch (err) {
      ctx.response.status = 500;
      ctx.body = {
        code: 500,
        message: "服务器错误,请求失败!",
      };
    }
  }
  static async getAll(ctx){ --获取总条数
    try {
       const data = await TagModule.gettagsAll();
       ctx.response.status = 200;
       if (data) {
         ctx.body = {
           code: 200,
           data: data,
           message: "获取成功",
         };
       } else {
         ctx.body = {
           code: 201,
           message: "获取失败",
         };
       }
    } catch (err) {
      const msg = err.errors[0];
      ctx.body = {
        code: 500,
        message: msg.value + msg.message,
      };
    }
  }
  static async addtag(ctx) {
    let query = ctx.request.body;
    if (!query.name) {
      ctx.response.status = 200;
      ctx.body = {
        code: 1003,
        desc: "标签不能为空",
      };
      return false;
    }
    try {
      const data = await TagModule.addTag(query);
      ctx.response.status = 200;
      if (data) {
        ctx.body = {
          code: 200,
          message: "新增成功",
        };
      } else {
        ctx.body = {
          code: 201,
          message: "新增失败",
        };
      }
    } catch (err) {
      const msg = err.errors[0];
      ctx.body = {
        code: 500,
        message: msg.value + msg.message,
      };
    }
  }
  static async deletag(ctx) {   //删除
    //const { id } = ctx.request.body;
    const { id } = ctx.params;
    try {
        const result = await TagModule.delTag(id);
        if (result) {
          ctx.response.status = 200;
          ctx.body = {
            code: 200,
            message: "数据删除成功",
          };
        }
      } catch (err) {
        ctx.response.status = 500;
        ctx.body = {
          code: 500,
          message: "服务器错误,请求失败!",
        };
      }
  }
  static async updatetag(ctx) {    //更新
    let query = ctx.request.body;
    try {
      const result = await TagModule.updateTag(query);
      if (result) {
        ctx.response.status = 200;
        ctx.body = {
          code: 200,
          message: "数据更改成功",
        };
      }
    } catch (err) {
      ctx.response.status = 500;
      ctx.body = {
        code: 500,
        message: "服务器错误,请求失败",
      };
    }
  }
}
module.exports = TagControler;

7、router接口配置

在routes目录下创建tags.js

const router = require("koa-router")();

const Tag = require("../controller/tags");

router.prefix("/api"); --路由前缀

router.post("/taglist", Tag.taglist);

router.get("/tag/alllist", Tag.getAll);

router.post("/tag/add", Tag.addtag);

router.del("/tag/delete/:id", Tag.deletag);

router.post("/tag/edit", Tag.updatetag);
module.exports = router;

如何部署到宝塔服务器

具体可以看一下这个 https://www.cnblogs.com/yingyigongzi/p/13926828.html

还有在宝塔面板安全里面,要把后端服务的端口放行,很重要,不然访问不了接口

三、前端采用的是react+antd

前端的目录为

├── public
├── index.html
└───src
│   │── api           --接口
│   │—— assets        --静态资源
│   │—— components    --组件
|   |—— config        --配置
|   |—— pages         --页面
|   |—— Router        --路由
|   |—— store         --状态管理
|   |—— utils         --工具
└───index.js
└───serviceWorker.js  --跨域配置
└───app.js

1、前端请求封装

1.1、在api文件夹下新建apiIP.js用来配置生产和开发的域名

//对外提供的服务地址
export const publicIp =
  process.env.NODE_ENV === "development"
    ? "http://127.0.0.1:3001"
    : "生产域名";
//对外提供获取图片的地址
export const logoImgIp = process.env.NODE_ENV === "development" ? "" : "";
// 登录路由
export const LOGIN = "";

1.2、在api下新建requiest.js

import axios from "axios";
import { publicIp } from "./apiIp";
import { message } from "antd";
import store from "../store";
let hide = null;
const instance = axios.create({
  //创建axios实例,在这里可以设置请求的默认配置
  timeout: 10000, // 设置超时时间10s
  baseURL: publicIp, //根据自己配置的反向代理去设置不同环境的baeUrl
});
// 文档中的统一设置post请求头。下面会说到post请求的几种'Content-Type'
//instance.defaults.headers.post["Content-Type"] = "application/json";
instance.defaults.headers["Content-Type"] = "application/json;chartset=UTF-8";
let httpCode = {
  //这里我简单列出一些常见的http状态码信息,可以自己去调整配置
  400: "请求参数错误",
  401: "权限不足, 请重新登录",
  403: "服务器拒绝本次访问",
  405: "Token已过期,请重新登录",
  404: "请求资源未找到",
  500: "内部服务器错误",
  501: "服务器不支持该请求中使用的方法",
  502: "网关错误",
  504: "网关超时",
};

/** 添加请求拦截器 **/
instance.interceptors.request.use(
  (config) => {
    config.headers["token"] = store.getState().token || "";
    hide = message.loading({ content: "Loading...", duration: 0 });
    // 在这里:可以根据业务需求可以在发送请求之前做些什么:例如我这个是导出文件的接口,因为返回的是二进制流,所以需要设置请求响应类型为blob,就可以在此处设置。
    if (config.url.includes("pur/contract/export")) {
      config.headers["responseType"] = "blob";
    }
    // 我这里是文件上传,发送的是二进制流,所以需要设置请求头的'Content-Type'
    if (config.url.includes("pur/contract/upload")) {
      config.headers["Content-Type"] = "multipart/form-data";
    }
    return config;
  },
  (error) => {
    // 对请求错误做些什么
    return Promise.reject(error);
  }
);

/** 添加响应拦截器  **/
instance.interceptors.response.use(
  (res) => {
    hide()
    if (res.status == "200") {
      // 响应结果里的statusText: ok是我与后台的约定,大家可以根据实际情况去做对应的判断
      return Promise.resolve(res.data);
    }else{
       this.props.history.push("/login");
    } 
    // else {
    //   message.error(res.data.message);
    //   return Promise.reject(res.data.message);
    // }
    // return Promise.resolve(res);
  },
  (error) => {
    hide()
    if (error.response) {
      // 根据请求失败的http状态码去给用户相应的提示
      let tips =
        error.response.status in httpCode
          ? httpCode[error.response.status]
          : error.response.data.message;
      message.error(tips);
      if (error.response.status === 401) {
        // token或者登陆失效情况下跳转到登录页面,根据实际情况,在这里可以根据不同的响应错误结果,做对应的事。这里我以401判断为例
        //针对框架跳转到登陆页面
        this.props.history.push("/admin/page");
      }
      return Promise.reject(error);
    } else {
      message.error("请求超时, 请刷新重试");
      return Promise.reject("请求超时, 请刷新重试");
    }
  }
);

/* 统一封装get请求 */
export const get = (url, params, config = {}) => {
  return new Promise((resolve, reject) => {
    instance({
      method: "get",
      url,
      params,
      ...config,
    }).then((response) => {
        resolve(response);
      },err =>{
        reject(err);
      }).catch((error) => {
        reject(error);
      });
  });
};

/* 统一封装post请求  */
export const post = (url, data, config = {}) => {
  return new Promise((resolve, reject) => {
    instance({
      method: "post",
      url,
      data,
      ...config,
    }).then((response) => {
        resolve(response);
      },err =>{
        reject(err);
      }).catch((error) => {
        reject(error);
      });
  });
};

/* 统一封装del请求  */
export const del = (url, data, config = {}) => {
  return new Promise((resolve, reject) => {
    instance({
      method: "delete",
      url,
      data,
      ...config,
    }).then((response) => {
        resolve(response);
      },err =>{
        reject(err);
      }).catch((error) => {
        reject(error);
      });
  });
};

/* 统一封装update请求  */
export const patch = (url, data, config = {}) => {
  return new Promise((resolve, reject) => {
    instance({
      method: "patch",
      url,
      data,
      ...config,
    }).then((response) => {
        resolve(response);
      },err =>{
        reject(err);
      }).catch((error) => {
        reject(error);
      });
  });
};

1.3、请求方式和接口的挂载

在index.js里面设置

import React, { Component } from "react";
import ReactDOM from 'react-dom';
import './index.less';
import './assets/root.css';
import App from './App';
import { get, post, del, patch } from "./api/request";
import { apiUrl } from "./api/apiUrl";
import * as serviceWorker from './serviceWorker';
// import Router from './Router/router'
Component.prototype.get = get;
Component.prototype.post = post;
Component.prototype.del = del;
Component.prototype.patch = patch;
Component.prototype.api = apiUrl;
ReactDOM.render(<App />, document.getElementById('root'));

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();

2、前端接口如何调用

如在tag.js的页面里

code.png

项目地址

前端博客展示 http://weblod.wxinxianyun.com/web/index

项目地址
https://gitee.com/xinxianbaiguoshu/koa-react

标签: koa, React, sequelize

非特殊说明,本博所有文章均为博主原创。

评论啦~