Flutter & Koa2 实战全面升级,试问谁不孤独?(小万字长文)

那个执拗的少年回来了 《孤岛 App》这个系列已经停更了好久,此次全面升级,对的起给我star 的老铁们php

介绍

APP 名称:《独 °》css

客户端方案:flutterhtml

服务端方案:Koa2前端

功能

写着完善着vue

- 个人
  - 我的信息
  - 头像修改
 - 首页
  - 列表页
  - 发送图文、视频等
 ……
复制代码

前序准备

  • 下文连接预警
  • 长文预警
  • 唠嗑方式不正经预警
  • 错别字警告

当开始全面更新迭代的时候,没有产品 的思惟是多么可怕的一件事,开发的过程当中会同步更新系列文章,但愿一块撩一撩flutter 固然了这些文章都是没有更新的node

分支变更

20200315111241.png
是这样的,我们把以前持续更新的移动到 lsolated_island_app 这个分支。想要翻看的能够自行 clone 《独》全部的开发如今 master 分支。 可能旧的文档地址找不到的状况,这个我后续更新一下

以前个人评论区react

  • 问:为啥 flutter 评论那么少
  • 答:可能你们还不太了解

我以为本身开发个小 app 也挺好玩的。nginx

但愿多多鼓励很关注,有不恰当的地方也欢迎指正。git

友情建议:es6

最近一段时候因为公司需求,笔者在用 Vue 生态的 uniapp 技术栈来开发 app,整体体验是不太好的

不作什么横向对比,在正确使用 flutter 的前提下,flutter 开发的应用是相比于 uniapp 好不少的(这只是我我的见解)

我的感受 flutter 的学习成本仍是比较高的,若是公司想要经过这个技术来开发的话,可能须要有同事持续跟进 flutter 的生态发展,并按期分享给成员,由于 flutter 生态是愈来愈活跃,技术的更新迭代是至关的迅速,相关的第三方包插件今个能用。明天可能你就不知道咋回事了

flutter 只是一个简单的 UI(这里特别说一下并无所谓的嵌套问题),可是其在安卓 IOS 上的渲染能力,动画能力是十分的惊人

最后简单说一下,企业项目十分花里胡哨的话,可能生态中并无良好的解决方案,这就须要改一些现有的源码,什么和开发者沟通我该怎么实现,这也为何企业选择 taro uniapp rn 等等

数据分析

  • 为何有的人说 flutter 凉了吗
  • 有的人说 2020flutter 你跳槽张薪资必备

简单的数听说话

多终端解决方案 星星数
flutter 88.3K
react-native 85.5K
taro 24.3K
uni-app 19K

虽然星星数 并不能说明什么,但在技术选型的时候,它仍是一个十分重要的参考价值,笔者最近在作本身的全栈项目相信不久会出生吧

也只好经过以文字的时候,敦促本身,其实想一想录视频也挺好的 慢慢来吧

BIOS 开启

因为简单配了台主机flutter 的运行须要 主机开启BIOS 模式

  1. 开机快速按f12或者DEL 也就是删除键 进入 BIOS(不一样的电脑型号是不尽相同的)
  2. 先不切换语言模式(通常状况下默认是 english)点击 Advanced Mode(F7)进入高级选项。
  3. 点击 Advanced,而后点 CPU Configuration。
  4. 下拉菜单找到 Intel Virtualization Technology,在其子菜单下把选项改为 Enabled。
  5. 按 F10 保存退出,开启成功。
  6. 这样通常就能够成功重启了

至于想我这样,多快的手速都进不去BIOS 的人,那可能须要简单的拆一下显卡 而后再简单的卸载一下主板的电池

20200315191116.png
上边的 乱七八糟的 唠嗑,好像跟 flutter 并无什么关系,不过
20200315103454.png

在咱们借用Asd 开启一个虚拟机设备调试的时候,可能会遇到一个问题,这就须要主机设备开启虚拟 通常状况下默认是不开启的。我是偏前端开发者,固然了看到这篇文章的你若是没在开发app 也不要走,由于技术就是金钱

