Router真是一个非常好的一种设计模式, 能够做到超低的耦合, 作为系列的最后一节, 我们来统筹三端, 实现热修复架构, 再来说下这个架构的定义, 就是当Native页面业务逻辑出现Bug时, 又不能马上打包上线通过审核的情况下, 通过后台配置将页面降级为H5的页面, 等待修复完成上线后再回到Native的设计思想.
参考链接:
以下内容在上述文章基础上进行, 请事先查阅.
不过写到这里, 不禁感觉自己写的这个架构不是传统意义上的热更新, 而是变相的替代, 就像AT&T的蜂窝数据从LTE降到3G一样, 就是加载的速度上的差异, 但想必也比有Bug来的好吧.
我们还是先来完成前端的页面吧, 作为M站, 导航栏在浏览器的访问下需要显示而在移动端的降级时不需要, 所以我们先来完成这项工作, 首先我们需要通过get参数来判断跳转情况.
src
├── javascripts
│ ├── http.js
│ └── regex.js
regex.js
export function URLQuery(key) {
let reg = new RegExp("(^|&)" + key + "=([^&]*)(&|$)");
let r = window.location.search.substr(1).match(reg);
if (r != null) {
return unescape(r[2]);
}
return null;
}
接着我们创建导航组件:
src
├── components
│ ├── J1.vue
│ └── navigation.vue
navigation.vue
<template lang="html">
<div v-show="show">
<div class="nav">
<span class="title"></span>
</div>
<div class="nav-offset"></div>
</div>
</template>
<script>
import {
URLQuery
} from '../javascripts/regex'
export default {
data () {
return {
show: true
}
},
props: {
documentTitle: String
},
mounted:function () {
if (URLQuery('client') === 'app') {
this.show = false;
}
}
}
</script>
<style scoped>
.nav {
width: 100%;
height: 44px;
position: relative;
background: rgba(248,248,248,1.0);
border-bottom: 1px solid lightgray;
position: fixed;
top: 0px;
left: 0px;
z-index: 1;
}
.nav-offset {
height: 44px;
}
.title {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
在J1.vue中挂载子组件
<template lang="html">
<div>
<navigation :documentTitle="title"></navigation> //update
<div class="cell" v-for="model in models">

<span></span>
<span></span>
</div>
</div>
</template>
<script>
import navigation from './navigation' //update
import { GET,URL } from '../javascripts/http'
import { URLQuery } from '../javascripts/regex' //update
export default {
data () {
return {
title: '',
models: []
}
},
components: { //update
navigation
},
methods: {
request: function() {
GET(URL.getJ1List).then((data) => {
this.models = data.models;
}).catch((error) => {
})
}
},
mounted:function () {
this.title = document.title = "J1";
this.request();
// alert(window.location.href);
}
}
</script>
<style scoped>
.cell {
height: 100px;
position: relative;
border-bottom: 1px solid lightgray;
}
.cell img {
position: absolute;
top: 50%;
transform: translateY(-50%);
padding-left: 10px;
}
.cell span:first-of-type {
position: absolute;
top: 35%;
transform: translateY(-35%);
padding-left: 84px;
font-size: 15px;
}
.cell span:last-of-type {
position: absolute;
top: 65%;
transform: translateY(-65%);
padding-left: 84px;
font-size: 12px;
}
</style>
在移动端对webView页面进行传参:
extension Router {
func addParam(key: String, value: Any) {
params?[key] = value
}
func clearParams() {
params?.removeAll()
}
func push(_ path: String) {
guardRouters {
guard let state = self.routers?[path] as? String else { return }
if state == "app" {
guard let nativeController = NSClassFromString("RouterPatterm.\(self.map[path]!)") as? UIViewController.Type else { return }
currentController?.navigationController?.pushViewController(nativeController.init(), animated: true)
}
if state == "web" { //update
let host = "http://localhost:3000/"
var query = ""
let ref = "client=app"
guard let params = self.params else { return }
for (key, value) in params {
query += "\(key)=\(value)&"
}
self.clearParams()
let webViewController = WebViewController("\(host)\(path)?\(query)\(ref)")
currentController?.navigationController?.pushViewController(webViewController, animated: true)
}
}
}
}
通过client是不是app来判断导航栏的显示和隐藏, 接下来我们通过WKWebView来进行对JS的通讯:
class WebViewController: ViewController {
fileprivate lazy var configuretion: WKWebViewConfiguration = { [weak self] in
let configuretion = WKWebViewConfiguration()
configuretion.userContentController.add(self!, name: "push") //update
configuretion.userContentController.add(self!, name: "params") //update
return configuretion
}()
...
}
extension WebViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { //update
let methods = "\(message.name):"
let selector = NSSelectorFromString(methods)
if self.responds(to: selector) {
self.perform(selector, with: message.body)
}
}
}
extension WebViewController {
@objc fileprivate func push(_ path: String) { //update
Router.shareRouter.push(path)
}
@objc fileprivate func params(_ params: [String : Any]) { //update
Router.shareRouter.params = params
}
}
在JS中添加跳转和添加参数的方法并通过runtime的方法匹配SEL原生方法, 接下来, 我们在前端中也加入交互的代码:
├── javascripts
│ ├── http.js
│ ├── native.js
│ └── regex.js
native.js
export function NativePush(path) {
window.webkit.messageHandlers.push.postMessage(path);
}
export function NativeParams(params) {
window.webkit.messageHandlers.params.postMessage(params);
}
并在J1.vue中添加上交互逻辑
<template lang="html">
<div>
<navigation :documentTitle="title"></navigation>
<div class="cell" v-for="model in models" @click="push('J1')"> //update

