那個執拗的少年回來了 《孤島 App》這個系列已經停更了好久,此次全面升級,對的起給我star
的老鐵們php
APP 名稱:《獨 °》css
客戶端方案:flutterhtml
服務端方案:Koa2前端
寫着完善着vue
- 個人
- 我的信息
- 頭像修改
- 首頁
- 列表頁
- 發送圖文、視頻等
……
複製代碼
當開始全面更新迭代的時候,沒有產品
的思惟是多麼可怕的一件事,開發的過程當中會同步更新系列文章,但願一塊撩一撩flutter
固然了這些文章都是沒有更新的node
lsolated_island_app
這個分支。想要翻看的能夠自行
clone
《獨》全部的開發如今
master
分支。
可能舊的文檔地址找不到的狀況,這個我後續更新一下
以前個人評論區react
我以爲本身開發個小 app 也挺好玩的。nginx
但願多多鼓勵很關注,有不恰當的地方也歡迎指正。git
友情建議:es6
最近一段時候因爲公司需求,筆者在用 Vue 生態的 uniapp 技術棧來開發 app,整體體驗是不太好的
不作什麼橫向對比,在正確使用 flutter 的前提下,flutter 開發的應用是相比於 uniapp 好不少的(這只是我我的見解)
我的感受 flutter 的學習成本仍是比較高的,若是公司想要經過這個技術來開發的話,可能須要有同事持續跟進 flutter 的生態發展,並按期分享給成員,由於 flutter 生態是愈來愈活躍,技術的更新迭代是至關的迅速,相關的第三方包插件今個能用。明天可能你就不知道咋回事了
flutter 只是一個簡單的 UI(這裏特別說一下並無所謂的嵌套問題),可是其在安卓 IOS 上的渲染能力,動畫能力是十分的驚人
最後簡單說一下,企業項目十分花裏胡哨的話,可能生態中並無良好的解決方案,這就須要改一些現有的源碼,什麼和開發者溝通我該怎麼實現,這也爲何企業選擇 taro uniapp rn 等等
簡單的數聽說話
多終端解決方案 | 星星數 | |
---|---|---|
flutter | 88.3K | |
react-native | 85.5K | |
taro | 24.3K | |
uni-app | 19K |
雖然星星數
並不能說明什麼,但在技術選型的時候,它仍是一個十分重要的參考價值,筆者最近在作本身的全棧項目相信不久會出生吧
也只好經過以文字的時候,敦促本身,其實想一想錄視頻也挺好的
慢慢來吧
因爲簡單配了臺主機flutter
的運行須要 主機開啓BIOS
模式
f12
或者DEL
也就是刪除鍵 進入 BIOS(不一樣的電腦型號是不盡相同的)至於想我這樣,多快的手速都進不去BIOS
的人,那可能須要簡單的拆一下顯卡
而後再簡單的卸載一下主板的電池
亂七八糟的
嘮嗑,好像跟
flutter
並無什麼關係,不過
在咱們借用Asd
開啓一個虛擬機設備調試的時候,可能會遇到一個問題,這就須要主機設備開啓虛擬
通常狀況下默認是不開啓的。我是偏前端開發者,固然了看到這篇文章的你若是沒在開發app
也不要走,由於技術就是金錢
圖片素材 登陸頁的背景圖 免費圖庫相片 中文 臺
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
,放這個圖片吧
徹底新建一個新的flutter項目
刪除 main.dart 中的文件先,保留一個整潔的開始,它暫時是這樣的
├─lib
│ ├─pages
│ │ └─login
│ └─provider
└─test
複製代碼
首先先實現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 ,就是咱們邏輯部分所用獲得的初始化的數據,通常是空的 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();
}
複製代碼
拿到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
provider
其實在開發的過程當中,有錯誤是十分正常的,也是十分常見的,尤爲是在flutter
的開發中。更是一些莫名奇妙的問題,
console.log
console.table
等等直接能夠在控制檯打印輸出flutter run
,不要慌,不要怕 在 flutter 的開發中控制檯一大大大大長串的錯誤非常常見主要就是參考關鍵字
常見的關鍵字 而後出門google
也能夠推門 Flutter 實際項目開發中踩坑大合集(持續更新..)
像這種一上來就是什麼堆棧溢出
不過通常狀況下,就是我們的程序寫的有問題
本篇章的上半段呢即是對客戶端項目的初始化,其中使用到了動畫
這也是 flutter ui 中的核心力量,優雅的渲染能力;還有就是provider
在閒魚的fish redux
等等等等一系列的狀態管理實踐中,固然了,使用哪一個都行,但我以爲provider
不錯。(前提是在用對的狀況下)
主要是登陸頁的一個背景圖,透明度的切換
,並無什麼實質的內容。
再更新的話,即是,客戶端登陸,而後請求到公網的數據,至於接口是什麼,我想能夠繼續往下翻一番,感受文章很隨意的話,也請隨意的分享
一下,請註明,來源
後端的服務選型 ,最初有幾種方案
因此這個就先用大名鼎鼎的Koa2
先來講說我對 koa2 的理解
├─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);
複製代碼
在這裏咱們新建了一個類
主要就是初始化應用的,這一點在 Nuxt
和 Next
等等的框架中都有核心的體現,包括在egg js
中 ,
還有一點十分重要就是在node
環境中模塊化的規範不一樣於es6
的import
至於模塊化規範,自行右轉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
複製代碼
直接在瀏覽器輸入:
當看到接口返回的信息就說明後臺的基本服務已經啓動了,可是這還遠遠不夠,遠遠的不夠
{
"code": 0,
"data": {
"nickName": "yayxs",
"fav": [
{
"id": 1,
"type": "writing"
}
]
},
"msg": "獲取用戶信息成功"
}
複製代碼
順便先說一下,這個項目我們用MySql
,感興趣的話,你也能夠推門 Node | 自我爬蟲掘金專欄文章
核能預警
一提到服務器相關的知識
不要慌,咱們一步步來實現公網IP
部署node 服務
目標一:掌握 pm2 部署 node JS 服務 進行守護,進程
目標二:掌握基本的 nginx 反向代理
目標三:暫時拜別本地服務
當咱們在任何一臺有網的電腦上地址欄輸入 http://62.234.111.140/api/v1/user
,便會成功的返回咱們所寫接口返回的數據
爲何說第一步要準備雲服務器,由於哪怕你用原生html
或者說什什麼的ssr渲染框架
或者說jQ
等等吧,哪怕是以前讀書作的告白網站
你總得放在公網上吧,否則總不能把女孩子拉到本身的家裏,而後npm run start
等等,你看你看………………
因此仍是國內的BATH
等等這幾家的,都差很少真的不騙你
問:什麼是雲服務器?什麼是域名解析?什麼是部署?怎麼反向代理?那你能幫幫我嗎??
答:你玩遊戲的是啥 ,電腦,雲服務器就是電腦(固然了在這裏是不正確的也不是很準確的)
雖然我沒有那麼浪漫和騷,可是我有云服務器 仍是包年的 公網 IP http://62.234.111.140/
但願大佬們不要對我作壞事情,跪拜
輸入用戶名密碼鏈接就好了
這樣就能夠了,我發現真的是 廢話好多啊 具體的細節,若是你們有什麼疑問,能夠再評論區留言,能力所及,會回覆的。首先上來的時候要安裝幾個東西npm i -g node // 全局安裝最新版的node環境
npm i -g pm2 // 全局安裝線程管理
// 等等等等
複製代碼
一切的環境準備好以後,就須要同步一下我們的服務端代碼到雲服務器
這一點一樣十分的重要,否則就沒得進行了。
總得有個同步的服務器,咱們選擇github
服務器,這樣在雲服務也好,仍是我們的本地電腦,代碼最起碼丟不了
我是放在了home
文件夾下
那就裝依賴唄,老套路
cd /home/flutter-koa2-du/koa2-server
npm i
複製代碼
趁着也安裝一下nodemon
吧 答應我安裝下好嗎npm-nodemon
雖說咱們也是在本地開發而後同步到雲服務器,雲服務器的代碼通常不會怎麼變更,
啓動項目
不要慌,解決問題
這個時候咱們就要引入PM2
純純的大寫的高調的pm2
首先來看下pm2
幹些什麼
可能如今這樣說,也沒設好理解的,是有一個這樣的場景,雲服務器的環境可不像咱們本地的電腦,即開發環境(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
查看進程啓動狀況,顯然咱們的項目已經在雲服務器的3000
端口啓動了,那麼這個時候咱們把進程stop all
停掉
咱們經過命令先聽到 3000 端口
pm2 start app.js -i max -n node-koa-pm2
複製代碼
詳細的含義自行google
, ok 到這裏應該就沒什麼問題了
curl http://127.0.0.1:3000/api/v1/user
複製代碼
是這樣的,咱們考慮一下,接口訪問的時候怎麼才優雅,也不知道端口是 3000 啊,因此須要一個代理服務器
google
直接在雲服務器經過yum
就能夠了我以爲
yum install nginx
-----
apt update
apt install nginx
複製代碼
而後怎麼辦呢,觸及到個人知識盲區了,仍是不要慌,遇到問題解決問題。勇於試錯吧
查看當前雲服務安裝的nginx
版本
查看配置,這很重要,由於它會定位到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
複製代碼
查看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 會導入並做爲配置
這時候咱們只須要新建一個**.conf
的文件就可,添加以下的配置
server {
listen 80;
server_name localhost;
location /api {
proxy_pass http://127.0.0.1:3000;
}
}
複製代碼
咱們新增的nginx
配置並無包括
nginx -s reload
複製代碼
沒什麼錯誤的話,應該就能夠了, 這個時候驗證一下本身的成果
{
"code": 0,
"data": {
"nickName": "yayxs",
"fav": [
{
"id": 1,
"type": "writing"
}
]
},
"msg": "獲取用戶信息成功"
}
複製代碼
一個簡單的get
請求舊簡單的部署到服務器上
了
在幹擼node
的時候,如何當進程拋出錯誤的時候。構建高可用的 node 是十分有必要的以下的代碼可參考閱讀,方便理解在服務端的環境下爲何須要 PM2 來管理進程、
// 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
var http = require("http");
setInterval(async () => {
try {
await http.get("http://localhost:3000");
} catch (error) {}
}, 1000);
複製代碼
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);
});
複製代碼
這一段的時間,上下班的時間一直在想產品
的相關的問題,才知道設計一個東西是多麼的難,思惟很混亂,這也是爲何這麼久沒更新(當初說好的一週一更呢)。
每一個人的思路,每一個人的共享對於產品的誕生是多麼的重要
都同樣的,惟一不一樣的是工資
不同的,當個愛好,沒事分享分享
這篇文章包含了兩個大的方向flutter
與 node
nginx
等服務端運維相關的知識,即便皮毛感受有意思的也但願一切探討。完整項目的 github 倉庫地址獨 °,真的但願能給個stat
這也是爲何我從新構思繼續開發。