flutter_du 初始化

准备

  • 图片素材 登陆页的背景图 免费图库相片 中文 台

    20200316211452.png

    assets:
        - lib/images/login_bg1.jpeg
        - lib/images/login_bg2.jpeg
        - lib/images/login_bg3.jpeg
        - lib/images/login_bg4.jpeg
    复制代码
  • 状态管理:全局状态管理方案(这一点在实际的开发中也是十分必要的)

  • 插件:Flutter Provider Snippets vscode 插件 类和方法的集合 也规范化provider 的书写

能够参考阅读一下我以前的分享 Flutter 状态管理一锅端:第一章 Provider ,这篇简单的介绍了如何在一个项目中管理数据,固然了即便是项目很简单,统一的管理数据能够尽量的方便后期的维护,视图UI层与数据状态层分离

实现效果

实现的效果是底部轮播图,全屏的滑动,因为这个效果图,我搞的gif 有点大7,8M ,放这个图片吧

20200316231227.png

目录结构

彻底新建一个新的flutter项目 删除 main.dart 中的文件先,保留一个整洁的开始,它暂时是这样的

20200316213457.png

├─lib
│  ├─pages
│  │  └─login
│  └─provider
└─test
复制代码

provider

首先先实现provider 登陆的状态管理,其中主要就是运用到的动画 相关的内容。动画相关的内容推荐阅读

那么刚开始咱们就直接使用provider 是的,渐进式开发,遇到问题,解决问题。开发的过程当中,咱们能够本身写包而后上传到 flutter pub

基本结构

import 'package:flutter/material.dart';
class LoginProvider extends State<StatefulWidget> with ChangeNotifier, TickerProviderStateMixin {


  @override
  Widget build(BuildContext context) {
    return null;
  }
}
复制代码

状态 init

所谓的状态 init ,就是咱们逻辑部分所用获得的初始化的数据,通常是空的 list 或者字符串等

Animation<double> bgAnimation;  // 动画的
  AnimationController bgController; // 控制器 文本输入一样有控制器
复制代码
double mainPicOp = 1; // 透明度
  double otherPicOp = 0; // 透明度
复制代码
List<String> imgsList; // 背景图轮播的素材列表

imgsList = List<String>(); // 初始化
imgsList.add('lib/images/login_bg1.jpeg');
imgsList.add('lib/images/login_bg2.jpeg');
imgsList.add('lib/images/login_bg3.jpeg');
imgsList.add('lib/images/login_bg4.jpeg');
复制代码
int mainPicIndex = 0; // 当前正在显示的图片编号
  int otherPicIndex = 1; // 备胎是1
复制代码

定时器

须要咱们初始化定时器,让图片的透明度切换

Timer dingShiQi; // 定时器

  dingShiQi = Timer.periodic(Duration(seconds: 2), (cb) {
      bgController.forward(from: 0);
    });
复制代码

主要逻辑

if (state == AnimationStatus.completed) {
          mainPicIndex = mainPicIndex + 1;
          otherPicIndex = otherPicIndex + 1;
          if (mainPicIndex == imgsList.length) {
            mainPicIndex = 0;
          }
          if (otherPicIndex == imgsList.length) {
            otherPicIndex = 0;
          }

          mainPicOp = 1.0;
          otherPicOp = 0.0;
          notifyListeners();
        }
复制代码

该释放的释放,该取消的取消

void dispose() {
    dingShiQi.cancel();
    bgController.dispose();
    super.dispose();
  }
复制代码

ui 布局

使用 provider

拿到provider 这样咱们在无状态的组件中一样能够来取自如的使用数据

LoginProvider provider = Provider.of<LoginProvider>(context);
复制代码

核心代码

Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => LoginProvider()),
      ],
      child: Consumer<LoginProvider>(
        builder: (context, counter, _) {
          return MaterialApp(home: LoginPage());
        },
      ),
    );
  }
复制代码

上边分享的代码是十分有必要的,由于provider 有个一次大的更新就是废除了build 而后改为了create

  • MultiProvider 这是在须要多个 provider
  • Consumer 至关因而监听订阅的变化

