Vercel部署Koa项目,并实现跨域

若不需要查看koa-generator相关内容,直接跳转到最后配置

安装koa-generator

全局安装koa-generator

npm i koa-generator -g
复制代码

初始化项目

koa2 goudong-server 
复制代码

进入并安装依赖

cd goudong-server 
npm install
复制代码

改造项目环境

  • 在根目录下新建src目录

  • publicroutesviewsapp.js拖入src目录

  • 修改bin/www中的var app = require('../app');var app = require('../src/app');

  • 改造后目录

  • |- bin
      |- www
    |-node_modules
    |-src
      |-public
      |-routes
      |-views
      |-app.js
    |-package.json
    复制代码
  • src目录下创建四个目录controllerdbmiddlewaremodels

实现登录功能

配置开发环境

安装koa-generic-session依赖

ni koa-generic-session 
复制代码

使用

const session = require('koa-generic-session')

//session配置
app.keys = ['liyunfuAAA'] //密钥用于加密
app.use(session({
  //配置cookie
  cookie: {
    path: '/',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  }
}))
复制代码

跨域

安装koa2-cors依赖

ni koa2-cors
复制代码

使用

const cors = require('koa2-cors')

//cors配置
app.use(cors({
  origin: 'http://localhost:8080',  //前端origin
  credentials: true //允许跨域带cookie
}))
复制代码

连接数据库

安装mongoose

ni mongoose
复制代码

db目录下新建db.js

/**
 * @description mongoose 连接数据库
 * @author liyunfu
 */
const mongoose = require('mongoose')

const DB_URL = 'mongodb://root:example@47.99.147.11.27017/jingdong?authSource=admin'

// 开始连接
mongoose.connect(DB_URL, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})

// 连接对象
const db = mongoose.connection

db.on('error', err => {
  console.error('mongoose connect error', err)
})
db.once('open', () => {
  console.log('mongoose 连接成功')
})

module.exports = mongoose
复制代码

设计Schema和Model

  • models目录下新建User.js

    /**
     * @description user Model
     * @author liyunfu
     */
    
    const mongoose = require('../db/db')
    
    const Schema = mongoose.Schema({
      username: {
        type: String,
        require: true,
        unique: true
      },
      password: String
    }, { timestamps: true })
    
    const User = mongoose.model('user', Schema)
    
    module.exports = User
    复制代码
  • models下新建Address.js

    /**
     * @description Address Model
     * @author liyunfu
     */
    
    const mongoose = require('../db/db')
    
    const Schema = mongoose.Schema({
      username: {
        type: String,
        require: true
      },
      city: String,
      department: String,
      houseNumber: String,
      name: String,
      phone: String
    }, { timeStamps: true })
    
    const Address = mongoose.model('address', Schema)
    
    module.exports = Address
    复制代码
  • models下新建Shop.js

    /**
     * @description Shop Model
     * @author liyunfu
     */
    
    const mongoose = require('../db/db')
    
    const Schema = mongoose.Schema({
      name: String,
      imgUrl: String,
      sales: Number,
      expressLimit: {
        type: Number,
        default: 0
      },
      expressPrice: Number,
      slogan: String
    }, { timeStamps: true })
    
    const Shop = mongoose.model('shop', Schema)
    
    module.exports = Shop
    复制代码
  • models下新建Product.js

    /**
     * @description Product Model
     * @author liyunfu
     */
    
    const mongoose = require('../db/db')
    
    const Schema = mongoose.Schema({
      ShopId: {
        type: String,
        require: true
      },
      name: String,
      imgUrl: String,
      sales: Number,
      price: Number,
      oldPrice: Number,
      tabs: [String]  //示例 tabs:['all','seckill']
    }, { timestamps: true })
    
    const Product = mongoose.model('product', Schema)
    
    module.exports = Product
    复制代码
  • models下新建Order.js

    /**
     * @description Order Model
     * @author liyunfu
     */
    
    const mongoose = require('../db/db')
    
    const Schema = mongoose.Schema({
      username: {
        type: String,
        require: true
      },
      shopId: String,
      shopName: String,
    
      idCanceled: {
        type: Boolean,
        default: false
      },
      address: {
        username: String,
        city: String,
        department: String,
        houseNumber: String,
        name: String,
        phone: String
      },
      products: [
        {
          product: {
            shopId: {
              type: String,
              require: true
            },
            name: String,
            imgUrl: String,
            sales: Number,
            price: Number,
            oldPrice: Number,
            tabs: [String]
          },
          orderSales: Number
        }
      ]
    }, { timestamps: true })
    
    const Order = mongoose.model('order', Schema)
    
    module.exports = Order
    复制代码
  • models 下新建index.js

    /**
     * @description Model 入口文件
     * @author liyunfu
     */
    const Address = require('./Address')
    const Order = require('./Order')
    const Product = require('./Product')
    const Shop = require('./Shop')
    const User = require("./User")
    
    module.exports = {
      Address,
      Order,
      Product,
      Shop,
      User
    }
    复制代码

