Flask 創建一個二手書籍線上購物平台-開發階段規劃

Flask 創建一個二手書籍線上購物平台-開發階段規劃

本系列文章是紀錄研究所軟體工程課程修課實作過程

專案時程規劃

考慮到組員在 11 月和 12 月有許多課程報告需要完成,我們將系統程式的開發時間拉長了 5 到 6 週。預計在 12 月搭建測試環境,並開放給負責系統測試的團隊成員進行測試。他們將在測試期間收集所有未通過的案例問題。在最後兩週,開發人員將解決這些問題。以下是我們的專案時程表。

專案時程規劃

什麼是 WBS?

工作分解結構(Work Breakdown Structure,簡稱 WBS)是在專案管理中常用的工具,把專案的工項分解成更小、更容易管理的部分。WBS基本上是一種樹狀結構,展示了專案的主要交付物,以及為了完成這些交付物所需的各種任務和活動。

WBS

我們使用這套架構來規劃分派給團隊成員的工作。在這個階段,我們的交付內容是開發程式。我們根據使用者案例圖和活動圖,規劃出大約 24 個 API 的開發任務。基於業務流程的順序性,我們討論了任務開發的優先順序,並在下面的 Level 和 WBS 圖表中標示出來,最後將這些任務分派給負責開發和 code review 的團隊成員。

專案WBS

程式碼提交與整合

此專案使用 GitLab 做程式碼儲存、提交、整合,採 Git flow 方式整合程式碼,開發階段步驟如下:

  1. 首先開發人員要從 dev 分支拉出 feature 分支
  2. 完成程式實作後,抓最新版 dev 分支,與自己的 feature 分支,以此避免提交 merge request 會有版本衝突發生
  3. 推 local feature 分支到 remote feature 分支
  4. 於 GitLab 發 merge request,將 feature 分支合併至 dev 分支
  5. 由 code reviewer 做程式碼審閱,確認無誤後同意合併
  6. 循返往復

Git flow

Flask 創建一個二手書籍線上購物平台-系統設計

Flask 創建一個二手書籍線上購物平台-系統設計

技術可行性評估

  1. 評估組員的技能樹,大部分組員有點 Python,且有使用過 Flask 框架的經驗,故使用Flask 框架。
  2. 考量到我們只有一個月的時間開發(不含測試),且同時要修課,我們決定使用 ORM ,原因是因為組員不需要額外寫 SQL,可以加快整體開發進度。
  3. 目前業界最新技術採用前後端分離,我們也是考量到時間,且組內的組員對 JavaScript 沒有到很熟悉,我們改由後端來渲染前端模板。
  4. 三層式架構雖然會增加開發時間,採用它的原因在於好測試,最主要這門課程著重在「測試」。

系統部署架構

硬體資訊

硬體 作業系統 安裝模組
Client PC Windows / MacOS/ Linux
Web Server AWS EC2 (Amazon Linux) nginx-1.23.4
Application Server AWS EC2 (Amazon Linux) Gunicorn 21.2.0
Database Server AWS RDS PostgreSQL 15

使用者會透過 PC 瀏覽我們二手書籍線上購物平台,期間發出的 Http request 會打到 Nginx 反向代理伺服器,再導向應用程式伺服器,在應用程式伺服器裡面運行 gunicorn 的網頁伺服器,如若涉及操作資料庫,會再到 PostgreSQL 做操作。

實際營運環境要用什麼樣的硬體規格,會在測試階段環節確認出來。

Image

系統軟體架構設計

系統使用 Python 的 Flask 網頁伺服器框架作為 MVC 框架,並且採用了三層式架構。首先是Controller層,負責接收和處理 Http request,同時處理與 View 的交互。第二層是Service層,專門負責處理商業邏輯,確保系統邏輯的一致性和有效性。最後一層是 ORM 層,主要負責操作 Model 並與 PostgreSQL 資料庫伺服器進行溝通。

選擇三層式架構的原因在於可以實現元件之間的解耦,提高模組的內聚力,同時為未來的開發工作提供方便,特別是在進行單元測試和整合測試時。這樣的結構使得不同層次的功能分明,開發人員能夠更容易地定位和處理相關模組的問題,同時也提升了代碼的可維護性和可擴展性。

模組版本資訊

1
2
3
4
5
1. Python version: 3.9
2. pytest: 7.4.3
3. Flask: 3.0.0
4. SQLAlchemy: 2.0.22
5. PostgreSQL version: 15

Image

檔案結構

在 Presentation layer 會放 Controller 模塊、跟網頁模板,在 Business layer 會放 Service 模塊,負責接收從 Controller 傳過來的資料,做完商業邏輯運算後,會使用 DBManager 模塊與 PostgreSQL 溝通操作。

Image

類別圖設計

Python 實務上不像 Java 會以類別包住 API,更常是直接在 py 檔上寫 function,所以我們 python 模塊視為類別,在類別圖上我們有特別標示 Module 字樣。