错误解决

其实在开发的过程当中,有错误是十分正常的,也是十分常见的,尤为是在flutter 的开发中。更是一些莫名奇妙的问题,

  • 不像 web 端有console.log console.table 等等直接能够在控制台打印输出

方案

  • 一种有效的方案是世界flutter run ,不要慌,不要怕 在 flutter 的开发中控制台一大大大大长串的错误非常常见
  • 20200316215722.png

主要就是参考关键字 常见的关键字 而后出门google 也能够推门 Flutter 实际项目开发中踩坑大合集(持续更新..)

  • 还有一种方案是 ,利用编辑器的调试工具

20200316220335.png

像这种一上来就是什么堆栈溢出 不过通常状况下,就是我们的程序写的有问题

小结

本篇章的上半段呢即是对客户端项目的初始化,其中使用到了动画 这也是 flutter ui 中的核心力量,优雅的渲染能力;还有就是provider 在闲鱼的fish redux 等等等等一系列的状态管理实践中,固然了,使用哪一个都行,但我以为provider 不错。(前提是在用对的状况下)

实现模块

主要是登陆页的一个背景图,透明度的切换,并无什么实质的内容。

预览

再更新的话,即是,客户端登陆,而后请求到公网的数据,至于接口是什么,我想能够继续往下翻一番,感受文章很随意的话,也请随意的分享 一下,请注明,来源

Koa2 初始化

后端的服务选型 ,最初有几种方案

  • node 原生开撸 (这个主要是写的太啰嗦)
  • Express Node 的一个框架(早期的)这个也行
  • Koa2 如今通常都更新到第二代了,V2.11.0
  • eggjs 这个企业级约定俗称也还不错(在笔者的其余项目用了,这个 flutter 就不用了)
  • Nestjs 这个同步接口文档十分方便(属于尝鲜玩法,也在笔者的其余项目用了,那就也先不用在这儿)

因此这个就先用大名鼎鼎的Koa2 先来讲说我对 koa2 的理解

  • 洋葱模型,兜一圈又回来
  • 异步编程 能够说 koa2 很好实践 js 异步编程理念
  • 可定制化,根据习惯随意开发,拓展性强

项目目录

├─app
│  └─api
│      └─v1  // RESTful API 接口规范 v1 版本
├─core    // 核心的代码
└─test
复制代码

开发依赖

在前端的项目中,package.json 通常这个就是管理包文件的,那在咱们的flutter 项目中使用pubspec.yaml 文件是同样的,差很少

"dependencies": {
    "koa": "^2.11.0", // 这个是项目依赖的koa 虽然名字仍是koa但她已是2代了
    "koa-router": "^8.0.8", // 这个是路由跳转的
    "require-directory": "^2.1.1"  // 这个等会再说
  }
复制代码

固然了这只是项目的初始化,后续更新会在此基础上,因此仍是强烈建议看一下的。也能够收藏,没事的时候翻出来看看

入口文件

目前仍是用js 来开发,若是对ts 感兴趣也能够推门

const Koa = require("koa");
const app = new Koa();
const Init = require("./core/init");
Init.entrance(app);
app.listen(3000);
复制代码

在这里咱们新建了一个 主要就是初始化应用的,这一点在 NuxtNext 等等的框架中都有核心的体现,包括在egg js 中 ,

  1. 导入 koa require("koa");
  2. 实例化 new Koa();
  3. 引入核心代码 require('./core/init')
  4. 传入 app Init.entrance(app)
  5. 监听端口 3000

还有一点十分重要就是在node 环境中模块化的规范不一样于es6import 至于模块化规范,自行右转google

应用初始化类

// 初始化加载路由
const requireDirectory = require("require-directory");
const Router = require("koa-router");
class Init {
  // 入口方法
  static entrance(app) {
    Init.app = app;
    Init.initRoute();
  }
  // 路由导入初始化
  static initRoute() {
    requireDirectory(module, "../app/api", { visit: visitor });

    function visitor(obj) {
      if (obj instanceof Router) {
        Init.app.use(obj.routes());
      }
    }
  }
}
module.exports = Init;
复制代码