标准化请求成功与失败的响应信息

  • res-model 下新建ErrorModel.js

    /**
     * @description 错误返回的数据结构
     * @author liyunfu
     */
    
    class ErrorModel {
      constructor(errno = -1, message = 'error') {
        this.errno = errno
        this.message = message
      }
    }
    
    module.exports = ErrorModel
    复制代码
  • res-model 下新建SuccessModel.js

    /**
     * @description 成功返回的数据类型
     * @author liyunfu
     */
    
    class SuccessModel {
      constructor(data) {
        this.errno = 0
        if (data !== null) {
          this.data = data
        }
      }
    }
    
    module.exports = SuccessModel
    复制代码
  • res-model 下新建入口文件index.js

    /**
     * @description 返回数据类型 入口文件
     * @author liyunfu
     */
    const SuccessModel = require('./SuccessModel')
    const ErrorModel = require('./ErrorModel')
    
    module.exports = { SuccessModel, ErrorModel }
    复制代码

编写登录验证中间件

  • middleware 下新建loginCheck.js

    /**
     * @description 登录验证中间件
     * @author liyunfu
     */
    
    const { ErrorModel } = require('../res-model/index')
    
    module.exports = async (ctx, next) => {
      const session = ctx.session
    
      if (session && session.userInfo) {
        await next()
        return
      }
      ctx.body = new ErrorModel(10003, '中间件登录验证失败')
    }
    复制代码

用户操作接口

  • controller 下新建 user.js

    /**
     * @description user controller
     * @author liyunfu
     */
    
    const { User } = require('../models/index')
    
    /**
     * 注册方法
     * @param {Object} userInfo 用户信息
     * @returns 
     */
    async function register(userInfo = {}) {
      // 注意验证一下username unique
      const newUser = await User.create(userInfo)
      return newUser
    }
    
    async function login(username, password) {
      const user = await User.findOne({ username, password })
      if (user != null) {
        // 登录成功
        return true
      }
      return false
    }
    
    module.exports = {
      register, login
    }
    复制代码
  • routes 下新建users.js

    const router = require('koa-router')()
    
    
    const { register, login } = require('../controller/user')
    const { SuccessModel, ErrorModel } = require('../res-model/index')
    const loginCheck = require('../middleware/loginCheck')
    
    router.prefix('/api/user')
    
    // 注册
    router.post('/register', async function (ctx, next) {
      const userInfo = ctx.request.body
      try {
        await register(userInfo)
        // 返回成功
        ctx.body = new SuccessModel()
      } catch (ex) {
        console.log(ex)
        // 返回失败
        ctx.body = new ErrorModel(10001, `注册失败 - ${ex.message}`)
      }
    })
    
    // 登录
    router.post('/login', async (ctx, next) => {
      const { username, password } = ctx.request.body
      // 查询单个用户
      const res = await login(username, password)
    
      if (res) {
        // 登录成功
        ctx.session.userInfo = { username }  //设置session
    
        ctx.body = new SuccessModel()
      } else {
        ctx.body = new ErrorModel(10002, `登录验证失败`)
      }
    })
    
    router.get('/info', loginCheck, async function (ctx, next) {
      // 加了loginCheck之后,因为保证了必须登录
      const session = ctx.session
      ctx.body = new SuccessModel(session.userInfo)
    })
    module.exports = router
    复制代码