而這張類別圖是參考使用者案例圖所繪製出來的,我們將四大使用者案例轉換成 Python 模塊,細部使用者案例則設計 API 接口,承如上面所介紹的系統架構,以三層式設計,分別是 Controller、Service、DAO(ORM) 層。

大致上,負責開發的組員以這個架構下去做開發,更細部業務邏輯我們有記錄在系統設計書,提供可以組員開發做參考,在此不贅述。

Image

Flask 創建一個二手書籍線上購物平台-資料庫設計

Flask 創建一個二手書籍線上購物平台-資料庫設計

本系列文章是紀錄研究所軟體工程課程修課實作過程

資料模型設計

我們的資料庫包含8個實體,這些實體是根據使用者案例圖的設計而來。首先,我們從左上角開始進行說明。每位會員擁有一個「購物車」,而每個「購物車」可以包含多個「購物車品項」,這些「購物車品項」來自於「品項」實體,形成一對一的關係。

在二手平台中,我們從學長姐那裡收集書籍,將這些書籍的資訊登錄到「品項」實體中。由於這些品項有可能是同一本書,所以「品項」與「書籍」之間形成多對一的關係,這麼做的原因是為了區分提供者的身份。

回到前述,會員可以透過平台下單,因此每位會員可能擁有多張「訂單」,而每張訂單則包含多個「訂單商品」。「訂單商品」、「品項」、「書籍」這三個實體之間的關聯形成了訂單明細。

由於在會員模組中有一個「忘記密碼」的功能,我們計劃以系統分發token的方式來驗證會員身份,以便在後續進行密碼更改。因此,我們另外設計了一個實體,稱為「忘記密碼令牌」,以「會員的電子郵件地址」的屬性作為外部鍵,連接「會員」實體。由於我們限制系統中不允許相同的信箱重複註冊,這使得「忘記密碼令牌」和「會員」之間的實體關係保持為一對一。否則,在會員更改密碼時可能會影響到其他會員的密碼。

ERD

Image

ERD(雞爪圖)

Image

資料庫權限設計

PostgreSQL 為本專案選用關聯式資料庫管理系統

