钢材信息小程序开发总结(三) ---EggJS

整体项目代码
钢材信息小程序开发总结(一) --- 整体介绍
钢材信息小程序开发总结(二) ---uniapp

这次是对后端部分的介绍

一、系统环境介绍

  • NodeJS
  • MySQL
  • Redis

1.NodeJS

JS服务端运行环境

参考资料:

2.MySQL数据库

开源关系型数据库

参考资料:

3.Redis

开源、支持网络、基于内存、可选持久性的键值对存储数据库

参考资料:


二、EggJS 相关插件

  • egg-sequelize
  • egg-redis
  • egg-jwt
  • egg-router-plus
  • egg-validate
  • egg-cors
  • egg-view-nunjucks
  • egg-zrole

1.egg-sequelize

Sequelize的EggJS插件. Sequelize是MySQL的ORM框架

参考资料:

2.egg-redis

Redis的EggJS插件

参考资料:

3.egg-jwt

EggJS的JWT插件

参考资料:

4.egg-router-plus

EggJS的路由加强插件, 主要用于给路由统一添加前缀

参考资料:

5.egg-validate

EggJS的参数校验插件

参考资料:

6.egg-cors

EggJS的访问控制插件

参考资料:

7.egg-view-nunjucks

EggJS的模板渲染引擎插件, 语法类似 Django 的 jinjia

参考资料:

8.egg-zrole

EggJS的基于Casbin的权限控制插件

参考资料:


三、项目目录

.
├── app.js
├── server.js
├── app
│   ├── router.js 
│   ├── controller            
│   ├── extend           
│   ├── middleware
│   ├── service
│   ├── public
│   ├── view
│   ├── router
│   └── model (自建Sequelize目录)
└──  config
    ├── plugin.js
    ├── config.default.js
    └── config.prod.js

以上目录介绍如下:

  1. app/router.js 用于配置URL路由规则。
  2. app/controller/ 用于解析用户输入,处理后返回响应结果。
  3. app/extend/ 用于框架内部对象的拓展(request,response,context,application)和工具类(helper)的编写。
  4. app/middleware/ 用于编写中间件。
  5. app/service/ 用于编写业务逻辑,如数据库操作的封装,api请求的封装等。
  6. app/public/ 用于放置静态文件。
  7. app/view/ 用于放置模板文件。
  8. app/model/ 用于放置数据模型。
  9. app/router/ 用户放置分离的路由
  10. app.js 用于自定义启动时的初始化工作。
  11. server.js 用于使用pm2部署 pm2 start server.js

四、相关业务逻辑

1.分页逻辑

分页中间件, 获取分页及其他查询参数

module.exports = async (ctx, next) => {
  const { query } = ctx.request

  ctx.validate({
    pageField: { type: 'string', required: false },
    pageSort: { type: 'enum', values: ['ASC', 'DESC'], required: false },
    pageSize: { type: 'string', format: /\d+/, required: false },
    pageIndex: { type: 'string', format: /\d+/, required: false },
  }, query)

  const {
    pageField = 'updated_at',
    pageSort = 'DESC',
  } = query
  // ~~ 用于 Math.floor 的功能, 以及将 undefined, null 转为数字 0
  const pageSize = ~~query.pageSize || 20
  const pageIndex = ~~query.pageIndex || 1

  ctx.page = { pageField, pageSort, pageSize, pageIndex }

  return next()
}

求和服务, 获取列表总数

async findCount({ modelName, where = {} }) {
  return this.ctx.model[modelName].count({
    where
  })
}

根据查询参数获取列表数据

async findPage({
  modelName,
  where = {},
  page,
  attributes = {},
  include = [],
}) {
  const {
    pagination
  } = this.ctx.helper
  const {
    order,
    offset,
    limit
  } = pagination(page)
  return this.ctx.model[modelName].findAll({
    where,
    order,
    offset,
    limit,
    attributes,
    include,
  })
}

示例
1.controller, 获取用户日志

async getUserLog() {
  const { ctx, app } = this
  let { page } = ctx
  const { common } = ctx.service
  console.log(page)
  page = {
    ...page,
    pageField: 'id',
    pageSort: 'DESC'
  }
  const [total, items] = await Promise.all([
    common.findCount({ modelName: 'Log' }),
    common.findPage({ modelName: 'Log', page }),
  ])
  const res = { total, items, pageSize: page.pageSize, pageIndex: page.pageIndex }
  ctx.helper.success({ ctx, res: res })
}

2.配置路由, 引入分页中间件

apiV1Router.get('/log', app.jwt, middleware.pagination, controller.api.user.getUserLog)