地址操作接口

  • controller 下新建address.js

    /**
     * @description address controller
     * @author liyunfu
     */
    
    const { Address } = require('../models/index')
    
    /**
     * 创建地址 
     * @param {string} username 用户名
     * @param {Object} data 地址的详细信息
     * @returns 
     */
    async function createAddress(username, data) {
      const address = await Address.create({ username, ...data })
    
      return address
    }
    
    /**
     * 获取地址列表
     * @param {string} username 用户名
     * @returns 
     */
    async function getAddressList(username) {
      const list = await Address.find({ username }).sort({ updatedAt: -1 })
      return list
    }
    
    /**
     * 获取单个收获地址
     * @param {string} id id
     * @returns 
     */
    async function getAddressById(id) {
      const address = await Address.findById(id)
      return address
    }
    
    async function updateAddress(id, username, data) {
      const address = await Address.findOneAndUpdate(
        {
          // 查询条件
          _id: id,
          username,
        },
        {
          username, ...data
        },
        {
          new: true  //返回更新之后的最新数据,默认时false,返回更新之前的数据
        }
      )
      return address
    }
    
    module.exports = {
      createAddress,
      getAddressList,
      getAddressById,
      updateAddress
    }
    复制代码
  • routes 下新建address.js

    /**
     * @description address router
     * @author liyunfu
     */
    
    const router = require('koa-router')()
    const { createAddress, getAddressList, getAddressById, updateAddress } = require('../controller/address')
    const { SuccessModel, ErrorModel } = require('../res-model/index')
    const loginCheck = require('../middleware/loginCheck')
    
    router.prefix('/api/user/address')
    
    // 创建收货地址
    router.post('/', loginCheck, async (ctx, next) => {
      // 获取用户信息
      const userInfo = ctx.session.userInfo
      const username = userInfo.username
      const data = ctx.request.body
    
      // 创建数据
      try {
        const newAddress = await createAddress(username, data)
        ctx.body = new SuccessModel(newAddress)
      } catch (error) {
        console.log(error)
        ctx.body = new ErrorModel(10004, '创建收货地址失败')
      }
    })
    
    // 获取收货地址列表
    router.get('/', loginCheck, async (ctx, next) => {
      const userInfo = ctx.session.userInfo
      const username = userInfo.username
    
      // 获取列表
      const list = await getAddressList(username)
      ctx.body = new SuccessModel(list)
    })
    
    // 获取单个收获地址
    router.get('/:id', loginCheck, async (ctx, next) => {
      const id = ctx.params.id
      const address = await getAddressById(id)
    
      ctx.body = new SuccessModel(address)
    })
    
    // 更新收货地址
    router.patch('/:id', loginCheck, async (ctx, next) => {
      const id = ctx.params.id
      const data = ctx.request.body
      const userInfo = ctx.session.userInfo
      const username = userInfo.username
      // 更新
      const newAddress = await updateAddress(id, username, data)
      ctx.body = new SuccessModel(newAddress)
    })
    
    module.exports = router
    复制代码

商店商品接口

  • controller 下新建shop.js

    /**
     * @description shop controller
     * @author liyunfu
     */
    
    const { } = require('../models/index')
    
    // 热门商店列表
    async function getHotList() {
      const list = await Shop.find().sort({ _id: -1 }) //逆序
      return list
    }
    
    // 根据id获取单个商店信息
    async function getShopInfo(id) {
      const shop = await Shop.findById(id)
      return shop
    }
    
    // 根据商店id获取商品
    async function getProductByShopId(id, tab = '') {
      const pList = await Product.find({
        shopId: id,
        tabs: {
          $in: tab  //匹配tabs
        }
      }).sort({ _id: -1 })  //逆序
      return pList
    }
    
    module.exports = {
      getHotList,
      getShopInfo,
      getProductByShopId
    }
    复制代码
  • routes下新建shop.js

    const router = require('koa-router')()
    
    const { SuccessModel } = require('../res-model/SuccessModel')
    
    const {
      getHotList,
      getShopInfo,
      getProductByShopId
    } = require('../controller/shop')
    
    router.prefix('/api/shop')
    
    // 热门商店(首页商店列表)
    router.get('/hot-list', async function (ctx, next) {
      const list = await getHotList()
      ctx.body = new SuccessModel(list)
    })
    
    // 根据 id 查询单个商店信息
    router.get('/:id', async function (ctx, next) {
      const id = ctx.params.id  //商店id
      const shop = await getShopInfo(id)
      ctx.body = new SuccessModel(shop)
    })
    
    router.get('/:id/product', async function (ctx, next) {
      const id = ctx.params.id
      const tab = ctx.query.tab || 'all'
      const products = await getProductByShopId(id, tab)
      ctx.body = new SuccessModel(products)
    })
    复制代码