RBAC 模型(Role-Based Access Control:基於角色的訪問控制

在談論專案資料庫權限設計之前,我們不得不提及RBAC模型(基於角色的訪問控制)。RBAC模型是一種權限存取機制,由三個基本組成部分組成,即使用者、角色和許可權。這個模型強調誰(WHO)以什麼方式(HOW)可以對什麼(WHAT)進行存取。

以銀行貸款服務為例,承辦櫃員可以查詢顧客聯徵信用紀錄和初步審查貸款資料,最終由主管進行審核和撥款。因此,每個角色都擁有相對應的權責。我們的目標是使角色的權限最小化,以避免權限開放得太大。

同樣地,這種機制也可以應用在資料庫中,具有以下好處:

  1. 降低了潛在的安全風險: 如果受到SQL注入攻擊,這種機制可以防止系統使用的帳號擁有過大的權限,從而防止查詢到機敏資訊或竄改重要的資料,攻擊者僅能獲取到相應角色的權限。
  2. 細緻權限控制: 對於廠商或一般使用者,可以建立僅能讀取資料的帳號,而只有少數管理者能夠擁有寫入資料的權限,從而確保嚴格的讀寫權責的實施。
  3. 簡化權限管理:每個角色擁有特定的權限,無需針對每個使用者進行單獨的權限分配,只要分配對應的角色給使用者即可。

實作

我們將 8 張資料表創建在同一個 schema 底下,建立 rl_sel、rl_del、rl_upd、rl_ins,分別是可以對這 8 張資料表篩選、刪除、更新、新增的權限角色,再來是建立 app_001、it_001 這兩個可登入角色,app_001 是給系統使用的帳號,it_001 是給開發人員使用的帳號,此外這組帳號可以在這個 schema 新增、刪除資料庫物件。

如果後台系統資料表在另一個 schema 存放,會因為這套機制,可以有效地保護資料,不讓角色享有過大的權限,設計上,就可以成立 app_002 角色,只能對後台系統資料表操作,it_001 則可以跨 schema 對資料表操作。

Image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- 建立資料庫
CREATE DATABASE bookstore;
-- 建立 bookmazon schema
CREATE SCHEMA bookmazon;

-- 可以選資料表內容的角色
CREATE ROLE rl_sel;
-- 可以刪除資料表內容的角色
CREATE ROLE rl_del;
-- 可以更新資料表內容的角色
CREATE ROLE rl_upd;
-- 可以新增資料表內容的角色
CREATE ROLE rl_ins;

-- 為新增、刪除、修改、查詢賦予角色
GRANT SELECT ON TABLE bookmazon.book TO rl_sel;
GRANT DELETE ON TABLE bookmazon.book TO rl_del;
GRANT UPDATE ON TABLE bookmazon.book TO rl_upd;
GRANT INSERT ON TABLE bookmazon.book TO rl_ins;

-- 程式開發人員操作 db 的角色
CREATE ROLE user_001 WITH LOGIN CREATEDB CREATEROLE PASSWORD 'user_001';
GRANT rl_sel, rl_del, rl_upd, rl_ins to user_001;
-- 賦予 bookmazon schema 的使用權限
GRANT USAGE ON SCHEMA bookmazon to user_001;
-- 賦予可以 bookmazon schema 建立物件(trigger、function、procedure)的權限
GRANT CREATE ON SCHEMA bookmazon to user_001;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA bookmazon TO user_001;

-- 後端系統操作 db 的角色
CREATE ROLE app_001 WITH LOGIN PASSWORD 'app_001';
GRANT rl_sel, rl_del, rl_upd, rl_ins to app_001;
-- 賦予 bookmazon schema 的使用權限
GRANT USAGE ON SCHEMA bookmazon to app_001;
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA bookmazon TO app_001;

資料庫表空間管理

什麼是資料庫表空間?

在 PostgreSQL,表空間意思是指資料庫的管理者可以自行定義一段檔案系統的路經,讓資料庫的物件都儲存在這個資料夾底下,爾後凡有資料庫的物件,例如:資料表、索引、視圖要找地方儲存的時候,那就可以引用資料庫的管理者所定義好的路徑進行資料儲存。

為什麼要做資料庫表空間管理?

  1. 如果要資料表的資料要擴張到別的地方(新增硬碟),可以在不同的分割區上建立資料表空間。
  2. 資料庫管理者可以控制 PostgreSQL 安裝的磁碟規畫,假設資料不斷地增長,原有的檔案系統空間不足時,可以在新掛載的檔案系統建立新的表空間。
  3. 允許資料庫管理者依資料庫物件特性的知識來優化效能,比較常查詢的資料可以放在固態硬碟上,相對於不常查詢的資料,就可以放在傳統硬碟上。

實務上,如何進行?

在 PostgreSQL,資料表會以檔案方式落地在預設目錄 $PGDATA/base/,檔案以資料庫物件 OID 命名,資料庫的大小取決於磁碟分割的大小。

為了做到資料庫表空間管理,我們選擇將資料改放在 /var/pgdata/bookmazon/,在下 PostgreSQL TABLESPACE 指令之前,我們先建立 /var/pgdata/bookmazon/ 資料夾出來,讓資料夾擁有者賦予給 postgres 角色(作業系統的角色),權限設為 700。

以上操作必須做,否則在下 PostgreSQL TABLESPACE 指令,會因為作業系統的檔案權限不足而失敗。

1
2
3
4
sudo mkdir /var/pgdata
sudo mkdir /var/pgdata/bookmazon/
sudo chown -R postgres:postgres /var/pgdata
sudo chmod -R 700 /var/pgdata

我們用了 PostgreSQL TABLESPACE 指令在 /var/pgdata/bookmazon/ 建立了表空間。

1
CREATE TABLESPACE OWNER postgres bookmazontbspace LOCATION '/var/pgdata/bookmazon/';

以建立 password_reset_tokens 資料表為例,在下 CREATE TABLE 指令後面會加上 TABLESPACE [表空間名稱],來表示建立的資料表的資料要落地在哪一個磁區。

1
2
3
4
5
6
7
8
9
CREATE TABLE bookmazon.password_reset_tokens (
id SERIAL NOT NULL
, user_email VARCHAR(255) NOT NULL
, token VARCHAR(255) NOT NULL
, token_status VARCHAR(1)
, update_datetime TIMESTAMP
, create_datetime TIMESTAMP
, CONSTRAINT pk_password_reset_tokens PRIMARY KEY (id)
) TABLESPACE bookmazontbspace;

參考來源

EnterproseDB Quickstart — 快速入門筆記

PostgreSQL 繁體中文手冊 - 23.6. Tablespaces

PostgreSQL 表空間使用詳解

Flask 創建一個二手書籍線上購物平台-系統需求與分析

Flask 創建一個二手書籍線上購物平台-系統需求與分析

本系列文章是紀錄研究所軟體工程課程修課實作過程

系統需求

1. 專案目的與動機

現代教育體系中,學生每學期都需要購買大量教科書,然而,許多書籍僅在短時間內使用,之後就被束之高閣。為了解決這個問題,我們致力於建立一個二手書交流平台,讓學長姊能夠輕鬆販賣二手書,同時讓學生購書的負擔降低。這不僅有助於節省金錢,更能實現地球永續發展,減少資源浪費。

2. 系統特色

a. 公開的平台,價格透明化

我們的平台注重透明度,讓購買者能夠清晰了解市場價格,避免不合理的高價販售。這種公開的交易環境有助於建立信任,提高整個系統的效能。

b. 第三方平台管理

為了確保平台的公正性和安全性,我們引入第三方平台管理機制。這將有助於處理潛在的糾紛,確保交易的順利進行,同時為使用者提供更多保障。

3. 使用者定位

a. 不再需要用到該教科書的同學

透過平台,這些同學能夠迅速輕鬆地轉手自己不再需要的教科書,獲得一些額外的收入,同時也促進了書籍的再利用。

b. 需要教科書卻無法負擔高額費用的同學

平台使這些同學能夠以更經濟實惠的價格獲得需要的教材,減輕了他們的經濟負擔,提高了教育資源的可及性。

c. 想擁有學長姐精華筆記的同學

除了書籍,我們的平台還提供學長姐的筆記販售功能,這滿足了一部分同學對於高質量學習材料的需求。

系統分析

使用者案例圖找出誰是系統的參與人、歸納出領域、並劃分系統內的領域邊界

我們與同學深入討論後,整理出了這套系統可能的各種使用者案例,並繪製了相對應的 Use Case Diagram。系統的使用者案例主要分為四大模組,包括會員模組、商品模組、訂單模組、以及購物車模組。

1. 會員模組

會員模組涵蓋了所有使用者在系統中的身份認證和相關功能。註冊、登入、修改個人資料等功能均屬於會員模組的範疇。

2. 商品模組

商品模組提供了訪客和會員查詢、瀏覽商品的功能。即使在未登入的情況下,訪客可以方便地瀏覽並查詢系統中的商品。會員作為訪客的一種。

3. 訂單模組

訂單模組與購物車模組之間存在高度相依性。這是因為結帳購物車這個使用者案例會延伸到新增訂單,因此兩者之間有著密切的關係。這一模組負責處理使用者的訂單相關操作,包括查看歷史訂單、取消訂單等功能。

4. 購物車模組

購物車模組負責處理使用者在購物過程中的相關操作。這包括將商品加入購物車、管理購物車內商品、進行結帳等功能。購物車模組的操作直接影響到訂單模組的運作。

這套系統的構想是,訪客可以在不登入的情況下瀏覽、查詢商品,而會員則包含在訪客的範疇內,因此可以享有訪客使用的功能。特別值得一提的是,訂單模組與購物車模組之間存在相對高的相依性,這是因為結帳購物車的案例會直接連接到新增訂單的流程中。

Image

資料建模分析

活動圖(泳道圖)可以拿來疏理各個使用者案例的動向,以及了解業務流程中參與人會是誰

我們透過活動圖以泳道的方式呈現了系統的業務流程。使用者以「訪客」或「會員」身份進入網站後,開始瀏覽並查詢商品。若在這過程中點擊網頁加入購物車,系統會先檢查會員是否已登入。若未登入,系統將引導至登入畫面;若已登入,則將商品成功加入購物車。

在查看購物車期間,會員擁有刪除商品或調整商品購買數量的權限。完成購物後,系統將自動生成訂單。會員隨後可在訂單列表中查看已下訂的訂單,並追蹤其進度。若欲取消訂單,會員需提交取消申請,經系統管理員審核後方可生效。

Image

循序圖是用細部拆解使用者案例的流程

接著我們挑出比較核心的 Use case 來繪製成循序圖,方便我們梳理資料流、與業務流程,圖上的物件有會員、前端介面、後端模組、以及資料庫,整理流程為登入、瀏覽商品、加入購物,最後到結帳。

登入流程

  1. 會員或訪客進入前端介面。
  2. 前端介面發送登入請求給後端模組。
  3. 後端模組驗證登入請求,若為會員,則檢查會員身份;若為訪客,則要求註冊。
  4. 登入結果回傳給前端介面。

瀏覽商品流程

  1. 會員或訪客透過前端介面發送瀏覽商品請求。
  2. 後端模組從資料庫中搜尋商品資訊。
  3. 商品資訊回傳給前端介面。

加入購物車流程

  1. 會員或訪客透過前端介面發送加入購物車請求。
  2. 後端模組驗證使用者身份,確認是否為登入狀態。
  3. 若為登入狀態且有庫存狀態下,將商品加入購物車,並把加入結果回傳給前端介面。
  4. 如果沒有庫存,回傳「暫無庫存」給前端介面。

結帳流程

  1. 會員透過前端介面發送結帳請求。
  2. 後端模組從購物車中搜尋預購買商品,在庫存數足夠狀態下,扣除購買商品的庫存數,若不足,回傳「暫無庫存」給前端介面。
  3. 會員的購物車的商品移除,將購物商品的資訊儲存為訂單。
  4. 訂單成立後,將訂單資訊儲存到資料庫。
  5. 回傳訂單資訊給前端介面。

Image

需求訪談(補充內容)

怎麼需求獲取?

在進行需求訪談之前,充分的事前準備是必要的。事前的準備包括深入了解相關背景知識。需求訪談通常應該針對真實的使用者進行,人數最好控制在 20 人以內。根據我們的案例,可以選擇不同系所的同學進行訪談,同時也應該尋求領域專家(例如老師或其他電商平台經營者)的意見。需要注意的是,並不是每位受訪者都能充分表達對於系統需求的看法,因此訪談者可能需要巧妙設計問題,引導受訪者表達他們對系統的需求。整體過程有點像摸象,透過了解需求來逐漸塑造系統的輪廓。

怎麼確認需求?

在需求訪談結束後,我們將收集到各種意見和功能需求。基於這些資料,我們可以進行資料建模,而 UML(統一建模語言)是一個強大的工具,用來繪製系統使用者案例、設計業務流程等。接著,我們可以再次召集使用者和領域專家來檢視繪製的 UML 圖,請他們確認這是否是他們期望的系統樣貌,這是為了確保我們所想的與使用者需求是否一致。

在實務操作中,需求訪談往往需要進行多輪,且顧客的時間寶貴。因此,在召開會議之前,確保準備工作充足至關重要。需求文件要等到顧客簽署後方能確認完畢,這樣的確認流程是為了保證最終的系統需求與使用者的期望保持一致。

Snow Algorithm 雪花演算法

Snow Algorithm 雪花演算法

什麼是雪花演算法?

我們都知道在自然界當中每一片雪花都是獨一無二的。在目前提倡容器化、分散式的架構時代下,很可能會遇到 ID 重號的問題,我們對 ID 的期望就會希望雪花般一樣都不重複。

這個演算法最早是由 Twitter 所創建的,主要是拿來產推文的 ID,後來這套演算法被 Discord、Instagram採用,也衍伸許多不同格式的版本。

會想要使用這套演算法的原因是因為筆者想要拿它產生 token,快取在 redis 上,合法的使用者可以拿著這個 token 存取筆者在部署 Docker 上的微服務系統。

雪花演算法核心組成

筆者先為大家科普一下雪花演算法的組成的成分,主要共分 4 個欄位組成 64 位元,頭一個位元我們不使用,其他欄位下方逐一跟各位說明。

Image

時間戳記

第二欄位是時間戳記,共 41 個位元,以 UNIX 時間(起始年是 1970 年)的毫秒為單位正好可以填滿41 bits 的空間,可表示最大數為 2,199,023,255,551,以毫秒計算約 69 年,這個數值是兩個時戳相減得出的。

如果是拿當下的時間戳填進去這個欄位,那麼數值很快就會到達天花板,我們身為生活智慧王,當然要善用空間,所以才會以產生 token 的時間戳減去一個開始時間戳。

由於前面有提到是微服務,為了保持一致,開始時間戳需要寫死,建議可以訂在系統上線前的日期,像筆者就選擇把開始時間的時戳訂成自己的生日。

工作站台 ID

第三欄位是工作站台 ID,共 10 個位元,用來區分哪ㄧ台機器發出的 token。

流水號

第四欄位是流水號 ID,共 12 個位元,意思是同一個時間下同一機器所配發出去的流水號,從 0 開始遞增,直到進入下一個毫秒,流水號再重新編號。

演算法說明

位元分配器

筆者建立一個類別叫作 BitsAllocator,主要做兩件工作,第一件事是初起化各個欄位位元數,第二件是配發雪花演算法的唯一 ID,以下筆者會逐一說明。

Image

Image

一開始在 BitsAllocator 的建構子決定好各欄位要多長,由參考 BitsAllocator 的類別去制定,假設我們按造預設的長度,那麼建構子內的三個參數分別為 41, 10, 12。

第二步就是要製作位元遮罩,所以我們要取得這三個欄位的最大值,舉例來說如果我們想要取得工作站 ID 的最大值 2¹⁰ — 1,也就是 10 個位元都是 1。

如何取得最大值呢?在 Java 的世界中整數採用二的補數,先將 — 1 向左位移 10 個位元的位置,再位元 NOT 運算,就可以得到囉。

Image

Image

第三步要把各個欄位擺放到正確的位置,像是時間戳記需要位元要向左位移 10 + 12 個位元,工作站站台 ID 要向左位移 12 個位元,流水號則不需要移動,所以我們需要紀錄一下到時候要位移的個數。

Image

第四步就是把它們像積木一樣拼起來,筆者這邊設計一個方法,讓參考BitsAllocator 的類別呼叫 allocate,把當下的時間戳、機器的 ID、配發的流水號傳進來,該方法會把這三個欄位推到正確的位置,用到的是我們上面紀錄下來位移的各個去推,最後用 OR 運算合併。

Image

唯一性 ID 取號器

筆者設計了一個 SnowFlakeAlg 類別用來取號。

Image

一開始建構 SnowFlakeAlg 類別,首先先注入 BitsAllocator,後續我們取號的時候會使用到。由於每一個放置在 Docker 容器內每一個微服務都會分配一組 IP,所以筆者是取其 IPv4 第四個欄位作為工作站站台 ID。

這組 IP 是怎麼得來的呢?Java 會在作業系統建立 Socket 連線,使用 InetAddress.getLocalHost().getHostAddress() ,可以得到本機的主機位址。

Image

至於時戳怎麼產生呢?是呼叫 System.currentTimeMillis() 取得系統當下的時戳,記得前面我們有說雪花演算法時戳這個欄位是兩個毫秒相減得出的嗎?系統當下的時戳減去一個我們是先制定好的開始時戳,這邊我減去的是我去年的生日那天,如果結果值大於 2⁴¹ — 1,那就代表時戳佔用位元已耗盡,無法產製唯一性 ID。

會呼叫 nextMilliSecond() 方法原因在上一毫秒的流水號已經分配完,所以要循環等待到下一毫秒,為了避免因為機器硬體時鐘問題造成時戳還停留在過去,所以我們有寫了 while 迴圈,重新取時戳,同時判斷是否有大於上一個時戳的時間。

Image

SnowFlakeAlg 類別主要功能也就是取號,第一先取得系統當下的時戳,同一個毫秒下,我們就讓遞增取流水號,在取號的過程中,我們會拿遞增後的流水號跟流水號的最大值做 AND 運算,看結果值是否為 0?如果是,那代表該時戳可分配的流水號全部分玩了,需要等到下一個毫秒,再重新分配。

最後我們拿系統時戳減開始時戳、工作站 ID、流水號當作參數呼叫bitsAllocator.allocate(),就可以拿到唯一性的 ID 當作 token 使用了。

Image

完整程式碼

SnowflakeAlgo

VS Code 帶參數 debug python程式

VS Code 帶參數 debug python程式

問題描述

學姊最近問我要怎麼在 VS Code 帶參數去debug python程式,什麼是帶參數執行 Python 程式呢?也就是在 Python 腳本檔依據執行指令的參數來決定程式要跑一個環境、哪一個資料集等等,是一個非常好用的功能。

學姊遇到的問題是如果在終端機上下 python main.py –e 1000 –d datasets,沒辦法在 VS Code 上 debug,原因是因為沒有使用 VS Code debugger,就算用了,也無法指定參數內容,當跑到要吃參數的程式片段的時候會因此拋錯。

解決方法

首先,我先準備了一個範例程式碼 main.py,內容如下,在這個範例檔案會吃兩個參數,一個是 -e 迭代次數、另一個 -d 資料集,我使用了 argparse 套件來吃參數,用法在此不介紹。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

import argparse

def parse_args():
parser = argparse.ArgumentParser(description='a simple demo of argparse')

# epoch argument
parser.add_argument('-e', type=int, required=True)

# datasets path argument
parser.add_argument('-d', type=str, required=True)

# resolve arguments
args = parser.parse_args()

return args


if __name__ == '__main__':
args = parse_args()
epoch = args.e
datasets_path = args.d
print(epoch)
print(datasets_path)

點開 VS Code 左側的 Run and debug,再點選 create a lauch.json file。

Image

接著會跳出 Prompt,選擇 Python File。
Image

點選後,VS Code 就會在當前的資料夾底下,建立一個 .vscode 的資料夾,裡面放 lauch.json,我會要在 configurations 裡面多新增一個 key 叫做 args,裡面填寫我們要執行的參數值,要注意的地方是,參數跟值要分開來寫,舉例來說,原本用終端機執行 python 的指令是 python -e 1000 -d datasets,args就要這樣寫 [“-e”, “1000”, “-d”, “~/test/datasets.csv”]。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"args": ["-e", "1000", "-d", "~/test/datasets.csv"],
"justMyCode": true
}
]
}