在这个文件中,咱们引入了上文说起的require-directory

var requireDirectory = require('require-directory'),
  visitor = function(obj) {
    return obj(new Date());
  },
  hash = requireDirectory(module, {visit: visitor});
复制代码

意识是说咱们能够导入文件,那么咱们要作的就是自动化注入路由文件

  • user.js

    // 我的中心v1-api
    const Router = require("koa-router");
    
    const router = new Router();
    
    router.get(`/api/v1/user`, (ctx, next) => {
      ctx.body = {
        code: 0
      };
    });
    module.exports = router;
    复制代码

咱们总体遵循RESTful API 风格 api 不得不思考一个问题,关于后端接口迭代的问题,这也是为何咱们调用的接口会有v1 v2 版本前缀,这对于规范化开发十分重要。因此咱们把路由文件这样安排

- app
 	- v1
 	  - home.js
 	- v2
 	- v3
 	……
复制代码

启动后台服务

npm i // 没有几个包下起来很快
npm i -g nodemon // 自动监听文件变化,这在node开发过程当中十分重要
复制代码
nodemon app.js
复制代码

直接在浏览器输入:

本地 3000 端口,可尝试直接点击

当看到接口返回的信息就说明后台的基本服务已经启动了,可是这还远远不够,远远的不够

20200315121326.png

{
  "code": 0,
  "data": {
    "nickName": "yayxs",
    "fav": [
      {
        "id": 1,
        "type": "writing"
      }
    ]
  },
  "msg": "获取用户信息成功"
}
复制代码
  • 1、这是咱们本身的本地服务,当写了十分炫酷的服务,显然是不便分享到他人的
  • 2、这也仍是咱们本身的写的测试的 json 数据,并无链接数据库

顺便先说一下,这个项目我们用MySql ,感兴趣的话,你也能够推门 Node | 自我爬虫掘金专栏文章

服务器部署

核能预警

一提到服务器相关的知识不要慌,咱们一步步来实现公网IP 部署node 服务

目标

目标一:掌握 pm2 部署 node JS 服务 进行守护,进程

目标二:掌握基本的 nginx 反向代理

目标三:暂时拜别本地服务

效果

当咱们在任何一台有网的电脑上地址栏输入 http://62.234.111.140/api/v1/user,便会成功的返回咱们所写接口返回的数据

20200315132602.png

云服务器准备

为何说第一步要准备云服务器,由于哪怕你用原生html 或者说什什么的ssr渲染框架 或者说jQ

等等吧,哪怕是以前读书作的告白网站

81OIpD.gif

你总得放在公网上吧,否则总不能把女孩子拉到本身的家里,而后npm run start等等,你看你看………………

  • X里云
  • X讯云
  • X为云

因此仍是国内的BATH 等等这几家的,都差很少真的不骗你

问:什么是云服务器?什么是域名解析?什么是部署?怎么反向代理?那你能帮帮我吗??

答:你玩游戏的是啥 ,电脑,云服务器就是电脑(固然了在这里是不正确的也不是很准确的)

虽然我没有那么浪漫和骚,可是我有云服务器 仍是包年的 公网 IP http://62.234.111.140/

但愿大佬们不要对我作坏事情,跪拜

链接远程服务器

20200315123339.png

输入用户名密码链接就好了

20200315123519.png
这样就能够了,我发现真的是 废话好多啊 具体的细节,若是你们有什么疑问,能够再评论区留言,能力所及,会回复的。首先上来的时候要安装几个东西

npm i -g node  // 全局安装最新版的node环境
npm i -g pm2 // 全局安装线程管理
 // 等等等等
复制代码

20200315124004.png

一切的环境准备好以后,就须要同步一下我们的服务端代码到云服务器 这一点一样十分的重要,否则就没得进行了。

依赖三方服务器

总得有个同步的服务器,咱们选择github 服务器,这样在云服务也好,仍是我们的本地电脑,代码最起码丢不了

我是放在了home 文件夹下

