Castie!

正态分布, 优劣伴生

北冥有鱼,其名为鲲(kūn)。鲲之大,不知其几千里也;化而为鸟,其名为鹏。鹏之背,不知其几千里也;怒而飞,其翼若垂天之云。是鸟也,海运则将徙于南冥。南冥者,天池也。


北海若曰:“井鼃不可以语于海者,拘于虚也;夏虫不可以语于冰者,笃于时也;曲士不可以语于道者,束于教也。今尔出于崖涘,观于大海,乃知尔丑,尔将可与语大理矣。

Hybird 搭建客户端实时降级架构

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">
        ![](model.imageUrl)
        <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
        ![](model.imageUrl)
        <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, 随便…

安装完成后我们执行

  1. $ mongod –config /usr/local/etc/mongod.conf 开启服务.
  2. $ use J1 //创建数据库
  3. $ switched to db J1 //切换到数据库
  4. $ db.createCollection(‘routers’) //创建routers集合
  5. $ 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, 实现了可降级的移动端架构:

  1. cd /RouterPattern/server/RouterPattern $ npm start 开启接口服务器
  2. cd /RouterPattern/server/RouterPattern/public/javascripts $ node image.js 开启图片服务器
  3. cd /RouterPattern/web/RouterPattern $ npm run dev 打开前端页面
  4. cd /RouterPattern/app/RouterPattern $ open RouterPatterm.xcworkspace 打开Xcode

以上就是我想与大家分享的设计思想, 希望大家提出宝贵意见.

演示效果:

RouterPattern.gif

About:

点击下方链接跳转!!

🌟 源码 请点这里🌟 »> 喜欢的朋友请点喜欢 »> 下载源码的同学请送下小星星 »> 有闲钱的壕们可以进行打赏 »> 小弟会尽快推出更好的文章和大家分享 »> 你的激励就是我的动力!!

最近的文章

RxSwift 大神们都在看的响应式

上个系列的架构设计, 从MVC -> MVVM -> MVP -> Router -> Downgradable, 我们实现了通过分层将各个组件进行解耦并动态的升降级页面, 这一系列准备说说如何通过RxSwift进行响应式编程更好的和架构设计进行协同. 代码见:github这系列所述案例是通过Ray家的RxSwift一书进行笔记, 在学习本篇之前, 请确保熟练掌握Swift3基础语法并具备中级iOS开发能力. 对于RxSwift的概念不是很清晰的同学可以参照本文...…

移动开发继续阅读
更早的文章

Hybird 搭建路由Router实现组件化

MVP是个非常好的设计模式, 能够进行代码解耦, 业务分层, 组件化必备良品, 但是还能不能再提升一步呢? 现在项目中的耦合是很少了, 但是控制器之前的切换还是有耦合存在, 有什么办法能够实现控制器零耦合呢?? 那就要祭出本系列的核心Router模式了.代码见: github参考链接: Hybird 搭建零耦合架构从MVC开始 Hybird 搭建后端Koa.js并过度到MVVM Hybird 搭建前端Vue.js并升级至MVP以下内容在上述文章基础上进行, 请事先查阅.零耦合一直...…

移动开发继续阅读