Image

為何要這樣設定的原因是因為 VS Code 可以根據我們的參數設定來執行、除錯程式。

點開 VS Code 左側的 Run and debug,按下綠色 Play 按鈕就可以開始 debug,我是在程式碼的第 23 行下斷點,在 watch 監控 epoch、datasets_path 這兩個變數的內容,可以看到我們在 launch.json 設定的參數。

Image

參考文件

https://code.visualstudio.com/docs/cpp/launch-json-reference

在 Visual Studio Code 遠端開發(客戶端篇)

在 Visual Studio Code 遠端開發(客戶端篇)

研究所有時候要跑機器學習的東西,但用自己的主機跑又很容易炸裂,每次遠端登入進去 lab 的電腦又很麻煩,我剛好看得到學長姐用 vscode 遠端回去 lab 電腦,我想說自己也來玩玩看,以下是客戶端的步驟,照著步驟操作後,就可以在本地端 vscode 做程式開發,實際算力是用 lab 的電腦。

Visual Studio Code 遠端開發設定

下載 Remote SSH 插件

在 VS Code IDE 側欄找到 Extensions,輸入 “Remote-SSH”,然後按下安裝鍵。
Image

SSH 連線資訊設定

在 VS Code IDE 左下角有一個 >< 的符號,找到後按下去,IDE 會跳出一個 prompt,輸入伺服器的 SSH 連線資訊,格式為 [帳號]@[IP位址 / 主機網域名稱] (不用打中括號)。

