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

行文思路

參考閱讀

相關文章
相關標籤/搜索