20200315124901.png

那就装依赖呗,老套路

cd /home/flutter-koa2-du/koa2-server
npm i
复制代码

趁着也安装一下nodemon 吧 答应我安装下好吗npm-nodemon

虽说咱们也是在本地开发而后同步到云服务器,云服务器的代码通常不会怎么变更,

20200315125346.png

启动项目

20200315125503.png

不要慌,解决问题

  • 问题缘由:主要是在同一环境 3000 这个端口已经被占用
  • 解决问题:那就关闭 3000 进程

这个时候咱们就要引入PM2 纯纯的大写的高调的pm2

Pm2 应用

首先来看下pm2 干些什么

  • 内建负载均衡(使⽤ Node cluster 集群模块、⼦进程,能够参考朴灵的《深⼊浅出 node.js》⼀书 第九章)
  • 线程守护,keep alive
  • 0 秒停机重载,维护升级的时候不须要停机.
  • 如今 Linux (stable) & MacOSx (stable) & Windows (stable).
  • 多平台⽀持
  • 停⽌不稳定的进程(避免⽆限循环)
  • 控制台检测 id.keymetrics.io/api/oauth/l…
  • 提供 HTTP API

可能如今这样说,也没设好理解的,是有一个这样的场景,云服务器的环境可不像咱们本地的电脑,即开发环境(dev),一旦上线,会有各类复杂的问题出现,致使程序崩掉。不可以为咱们提供服务

配置
npm install -g pm2
pm2 start app.js --watch -i 2
// watch 监听⽂件变化
// -i 启动多少个实例
pm2 stop all
pm2 list
pm2 start app.js -i max # 根据机器CPU核数,开启对应数⽬的进程
复制代码

这只是简单的配置,详细的玩法能够自行右转google 也能够下翻参阅文章关于pm2 的分享,固然了仍是要滑上来的

pm2 list

20200315130743.png

咱们能够经过pm2 list 查看进程启动状况,显然咱们的项目已经在云服务器的3000端口启动了,那么这个时候咱们把进程stop all 停掉

pm2 stop all

20200315131034.png

咱们经过命令先听到 3000 端口

pm2 优雅启动 node 进程服务

pm2 start app.js -i max -n node-koa-pm2
复制代码

详细的含义自行google , ok 到这里应该就没什么问题了

curl 自行访问测试

curl http://127.0.0.1:3000/api/v1/user
复制代码

20200315132303.png

Nginx 反向代理

是这样的,咱们考虑一下,接口访问的时候怎么才优雅,也不知道端口是 3000 啊,因此须要一个代理服务器

  • 正向代理 :*******
    20200315133216.png
  • 反向代理:像这种就是反向代理,具体右转google
安装

直接在云服务器经过yum 就能够了我以为

yum install nginx
-----
apt update
apt install nginx
复制代码

而后怎么办呢,触及到个人知识盲区了,仍是不要慌,遇到问题解决问题。勇于试错吧

20200315133555.png

nginx -v

查看当前云服务安装的nginx 版本

nginx -t

查看配置,这很重要,由于它会定位到nginx的主要配置所在的位置 ,不一样的安装方式所在的位置是不一样的如下是笔者的

nginx: the configuration file /www/server/nginx/conf/nginx.conf syntax is ok
nginx: configuration file /www/server/nginx/conf/nginx.conf test is successful

复制代码
cat nginx.conf

查看nginx 主要的配置文件,

…………………………
server
    {
        listen 888;
        server_name phpmyadmin;
        index index.html index.htm index.php;
        root  /www/server/phpmyadmin;

        #error_page   404   /404.html;
        include enable-php.conf;

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

        location ~ /\.
        {
            deny all;
        }

        access_log  /www/wwwlogs/access.log;
    }