Image

Image

輸入完後,在在 VS Code IDE 右下角會挑出 Open Config,上一步的操作會在家目錄 ~/.ssh 中建立一個 config 檔案,裡面會紀錄我們的電腦跟其他主機的 SSH 連線資訊。
Image

打開後長這樣,裡面可以自行更改 IP 位址、Port 號、SSH 連線用的私鑰,Host 預設是我剛剛輸入的 IP位址,為了方便我識別,我改成 LAB706,所以 Host 是可以自行更改成喜歡的別名,但 HostName 不行,要輸入正確的IP位址或主機網域名稱。

Image

SSH 連線到遠端主機

確認沒問題後,就可以關閉,再次按下 VS Code IDE 左下角有一個 >< 的符號,選擇 LAB706,就可以登入到遠端伺服器。
Image

VS Code 將提示你輸入 SSH 密碼,輸入後即可連接到伺服器。

Image

連進來後,沒有跳出錯誤訊息的 prompt,且右下角顯示 LAB706,代表連線成功,那要怎麼看到遠端主機上的檔案呢?按下 VS Code Explorer,再按 Open folder,就可以看得到遠端主機上的資料夾、跟檔案,接下來可以開始做事了。

Image

Azure DevOps Stakeholder 權限看不到 Repo

Azure DevOps Stakeholder 權限看不到 Repo