订单接口

  • controller 下新建order.js

    /**
     * @description order controller
     * @author liyunfu
     */
    
    const { Order, Product, Address } = require('../models/index')
    
    // 创建订单(要从Address,Product里拷贝数据,比较麻烦)
    async function createOrder(username, data = {}) {
      console.log(username, data)
      // 结构data(前端传来的订单信息)
      const {
        addressId,
        shopId,
        shopName,
        isCanceled = false,
        products = []
      } = data
    
      // 根据addressId获取地址信息
      const address = await Address.findById(addressId)
    
      // 获取商品列表
      const pIds = products.map(p => p.id)
      const productList = await Product.find({
        // 条件1:商品id
        _id: {
          $in: pIds
        },
        // 条件2:商店id
        shopId
      })
    
      // 给商品列表增加销售数量(订单里,每个商品都有销量)
      const productListWithSales = productList.map(p => {
        // 商品id
        const id = p._id.toString()
    
        // 找到商品销量
        const filterProducts = products.filter(item => item.id === id)
        if (filterProducts.length === 0) {
          // 没有找到匹配的数量 报错
          throw new Error('未找到匹配的销量数据')
        }
    
        return {
          orderSales: filterProducts[0].num,
          product: p
        }
      })
    
      // 创建订单
      const newOrder = await Order.create({
        username,
        address,
        shopId,
        shopName,
        isCanceled,
        products: productListWithSales
      })
      return newOrder
    }
    
    // 获取订单列表
    async function getOrderList(username) {
      console.log('username', username)
      const list = await Order.find({ username }).sort({ _id: -1 })
      console.log('list', 'list')
      return list
    }
    
    module.exports = {
      createOrder,
      getOrderList
    }
    复制代码
  • routes 下新建order.js

    const router = require('koa-router')()
    
    const { SuccessModel, ErrorModel } = require('../res-model/index')
    const loginCheck = require('../middleware/loginCheck')
    const { createOrder, getOrderList } = require('../controller/order')
    
    router.prefix('/api/order')
    
    // 创建订单
    router.post('/', loginCheck, async function (ctx, next) {
      // 有登录验证 可以直接获取session
      const userInfo = ctx.session.userInfo
      const username = userInfo.username
    
      // 订单数据
      const data = ctx.request.body
    
      try {
        const newOrder = await createOrder(username, data)
        ctx.body = new SuccessModel(newOrder)
      } catch (ex) {
        console.error(ex)
        ctx.body = new ErrorModel(10005, '订单创建失败')
      }
    })
    
    // 获取订单列表
    router.get('/', loginCheck, async function (ctx, next) {
      // 有登录验证,可以直接获取session
      const userInfo = ctx.session.userInfo
      const username = userInfo.username
    
      const list = await getOrderList(username)
    
      ctx.body = new SuccessModel(list)
    })
    
    module.exports = router
    复制代码

改为适合部署到Vercel的项目

  1. 在根目录下新建vercel.json

    {
      "version": 2,
      "builds": [
        {
          "src": "./index.js",
          "use": "@vercel/node"
        }
      ],
      "routes": [
        {
          "src": "/(.*)",
          "dest": "/"
        }
      ]
    }
    复制代码
  2. 在根目录下新建index.js 将原bin/www内容移到此处

  3. 修改package.json

    "start": "node index.js",
    "dev": "./node_modules/.bin/nodemon index.js",
    "prd": "pm2 start index.js",
    复制代码

改造项目,实现跨域

  1. 修改根目录下vercel.json

    {
      "version": 2,
      "builds": [
        {
          "src": "./index.js",
          "use": "@vercel/node"
        }
      ],
      "routes": [
        {
          "src": "/(.*)",
          "dest": "/",
          "headers": {
            "Access-Control-Allow-Credentials": "true",
            "Access-Control-Allow-Origin": "*",
            "Access-Control-Allow-Methods": "GET,OPTIONS,PATCH,DELETE,POST,PUT",
            "Access-Control-Allow-Headers": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"
          }
        }
      ]
    }
    复制代码

其他

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享