实现:websocket 在项目中的使用-订单锁定
从前在订单模块用到了 websocket,记录一下。
场景
admin 订单详情模块存在一个页面,叫做 fraud order list。也就是退款订单页面。
大概长这样:

点击操作按钮会出现弹窗的表单, 填写完保存:

问题:
- 页面不是只有一个人在操作的,同一段时间内,如果有不同的人打开弹窗,巴拉巴拉填完,后面保存的操作会覆盖前面保存的操作。
解决方案
websocket 是在这个场景就是一个很好的解决方法。
- 首先,在订单的操作栏增加一个 lock 按钮:当我要编辑这笔订单时,点击 lock 按钮,通知到服务端。
- 服务端接受到消息,将订单锁定的状态存储起来。
- 再推送到所有客户端浏览器,让这个订单变成已锁定,隐藏所有按钮,不能再编辑。
这样,一个订单,在同一个时间段内,只能有一个人在处理。

实现
客户端部分代码。用 vue 编写。
这里用了一个 reconnecting-websocket 库,他封装了原生的 websocket,并且可以实现断开后自动重连。(有一些场景,例如网络断开一段时间,或者 node 服务需要重新发布,或者挂掉重启,这个时候是需要重新连接的。)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| <template> <div v-for="order in orderList"> <el-button @click="lockOrder" /> <el-button @click="edit" v-if="all_lock_data.includes(order)"> 操作 </el-button> <el-dialog> <el-button @click="save() && unlockOrder()" /> </el-dialog> </div> </template> <script> import ReconnectingWebSocket from 'reconnecting-websocket'
export default { methods: { initWebsocket() { const socket = new ReconnectingWebSocket('/websocket/lock_fruad_order') this.socket = socket socket.onopen = function (event) { console.info('websocket connection established') }
this.socket.onmessage = function (event) { let data = JSON.parse(event.data) $vm.all_lock_data = data }
this.socket.onclose = function (event) { console.error('[close] Connection died') }
this.socket.onerror = function (error) { console.error(`[error] ${error.message}`) } }, lockOrder(order) { const data = JSON.stringify([ isLock: true, order_number: order.order_number, account_name: window.KLK_PAGE_DATA._session.account.account_name ]); this.socket.send(data); }, unlockOrder(order) { const data = JSON.stringify([ isLock: false, order_number: order.order_number, account_name: window.KLK_PAGE_DATA._session.account.account_name ]); this.socket.send(data); } }, created() { this.initWebsocket() }, } </script>
|
上面就是客户端的写法,比较简单,再看看服务端。
服务端用的是 koa。
为了防止服务崩溃,发布或者重启,这里将锁定的订单都存在 redis 持久化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| const Koa = require('koa') const websockify = require('koa-websocket') const KoaRouter = require('koa-router') const redis = require('redis') const app = new Koa() const router = KoaRouter() const rds = redis.createClient()
app = websockify(app)
const RDS_PREFIX = 'fruad_order_lock:'
function broadcastAllData($ws) { rds.keys(`${RDS_PREFIX}*`, (err, res) => { if (err) { console.log(err) } let keys = res if (keys && keys.length) { rds.mget(keys, function (err, res) { if (err) { console.log(err) } let values = res let data = keys.reduce( (acc, key, index) => ({ ...acc, [key.replace(RDS_PREFIX, '')]: values[index], }), {} ) $ws.broadcast(JSON.stringify(data)) }) } else { $ws.broadcast(JSON.stringify({})) } }) }
router.get( '/websocket/lock_fruad_order', function* (next) { let has_auth = yield web_comm.has_auth.call(this) if (!has_auth) { this.websocket.close(1003, 'Auth Error') return } else { yield next } }, function* (next) { let $ws = this.websocket $ws.on('message', function (message) { let [isLock, order_number, name] = JSON.parse(message) let KEY = `${RDS_PREFIX}${order_number}` let updateHandler = function (err, res) { if (err) { console.log(err) } broadcastAllData($ws) } if (isLock) { rds.setex(KEY, 10 * 60, name, updateHandler) } else { rds.del(KEY, updateHandler) } })
broadcastAllData($ws)
yield next } )
app.use(router.routes())
|
以上。
-
版权声明: 本博客所有文章除特别声明外,均采用
CC BY 4.0 CN协议
许可协议。转载请注明出处!
Жизнь, как качели - то вверх, то вниз.