問題描述

我們使用 CI/CD 平台是 Azure DevOps,我們預計想做 Side project,想把技術支援連結放在 Wiki,朋友說他有將我們每一個人權限設置為 Admin,另外也有用 git 控管 Wiki,而我遇到的問題是無法在平台的 side menu 找到 Repo 圖示,於是我跳出來組織的畫面,從 Project 的 Repo 圖示點,但卻看到 403 無權限存取。

Image

Image

解決辦法

後來我才發現原來即便專案的權限開到最大,成員仍無法存取某些功能。

  1. 建立專案後,邀請其他人加入某一專案內,此人若沒有在這個組織內,系統會將其權限設定為 Stakeholder 的角色。
  2. Stakeholder 的角色是沒有版本控制的功能可以使用,換句話說開發人員角色必須是 Basic 或 Visual Studio Subscriber。
  3. 第二點合理的原因是因為利害關係人不應該觸碰版本控制的東西,他的職責所在在於「驗收產品」。

操作設定

首先,從專案頁面跳回去組織頁面,找到 side menu 的 Organization settings

Image

從 Organization settings 找到 Users,可以發現邀請的組員都會在裡面

Image

接著對應該是開發人員的組員 Access Level 調整成 Basic 或 Visual Studio Subscriber。

Image

最後調整完,那我們就可以看到 Repo 的功能了 :D