<span></span>
<span></span>
</div>
</div>
</template>
<script>
import navigation from './navigation'
import { GET,URL } from '../javascripts/http'
import { URLQuery } from '../javascripts/regex'
import { NativePush, NativeParams } from '../javascripts/native' //update
export default {
data () {
return {
title: '',
models: []
}
},
components: {
navigation
},
methods: {
request: function() {
GET(URL.getJ1List).then((data) => {
this.models = data.models;
}).catch((error) => {
})
},
push : function(path) { //update
if (URLQuery('client') === 'app') {
let params = {
text : "web端 传入数据",
code : 1002
}
NativeParams(params);
NativePush(path);
} else {
this.$router.push({
path: '/' + path
});
}
}
},
mounted:function () {
this.title = document.title = "J1";
this.request();
// alert(window.location.href);
}
}
</script>
这样我们的架构设计就算基本完成了.
彩蛋: 为我们的后端连上数据库吧, 通过接口改变数据库的值不用重启服务就能够操控页面. 数据库我们在这里使用mongodb, 当然你也可以使用大众情人mysql, 随便…
安装完成后我们执行
- $ mongod –config /usr/local/etc/mongod.conf 开启服务.
- $ use J1 //创建数据库
- $ switched to db J1 //切换到数据库
- $ db.createCollection(‘routers’) //创建routers集合
- $ db.routers.insert({‘J1’ : ‘app’}) //插入数据到集合
这样我们数据库的准备工作就完成了, 对于Node我们在package.json中导入mongoose, 然后在后端的目录结构上添加db路径:
db
├── J1
│ └── routers.js
├── base.js
└── config.js
config.js
const mongoose = require('mongoose');
var db = mongoose.connect('mongodb://localhost/J1');
db.connection.on("error", function(error) {
console.log("database connect fail:" + error);
});
db.connection.on("open", function() {
console.log("database connect success");
})
db.connection.on('disconnected', function() {
console.log('database disconnected');
})
exports.mongoose = mongoose;
base.js
let mongodb = require('./config');
let mongoose = mongodb.mongoose;
let Schema = mongoose.Schema;
let ObjectId = Schema.Types.ObjectId;
exports.mongodb = mongodb;
exports.mongoose = mongoose;
exports.Schema = Schema;
exports.ObjectId = ObjectId;
exports.Mixed = Schema.Types.Mixed;
routers.js
let base = require('../base');
let ObjectId = base.ObjectId;
let Scheme = new base.Schema({
J1: String
});
module.exports = base.mongoose.model('routers', Scheme);
然后在接口中调用数据库:
J1.js
exports.getRouters = async(ctx, next) => {
var routers = await Routers.find({});
ctx.body = {
routers: routers[0]
}
}
exports.updateRouters = async(ctx, next) => {
const controller = ctx.params.controller;
const client = ctx.params.client;
if (controller === 'J1') {
Routers.update({
J1: client
}, (err, doc) => {
console.log(doc);
})
} else {
console.log('controller is not exist');
}
}
添加路由:
J1_router.js
var router = require('koa-router')();
var J1 = require('../../app/controllers/J1');
router.get('/getRouters', J1.getRouters);
router.get('/updateRouters/:controller/:client', J1.updateRouters); //update
router.get('/getJ1List', J1.getJ1List);
module.exports = router;
这样我们在服务全部开启的情况下调用updateRouters接口,
浏览器输入: http://localhost:3001/api/J1/updateRouters/J1/web http://localhost:3001/api/J1/updateRouters/J1/app
我们就能够直接更改数据库动态的降级或升级页面了.
最后来总结一下, 我们通过了Swift3 + Vue2 + Koa2, 实现了可降级的移动端架构:
- cd /RouterPattern/server/RouterPattern $ npm start 开启接口服务器
- cd /RouterPattern/server/RouterPattern/public/javascripts $ node image.js 开启图片服务器
- cd /RouterPattern/web/RouterPattern $ npm run dev 打开前端页面
- cd /RouterPattern/app/RouterPattern $ open RouterPatterm.xcworkspace 打开Xcode
以上就是我想与大家分享的设计思想, 希望大家提出宝贵意见.
演示效果:
About:
🌟 源码 请点这里🌟 »> 喜欢的朋友请点喜欢 »> 下载源码的同学请送下小星星 »> 有闲钱的壕们可以进行打赏 »> 小弟会尽快推出更好的文章和大家分享 »> 你的激励就是我的动力!!