整體項目代碼
鋼材信息小程序開發(fā)總結(jié)(一) --- 整體介紹
鋼材信息小程序開發(fā)總結(jié)(二) ---uniapp
這次是對后端部分的介紹
一恤浪、系統(tǒng)環(huán)境介紹
- NodeJS
- MySQL
- Redis
1.NodeJS
JS服務(wù)端運行環(huán)境
參考資料:
2.MySQL數(shù)據(jù)庫
開源關(guān)系型數(shù)據(jù)庫
參考資料:
3.Redis
開源畅哑、支持網(wǎng)絡(luò)、基于內(nèi)存水由、可選持久性的鍵值對存儲數(shù)據(jù)庫
參考資料:
二荠呐、EggJS 相關(guān)插件
- 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的路由加強插件, 主要用于給路由統(tǒng)一添加前綴
參考資料:
5.egg-validate
EggJS的參數(shù)校驗插件
參考資料:
6.egg-cors
EggJS的訪問控制插件
參考資料:
7.egg-view-nunjucks
EggJS的模板渲染引擎插件, 語法類似 Django 的 jinjia
參考資料:
8.egg-zrole
EggJS的基于Casbin的權(quán)限控制插件
參考資料:
三、項目目錄
.
├── 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
以上目錄介紹如下:
- app/router.js 用于配置URL路由規(guī)則砂客。
- app/controller/ 用于解析用戶輸入泥张,處理后返回響應(yīng)結(jié)果。
- app/extend/ 用于框架內(nèi)部對象的拓展(request,response,context,application)和工具類(helper)的編寫鞠值。
- app/middleware/ 用于編寫中間件圾结。
- app/service/ 用于編寫業(yè)務(wù)邏輯,如數(shù)據(jù)庫操作的封裝齿诉,api請求的封裝等。
- app/public/ 用于放置靜態(tài)文件晌姚。
- app/view/ 用于放置模板文件粤剧。
- app/model/ 用于放置數(shù)據(jù)模型。
- app/router/ 用戶放置分離的路由
- app.js 用于自定義啟動時的初始化工作挥唠。
- server.js 用于使用pm2部署
pm2 start server.js
四抵恋、相關(guān)業(yè)務(wù)邏輯
1.分頁邏輯
分頁中間件, 獲取分頁及其他查詢參數(shù)
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 轉(zhuǎn)為數(shù)字 0
const pageSize = ~~query.pageSize || 20
const pageIndex = ~~query.pageIndex || 1
ctx.page = { pageField, pageSort, pageSize, pageIndex }
return next()
}
求和服務(wù), 獲取列表總數(shù)
async findCount({ modelName, where = {} }) {
return this.ctx.model[modelName].count({
where
})
}
根據(jù)查詢參數(shù)獲取列表數(shù)據(jù)
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.小程序登錄
http://www.reibang.com/p/8df2213f6f9d
4.小程序統(tǒng)計數(shù)據(jù)獲取
由于請求獲取access_token
的次數(shù)有限制, 所以將其存入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
請求獲取前一天的用戶畫像
注意: 在零點前后數(shù)據(jù)會獲取不到
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.熱門鋼材排序
按照新的順序, 遍歷數(shù)據(jù)庫中熱門鋼材的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.響應(yīng)體封裝
/**
* 錯誤碼
*/
errorCode: {
200: '服務(wù)器成功返回請求的數(shù)據(jù)。',
201: '新建或修改數(shù)據(jù)成功宝磨。',
202: '一個請求已經(jīng)進入后臺排隊(異步任務(wù))弧关。',
204: '刪除數(shù)據(jù)成功。',
400: '發(fā)出的請求有錯誤唤锉,服務(wù)器沒有進行新建或修改數(shù)據(jù)的操作世囊。',
401: '用戶沒有權(quán)限(令牌、用戶名窿祥、密碼錯誤)株憾。',
403: '用戶得到授權(quán),但是訪問是被禁止的。',
404: '發(fā)出的請求針對的是不存在的記錄嗤瞎,服務(wù)器沒有進行操作墙歪。',
406: '請求的格式不可得。',
410: '請求的資源被永久刪除贝奇,且不會再得到的虹菲。',
422: '當創(chuàng)建一個對象時,發(fā)生一個驗證錯誤掉瞳。',
500: '服務(wù)器發(fā)生錯誤毕源,請檢查服務(wù)器。',
502: '網(wǎng)關(guān)錯誤菠赚。',
503: '服務(wù)不可用脑豹,服務(wù)器暫時過載或維護。',
504: '網(wǎng)關(guān)超時衡查。'
}
/**
* 處理成功請求后的響應(yīng)
*/
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
}
}
/**
* 處理失敗請求后的響應(yīng)
*/
fail: ({ ctx, code = 500, res = {}, msg }) => {
ctx.status = 200
ctx.body = {
code: code,
message: msg || ctx.helper.errorCode[code],
data: res
}
}
8.數(shù)據(jù)庫初始化
- 第一次啟動, 取消掉
app.js
中注釋即可
'use strict';
module.exports = app => {
if (app.config.env === 'local') {
app.beforeStart(async () => {
await app.model.sync({ force: true })
})
}
}
- 之后注釋掉
await app.model.sync({ force: true })