參考資源

[Fixed] Cannot see Repos in Azure DevOps with Stakeholder Access

【把玩Azure DevOps】Day3 Organization與Projects

[Hexo] 如何在 Hexo 開發環境的文章中加入 LikeCoin button

[Hexo] 如何在 Hexo 開發環境的文章中加入 LikeCoin button

前言

LikeCoin 是一種加密虛擬貨幣,基於以太坊區塊鏈鎖延伸出來的代幣。不知道大家是不是在瀏覽網站是不是常常看到像 Medium 拍手按鈕, LikeCoin 基金會會依據別人給創作者的按讚數配發其發行虛擬幣幣給創作者,當 LikeCoin 數達到 2,000 時就可以兌換成法定貨幣。我想在自己的 Hexo 網頁放置 LikeCoin 主要原因為我很喜歡它的配色、 Logo 的設計,另一個原因是欣賞它的創作生態圈治理理念。

LikeCoin 加入到 Hexo

本篇文章是以 Hexo icarus 主題設定為主,且預設您已經註冊 LikeCoin 。

官方網站是以 Hexo next 主題為範例,而我的主題目前是使用 icarus , next 的模板引擎是用 ejs ,而 icarus 則是用 React 的 jsx ,所以設定上就有些許不同。

LikeCoin button Gitbook 官方範例

1
2
3
4
5
6
7
<div>
<script type="text/javascript">
document.write(
"<iframe scrolling='no' frameborder='0' sandbox='allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-storage-access-by-user-activation' style='height: 212px; width: 100%;' src='https://button.like.co/in/embed/[LikerID]/button?referrer=" +
encodeURIComponent(location.href.split("?")[0].split("#")[0]) + "'></iframe>");
</script>
<div>

icarus 主題預設可以設定 donate 的連結,可以透過 _config.icarus.yml 去做設定,它根據 yml 屬性找到對應連結的圖片,再渲染到前端頁面的卡片上。

Image

_config.icarus.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
donates:
# "Buy me a coffee" donate button configurations
-
type: buymeacoffee
# URL to the "Buy me a coffee" page
url: '你的buymeacoffee URL'
# Paypal donate button configurations
-
type: paypal
# Paypal business ID or email address
business: 'paypal ID'
# Currency code
currency_code: USD #收款幣別

donates.jsx 原始碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const logger = require('hexo-log')();
const { Component } = require('inferno');
const view = require('hexo-component-inferno/lib/core/view');

module.exports = class extends Component {
render() {
const { config, helper } = this.props;
const { __ } = helper;
const { donates = [] } = config;
if (!Array.isArray(donates) || !donates.length) {
return null;
}
return <div class="card">
<div class="card-content">
<h3 class="menu-label has-text-centered">{__('donate.title')}</h3>
<div class="buttons is-centered">
{donates.map(service => {
const type = service.type;
if (typeof type === 'string') {
try {
let Donate = view.require('donate/' + type);
Donate = Donate.Cacheable ? Donate.Cacheable : Donate;
return <Donate helper={helper} donate={service} />;
} catch (e) {
logger.w(`Icarus cannot load donate button "${type}"`);
}
}
return null;
})}
</div>
</div>
</div>;
}
};