include /www/server/panel/vhost/nginx/*.conf;
}

复制代码

主要的要看重这句话include /www/server/panel/vhost/nginx/*.conf;

意思是说会引入后缀名.conf 的文件做为配置的一部分,因此当咱们新增配置的时候,文件名要是.conf 这样 nginx 会导入并做为配置

20200315185013.png

这时候咱们只须要新建一个**.conf 的文件就可,添加以下的配置

server {

        listen 80;
        server_name  localhost;
        location /api {
            proxy_pass http://127.0.0.1:3000;
		}
}

复制代码

咱们新增的nginx 配置并无包括

  • 静态路由的配置
  • 等等
重启 nginx
nginx -s reload
复制代码

没什么错误的话,应该就能够了, 这个时候验证一下本身的成果

{
  "code": 0,
  "data": {
    "nickName": "yayxs",
    "fav": [
      {
        "id": 1,
        "type": "writing"
      }
    ]
  },
  "msg": "获取用户信息成功"
}
复制代码

一个简单的get 请求旧简单的部署到服务器上

构建高可用的 node 环境

在干撸node 的时候,如何当进程抛出错误的时候。构建高可用的 node 是十分有必要的以下的代码可参考阅读,方便理解在服务端的环境下为何须要 PM2 来管理进程

  • app.js
// app.js
// 引入koa
const Koa = require("koa");
// 建立⼀个Koa对象表示web app自己:
const app = new Koa();
// 对于任何请求,app将调⽤该异步函数处理请求:
app.use(async (ctx, next) => {
  // 随机产⽣错误
  Math.random() > 0.9 ? yayxs() : "2";
  await next();
  ctx.response.type = "text/html";
  ctx.response.body = "<h1>success</h1>";
});
if (!module.parent) {
  app.listen(3000);
  console.log("app started at port 3000...");
} else {
  module.exports = app;
}
复制代码
  • test.js
// test.js
var http = require("http");
setInterval(async () => {
  try {
    await http.get("http://localhost:3000");
  } catch (error) {}
}, 1000);
复制代码
  • cluster.js
var cluster = require("cluster");
var os = require("os"); // 获取CPU 的数量
var numCPUs = os.cpus().length;
var process = require("process");
console.log("numCPUs:", numCPUs);
var workers = {};
if (cluster.isMaster) {
  // 主进程分⽀
  cluster.on("death", function(worker) {
    // 当⼀个⼯做进程结束时,重启⼯做进程 delete workers[worker.pid];
    worker = cluster.fork();
    workers[worker.pid] = worker;
  });
  // 初始开启与CPU 数量相同的⼯做进程
  for (var i = 0; i < numCPUs; i++) {
    var worker = cluster.fork();
    workers[worker.pid] = worker;
  }
} else {
  // ⼯做进程分⽀,启动服务器
  var app = require("./app");
  app.use(async (ctx, next) => {
    console.log("worker" + cluster.worker.id + ",PID:" + process.pid);
    next();
  });
  app.listen(3000);
}
// 当主进程被终⽌时,关闭全部⼯做进程
process.on("SIGTERM", function() {
  for (var pid in workers) {
    process.kill(pid);
  }
  process.exit(0);
});
复制代码

其余

近期感想

这一段的时间,上下班的时间一直在想产品 的相关的问题,才知道设计一个东西是多么的难,思惟很混乱,这也是为何这么久没更新(当初说好的一周一更呢)。

  • 刚开始多是面向本身,孤独的本身
  • 接着可能会面向 B 端用户
  • 大众的 C 端产品

每一个人的思路,每一个人的共享对于产品的诞生是多么的重要

  • 哪怕一个实习生
  • 哪怕一个刚开始企业开发的小生
  • 哪怕一个宏观架构的大佬

都同样的,惟一不一样的是工资 不同的,当个爱好,没事分享分享

总结

这篇文章包含了两个大的方向flutternode

  • 如何从新出发,构思一个简答的跨端 app ,登陆页
  • 如何从 0 开始搭建一个简单的 node 后台服务,实现前端人的后端梦
  • 如何入门了解nginx 等服务端运维相关的知识,即便皮毛

求求

感受有意思的也但愿一切探讨。完整项目的 github 仓库地址独 °,真的但愿能给个stat 这也是为何我从新构思继续开发。

20200315190644.png

行文思路

参考阅读

相关文章
相关标签/搜索