2.文件上传

1.后端controller

class UploadController extends Controller {
  async index() {
    const { ctx } = this
    const stream = await ctx.getFileStream()
    ctx.helper.success({ctx, res: 'upload ok' + stream})
  }
}

2.前端上传文件

<label for="file">upload</label>
<input type="file" id="file">
<script>
  var file = document.querySelector('#file')
  file.addEventListener('change', function(e) {
    var f = e.target.files[0]
    var formdata = new FormData()
    formdata.append('file', f)
    console.log(formdata)
    fetch(`http://localhost:7001/upload`, {
      method: 'POST',
      body: formdata
    })
      .then(res => res.json())
      .then(res => {
        e.target.value = null
        console.log(res.data)
      })
  })
</script>

3.小程序登录

https://www.jianshu.com/p/8df2213f6f9d

4.小程序统计数据获取

由于请求获取access_token的次数有限制, 所以将其存入redis中缓存

async getWechatToken() {
  const {ctx, app} = this
  let result = await ctx.helper.request({ctx, url: `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${app.config.weapp.appId}&secret=${app.config.weapp.secret}`})
  app.redis.set('weToken', result.data.access_token, 'EX', result.data.expires_in)
  return result.data.access_token
}

使用access_token请求获取前一天的用户画像
注意: 在零点前后数据会获取不到

async getWechatUser() {
  const {ctx, app} = this
  const yesterday = dayjs().subtract(1, 'day').format("YYYYMMDD")
  let token = await app.redis.get('token');
  if(token && token !== '') {
    const result = await ctx.helper.request({
      ctx, 
      url: `https://api.weixin.qq.com/datacube/getweanalysisappiduserportrait?access_token=${token}`,
      method: 'POST',
      data: {
        begin_date: yesterday,
        end_date: yesterday
      }
    })
    return result
  } else {
    token = await ctx.service.data.getWechatToken()
    const result = await ctx.helper.request({
      ctx, 
      url: `https://api.weixin.qq.com/datacube/getweanalysisappiduserportrait?access_token=${token}`,
      method: 'POST',
      data: {
        begin_date: yesterday,
        end_date: yesterday
      }
    })
    return result
  }
}

5.热门钢材排序

按照新的顺序, 遍历数据库中热门钢材的sort字段

async setSortById(list) {
  let arr = []
  list.forEach(v => {
    arr.push(this.ctx.model.Hot.update(
      {sort: v.sort},
      {
        where: {
          id: v.id
        }
      }
    ))
  })
  const result = await Promise.all(arr)
  if(Array.isArray(result) && result.length > 0) {
    return true
  } else {
    return false
  }
}

6.HTTP请求封装

request: async ({ctx, method = 'GET', url='', data = {}}) => {
  const result = await ctx.curl(url, {
    method: method,
    contentType: 'json',
    dataType: 'json',
    data: data
  })
  return result
}

7.响应体封装

/**
 * 错误码
 */
errorCode: {
  200: '服务器成功返回请求的数据。',
  201: '新建或修改数据成功。',
  202: '一个请求已经进入后台排队(异步任务)。',
  204: '删除数据成功。',
  400: '发出的请求有错误,服务器没有进行新建或修改数据的操作。',
  401: '用户没有权限(令牌、用户名、密码错误)。',
  403: '用户得到授权,但是访问是被禁止的。',
  404: '发出的请求针对的是不存在的记录,服务器没有进行操作。',
  406: '请求的格式不可得。',
  410: '请求的资源被永久删除,且不会再得到的。',
  422: '当创建一个对象时,发生一个验证错误。',
  500: '服务器发生错误,请检查服务器。',
  502: '网关错误。',
  503: '服务不可用,服务器暂时过载或维护。',
  504: '网关超时。'
}

/**
 * 处理成功请求后的响应
 */
success: ({ ctx, code = 200, res = {}, msg }) => {
  if (!res) code = 404
  ctx.status = 200
  ctx.body = {
    code: code,
    message: msg || ctx.helper.errorCode[code],
    data: res
  }
}

/**
 * 处理失败请求后的响应
 */
fail: ({ ctx, code = 500, res = {}, msg }) => {
  ctx.status = 200
  ctx.body = {
    code: code,
    message: msg || ctx.helper.errorCode[code],
    data: res
  }
}

8.数据库初始化

  1. 第一次启动, 取消掉app.js中注释即可
'use strict';

module.exports = app => {
  if (app.config.env === 'local') {
    app.beforeStart(async () => {
      await app.model.sync({ force: true })
    })
  }
}
  1. 之后注释掉
await app.model.sync({ force: true })
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260