魔改 icarus 原始碼

由於我是使用 npm 下載主題,所以主題外框、文章、頁面元件會放在你資料夾的 node_modules/hexo-theme-icarus/layout/common/ 底下,我是在 common 資料夾底下新建一個 jsx 檔元件,此元件去繼承 React.Component ,繼承下來的 sub class 去覆寫 render() 方法,我們方法回傳官方網站提供 iframe 標籤的內容,iframe src 屬性的 [LikerID] 換成自己的 LikerID ,最後我們將建立好的類別輸出出去,我的檔案取名叫 likecoin.jsx

likecoin.jsx

1
2
3
4
5
6
7
const { Component } = require('inferno');

module.exports = class extends Component {
render() {
return <iframe scrolling='no' frameborder='0' sandbox='allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox allow-storage-access-by-user-activation' style='height: 212px; width: 100%;' src='https://button.like.co/in/embed/[LikerID]/button?referrer=" + encodeURIComponent(location.href.split("?")[0].split("#")[0]) + "'></iframe>;
}
}

原先包住在 <div class="buttons is-centered">...</div> 原始碼以迭代方式尋找在使用者在 _config.icarus.yml 設定的 donate type 有沒有在對應的type,再把type帶進去 Donate渲染出來,那我們要改的就是這一段,接著我們將 likecoin.jsx 引入到 donates.jsx ,使用標籤將元件包住,並取代剛剛我們提到的地方,由於我們不需要再透過 config 檔尋找 donate type ,所以我有把 _config.icarus.yml donate 那一塊刪掉,原始碼原本判斷 donates 是否為陣列及陣列長度也一併移除。

donates.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const logger = require('hexo-log')();
const { Component } = require('inferno');
const LikeCoin = require('./likecoin');
const view = require('hexo-component-inferno/lib/core/view');

module.exports = class extends Component {
render() {
const { helper } = this.props;
const { __ } = helper;
return <div class="card">
<div class="card-content">
<h3 class="menu-label has-text-centered">{__('donate.title')}</h3>
<div class="is-centered">
<LikeCoin></LikeCoin>
</div>
</div>
</div>;
}
};

參考資源

  1. 如何在 Hexo 開發環境的文章中加入 LikeCoin button

  2. React.Component

[Heroku][Flask] Couldn't find that process type (web).

[Heroku][Flask] Couldn't find that process type (web).

問題描述

朋友想製造一個 LINE 機器人,自學學習撰寫 Python Flask Web Framework ,並部署到 Heroku 上,預期傳訊息時機器人會自動回覆相同的訊息內容,但卻沒有顯示在對話視窗內,於是拜託我幫他看。

首先要看的就是 Heroku console log ,下 heroku logs --tail 從裡面一定可以找出一些蛛絲馬跡,果不其然看到一些訊息,從日誌檔可得知程式是包版成功的,但很顯然程式發生錯誤, Web server 根本就沒有啟動,官方網站給出的解決方法是下 heroku ps:scale web=1 這組指令,以手動方式將 Web server 程序部署到他們的引擎 dyno 上。

Heroku 上日誌檔

1
2
2022-03-12T05:20:20.000000+00:00 app[api]: Build succeeded
2022-03-12T05:20:30.962092+00:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/favicon.ico" host=groupbuyingchatbot.herokuapp.com request_id=808970d4-836d-40e6-9f8a-1e3b3a999199 fwd="42.77.131.204" dyno= connect= service= status=503 bytes= protocol=https

Heroku H14 錯誤代碼解釋

1
2
3
4
5
6
7
H14 - No web dynos running
This is most likely the result of scaling your web dynos down to 0 dynos. To fix it, scale your web dynos to 1 or more dynos:

heroku ps:scale web=1
Use the heroku ps command to determine the state of your web dynos.

2010-10-06T21:51:37-07:00 heroku[router]: at=error code=H14 desc="No web processes running" method=GET path="/" host=myapp.herokuapp.com fwd=17.17.17.17 dyno= connect= service= status=503 bytes=

你以這樣就解決問題了嗎?並沒有!我下了這道指令回覆訊息仍是找不是 Web Server 的程序,後續我確認了 Python 有沒有語法上的錯誤、部署腳本、套件 requirements.txt,也嘗試在本機上執行,皆無問題。

1
2
3
gordonfang$ heroku ps:scale web=1
Scaling dynos... !
! couldn't find that process type (web).

解決方法

隔一天再次檢查才發現原來朋友寫的 Procfile 多了副檔名,是 Procfile.txt , Procfile 是 Heroku 的部署腳本,告訴他包版完後要跑哪一個檔案將 Web Server 啟動起來,重新調整完後機器人就能順利回覆訊息。

Image

1
2
3
4
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
message = text=event.message.text
line_bot_api.reply_message(event.reply_token, TextSendMessage(message))