2026年10月17日 星期六

從活動照片到會動的繪本:AI 生圖與 Google Flow 影片實作

AI 創作實作|照片 × 插畫 × 影片

從活動照片到會動的繪本

把孩子操作機器人的真實活動照片,先轉成溫暖明亮的手繪插畫,再用 Google Flow 生成一段短影片,記錄科技教育活動的另一種方式。

一張活動照片記錄了當下;透過生成式 AI,我們還能把它轉化成具有繪本感的插畫,接著讓畫面中的人物和物件產生細微動作。本次以孩子們圍著 TaleMap 地圖、操作程式機器人的活動為例,實際完成「照片轉插畫」和「插畫轉影片」兩個步驟。

01|活動照片保留真實場景與互動
02|生成插畫轉換畫風,維持主要構圖
03|Flow 影片加入輕柔動作與鏡頭推近

一、從真實活動照片開始

原始照片中,孩子們圍在大型 TaleMap 地圖旁,觀察圖卡並操作白色程式機器人。照片還包含帶領活動的學生、桌面教材,以及右側的教具。生圖時最重要的是先說明哪些內容要保留:人物相對位置、姿勢、服裝、活動用品和現場情境。


二、用提示詞把照片轉成繪本插畫

提示詞可以分成四個部分:指定照片作為參考、列出要保留的內容、描述插畫風格,再補上不希望出現的改動。把構圖要求寫清楚,通常比只寫「變成可愛插畫」更容易得到符合活動紀錄用途的結果。

本次生圖提示詞

請以我上傳的照片為構圖與人物活動參考,將照片轉換成溫暖明亮的日系手繪插畫。保留照片中的主要人物、動作、互動關係、場景配置與重要物件,讓人仍能辨認原本的活動情境。

畫面採用兒童繪本插畫風格,結合水彩暈染與色鉛筆筆觸;人物比例自然可愛,表情生動親切,輪廓以細緻清楚的手繪線條呈現。使用明亮飽和的藍色、湖水綠、橘色與黃色,光線柔和,整體氣氛熱鬧、溫馨、有教育活動紀錄感。背景細節豐富但不凌亂,保留現場的展示物與活動用品。

請勿新增照片中沒有的人物或重要物件;不要改變人物的主要姿勢與相對位置。畫面呈現精緻、完整、適合活動成果展示的插畫,不要加入文字、標題、浮水印或邊框。


生成結果:保留孩子、地圖、機器人與活動互動,並轉為水彩與色鉛筆繪本風格。
觀察:插畫呈現了明亮色彩與手繪筆觸,也保留了地圖、機器人和孩子們操作教具的核心情境。生成式模型仍可能調整人物細節或重新詮釋背景,因此若要精準呈現活動紀錄,應以原照片為參考,並逐項寫明不可變動的元素。

三、把插畫放進 Google Flow 生成影片

完成插畫後,將圖片上傳到 Google Flow,選擇以圖片作為影片起始畫面的功能,再描述希望出現的動作與鏡頭運動。本次設計的是幅度較小的動作:孩子低頭看地圖、按下機器人按鍵,機器人在圖卡上緩慢前進;鏡頭則從全景稍微推近。

  1. 進入 Google Flow,建立新專案。
  2. 選擇可使用圖片作為起始畫面的影片功能(介面名稱與可用選項可能依帳號及模型而異)。
  3. 上傳生成的插畫,設定影片比例、長度與模型。
  4. 貼上動作提示詞,生成後檢查人物、地圖和機器人的連貫性。
  5. 若動作太大或場景變化過多,縮短描述並強調固定構圖,再重新生成。
本次 Flow 影片提示詞

以這張插畫作為影片起始畫面,保留孩子們圍著 TaleMap 操作機器人的構圖、服裝與人物位置。孩子自然地低頭看地圖、輕輕按下機器人按鍵,彼此微笑互動;機器人在地圖上緩慢前進。鏡頭平穩地由全景稍微推近,呈現溫暖明亮的兒童繪本氣氛,水彩與色鉛筆筆觸保持一致。動作自然、幅度小,不增加人物或物件,不改變場景,不出現文字或字幕。

Google Flow 生成的短影片:讓插畫中的孩子、地圖與機器人產生輕柔動態。

四、提示詞怎麼寫,影片會更穩定?

照片或插畫轉影片時,靜態畫面已經提供了人物和場景資訊,提示詞可以把重點放在「誰做什麼、動作多大、鏡頭怎麼移動」。動作越精簡、鏡頭越穩定,越容易維持原畫面。若一次要求多人做不同動作、快速移動鏡頭或大幅改變場景,生成結果就比較容易偏離原圖。

  • 人物動作:低頭看地圖、輕按按鍵、微笑互動。
  • 物件動作:機器人慢慢沿著地圖移動。
  • 鏡頭動作:平穩推近,避免快速旋轉或切換角度。
  • 一致性要求:保留人物位置、服裝、場景與插畫筆觸。
  • 排除事項:不增加人物、不改變場景、不生成字幕或文字。

五、從一張照片延伸出新的活動紀錄

這次實作把真實活動、繪本插畫與短影片連成一個簡單的創作流程。插畫可以用在成果報告、課程簡報或活動回顧;短影片則適合用於社群貼文或課程展示。對教育活動而言,AI 不只是把畫面變得更可愛,也提供了另一種說故事的方法:讓孩子的學習互動,以影像形式再次被看見。

使用提醒:若作品將公開發布,請確認照片中人物的肖像使用同意與活動素材授權。生成畫面可能改變臉部、文字或細節,重要資訊應以原始紀錄核對。Flow 的模型、功能、輸出長度和點數規則可能隨官方更新而調整,操作時請以平台當下顯示為準。

延伸參考:Google Flow 模型與支援功能說明|Google Flow 創作技巧

照片轉插畫與 Flow 影片生成實作|以兒童機器人地圖活動為例

2026年10月16日 星期五

泡泡牆為什麼能從另一台電腦開啟?IP、連接埠與區域網路入門

網際網路入門・Django 泡泡牆實作

泡泡牆為什麼能從另一台電腦開啟?
認識 IP、連接埠與區域網路

老師在一台電腦啟動 Django 泡泡牆,學生卻能用自己的手機或筆電投稿,投影幕也能即時出現新泡泡。這個過程不只是「打開一個網址」,而是裝置在區域網路中找到伺服器,並透過指定連接埠交換資料。

一、先看懂連線中的角色

角色在泡泡牆中的例子工作
用戶端學生手機、筆電、投影瀏覽器提出網頁請求、顯示畫面、送出資料
伺服器執行 Django 的教師電腦接收請求、驗證資料、查詢或更新資料庫、回應結果
區域網路教室 Wi‑Fi 或有線網路讓同一網路中的裝置彼此傳送資料

本例中,學生瀏覽器是用戶端,教師電腦是伺服器。Django 接到投稿後會儲存泡泡,再通知投影頁更新。

二、IP 位址:在網路中找到那台電腦

IP 位址像是裝置在網路上的地址。教室區域網路常見的私有 IPv4 位址會以 192.168.、10. 或 172.16 至 172.31 開頭。例如 Django 主機可能是 192.168.1.100。

在 Windows 主機開啟 CMD,輸入以下指令查詢網路設定:

ipconfig

找到正在使用的 Wi‑Fi 或乙太網路介面,查看「IPv4 位址」。遠端裝置要連線時,使用的就是這個主機 IP。主機若重新連接網路,IP 可能改變,需要重新查詢。

注意:127.0.0.1 是「本機迴路」位址,代表目前正在使用的這台電腦。遠端學生輸入 127.0.0.1 會連回自己的手機或電腦,因此要輸入 Django 主機的區域網路 IP。

三、連接埠:找到電腦裡正確的服務

一台電腦可能同時提供許多網路服務。連接埠(Port)用來區分資料應該交給哪個服務。泡泡牆開發時使用連接埠 8000,所以網址會寫成:

http://192.168.1.100:8000/

其中 192.168.1.100 是主機 IP,8000 是 Django 服務使用的連接埠,最後的斜線代表網站路徑。完整網址可拆成:

網址部分例子說明
通訊協定http://瀏覽器與網站使用 HTTP 溝通
主機位置192.168.1.100要連線的 Django 電腦
連接埠:8000該電腦上的 Django 服務
路徑/projector/要開啟的網站功能

四、為什麼要用 0.0.0.0 啟動?

Django 的開發伺服器預設只接受本機連線。若要讓同一區域網路的其他裝置連線,要讓它監聽主機的網路介面。進入 Django 專案資料夾後,可在 Windows CMD 執行:

set DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,192.168.1.100
set BUBBLE_TOKENS=class-a-2026
set BUBBLE_ADMIN_TOKEN=teacher-admin-2026
python manage.py runserver 0.0.0.0:8000
  • 0.0.0.0:8000:Django 在所有可用網路介面的 8000 埠接收連線。
  • DJANGO_ALLOWED_HOSTS:列出 Django 接受的主機名稱或 IP,請換成自己的主機位址。
  • BUBBLE_TOKENS:學生投稿時使用的活動 token。
  • BUBBLE_ADMIN_TOKEN:管理頁清空泡泡時使用的管理 token,應與投稿 token 不同。

這些 set 指令設定的值只存在於目前 CMD 視窗。關閉視窗後,需重新設定並啟動伺服器。

五、遠端裝置如何連線

學生手機或筆電連上相同 Wi‑Fi 後,在瀏覽器輸入:

  • 投稿頁:http://192.168.1.100:8000/
  • 投影頁:http://192.168.1.100:8000/projector/
  • 管理頁:http://192.168.1.100:8000/manage/

網址中的 IP 請替換成 Django 主機的實際 IPv4 位址。學生在投稿頁填入活動 token;教師若要清空本場泡泡,則到管理頁輸入管理 token。

六、HTTP 請求如何抵達 Django?

當學生按下送出,瀏覽器會向伺服器傳送 HTTP POST 請求。網路設備依照目標 IP 將資料送到教師電腦,作業系統再依連接埠把請求交給 Django。Django 驗證 token 和欄位後回傳結果;投影頁則透過 SSE 接收新泡泡事件。

動作請求/通訊結果
打開投稿頁瀏覽器向 Django 要求 HTML、CSS、JavaScript顯示投稿表單
送出泡泡POST /api/bubbles,附上 JSON 與 tokenDjango 驗證並寫入資料庫
投影端載入舊資料GET /api/state回傳目前泡泡清單
出現新泡泡/api/events 的 SSE 推播投影頁即時更新,不必重新整理

七、Windows 防火牆與網路限制

即使 IP 和網址正確,防火牆仍可能阻擋其他裝置連入。首次執行 Python 時,若 Windows 顯示網路存取提示,應在可信任的教室私人網路中允許通訊。必要時請管理者設定允許 TCP 8000 埠的私人網路入站規則。

部分校園、訪客 Wi‑Fi 會啟用用戶端隔離,阻止同一 Wi‑Fi 的裝置互相連線。若兩台設備都連上網路但仍無法互通,可請網管確認此設定,或改用允許裝置互通的教學網路。

八、連線檢查清單

  1. 主機上 Django 是否正在執行,且啟動指令包含 0.0.0.0:8000?
  2. 遠端裝置是否使用主機 IPv4,而不是 127.0.0.1?
  3. 網址是否包含 :8000 連接埠?
  4. DJANGO_ALLOWED_HOSTS 是否包含該 IP?
  5. Windows 防火牆是否允許私人網路連線?
  6. 兩台設備所連的 Wi‑Fi 是否允許用戶端彼此通訊?

九、區域網路與公開網際網路

使用主機私有 IP 的方式,只適合可互相連線的同一區域網路。校外的電腦通常不能直接使用 192.168.x.x 連到教室主機。若需要公開服務,應部署到正式伺服器、設定網域與 HTTPS,並妥善管理密鑰。

安全提醒:python manage.py runserver 是開發伺服器,適合本機開發或受控教室網路示範。不要直接把它公開到網際網路,也不要將活動 token 或管理 token 放在公開文章或程式碼中。

十、課堂延伸問題

  • 為什麼同一台電腦上的 127.0.0.1 和 192.168.1.100 指向不同連線情境?
  • 一台電腦開啟多個網站服務時,如何利用連接埠區分它們?
  • 若主機 IP 改變,學生書籤中的網址會發生什麼事?
  • 若教室網路不允許裝置彼此連線,有哪些可行的部署或網路安排?

從泡泡牆的遠端操作開始,學生可以逐步理解 IP 如何定位主機、連接埠如何定位服務、HTTP 如何交換資料,也能認識防火牆和區域網路規則對網站連線的影響。把這些概念與親手操作連在一起,網際網路就不再只是抽象名詞。

從遠端電腦操作 Django 泡泡牆:區域網路設定教學

DJANGO 泡泡牆・網路實作

從遠端電腦操作 Django 泡泡牆
區域網路設定教學

泡泡牆網站只需在一台電腦啟動 Django,其他電腦便可透過瀏覽器連線投稿或投影。本文以 Windows 主機為例,說明如何在教室的同一個 Wi‑Fi/區域網路內設定與操作。

先了解架構:一台電腦擔任 Django 主機,負責執行網站與保存泡泡資料;其他電腦、平板或手機是用戶端,透過瀏覽器連線。遠端裝置不需要另外安裝 Django。

一、適用情境與準備項目

  • 一台已解壓縮 Django 泡泡牆專案、能執行 Python 的主機。
  • 主機和遠端電腦連接同一個 Wi‑Fi 或區域網路。
  • 主機的區域網路 IP 位址,以及活動投稿 token。

以下假設專案已依照泡泡牆安裝教學完成套件安裝和資料庫遷移。若尚未安裝,請先在專案目錄執行 pip install -r requirements.txt 和 python manage.py migrate。

二、在 Django 主機查詢 IP 位址

步驟 1:開啟命令提示字元

在執行 Django 的 Windows 電腦按下「開始」,搜尋並開啟「命令提示字元」(CMD),輸入:

ipconfig

找到目前正在使用的 Wi‑Fi 或乙太網路介面,查看「IPv4 位址」。以下用 192.168.1.100 作為示例,實際操作時請換成您電腦顯示的 IP。

三、允許區域網路連線並啟動 Django

步驟 2:切換到專案資料夾

在 CMD 用 cd 進入包含 manage.py 的資料夾。例如:

cd C:\Users\學生\Desktop\bubblewall-django

請依照您實際解壓縮的位置修改路徑。

步驟 3:啟用虛擬環境(若有建立)

.venv\Scripts\activate

若命令提示字元前面出現 (.venv),表示虛擬環境已啟用。

步驟 4:設定允許的主機 IP、活動 token 並啟動伺服器

在同一個 CMD 視窗依序執行下列指令。請把 IP 換成主機的 IPv4 位址,也可將 token 換成您要提供給學生的活動通行碼。

set DJANGO_ALLOWED_HOSTS=127.0.0.1,localhost,192.168.1.100
set BUBBLE_TOKENS=class-a-2026
python manage.py runserver 0.0.0.0:8000

0.0.0.0:8000 表示 Django 開發伺服器接受主機其他網路介面的連線;若只執行 python manage.py runserver,通常只能由主機本身開啟。

請讓這個 CMD 視窗保持開啟,關閉視窗或停止伺服器後,遠端就無法連線。用 set 設定的環境變數只在目前 CMD 視窗有效。

四、從遠端電腦開啟泡泡牆

在遠端電腦確認已連上與主機相同的 Wi‑Fi,接著打開 Chrome、Edge 或其他瀏覽器,輸入主機 IP 和連接埠:

用途範例網址
學生投稿頁http://192.168.1.100:8000/
投影展示頁http://192.168.1.100:8000/projector/

請將範例網址中的 192.168.1.100 改成主機實際的 IPv4 位址。學生在投稿頁輸入活動 token、想法、隊伍名稱並選擇顏色,送出後投影頁會顯示新泡泡。

五、Windows 防火牆設定

若遠端電腦無法開啟網址,可能是 Windows 防火牆阻擋連線。首次啟動 Python 時若出現防火牆提示,允許 Python 在私人網路通訊。若沒有提示,可在 Windows「防火牆與網路保護」設定中,允許 Python 通過私人網路防火牆。

也可以由管理者建立入站規則,允許 TCP 連接埠 8000 在私人網路使用。請只開放教學場域需要的網路範圍。

六、常見問題排查

現象檢查方式
瀏覽器顯示無法連線確認 Django 主機的 CMD 視窗仍在執行;確認網址 IP 正確、連接埠是 8000,且兩台裝置在相同網路。
出現 DisallowedHost確認 DJANGO_ALLOWED_HOSTS 包含主機的 IPv4 位址;修改後重新啟動 Django。
網頁可開啟,但投稿失敗確認 token 與主機設定的 BUBBLE_TOKENS 相同;檢查文字、隊伍名稱與顏色欄位。
同一 Wi‑Fi 還是互相連不到查看防火牆是否允許私人網路;詢問網管 Wi‑Fi 是否啟用用戶端隔離。部分校園或訪客網路會禁止裝置彼此連線。
主機 IP 改變後網址失效重新執行 ipconfig 查詢 IPv4,並用新的 IP 開啟網址;也要更新允許的主機設定。

七、在外網或校外使用

上述方式適合教室內同一個區域網路使用。若要讓校外網路或不同網路的電腦連線,建議將網站部署到正式伺服器或雲端主機,設定網域名稱與 HTTPS,再妥善管理活動 token。

重要:python manage.py runserver 是 Django 開發用伺服器,不適合直接公開到網際網路。不要只為了遠端連線,就在路由器上將 8000 埠公開轉發到網路。

八、課堂操作流程建議

  1. 教師電腦啟動 Django,並將投影頁投放到教室螢幕。
  2. 教師確認主機 IP,將投稿網址和活動 token 提供給學生。
  3. 學生使用自己的電腦或手機開啟投稿頁,送出文字與顏色。
  4. 全班觀察投影頁即時出現泡泡,討論瀏覽器、HTTP 請求、伺服器、資料庫和 SSE 的分工。

透過這個流程,學生能把「網路上的資料如何傳送」與「多媒體內容如何呈現」連結起來,從實際操作理解網站與互動系統的基本架構。

用 Django 打造即時泡泡牆:從網際網路理論到多媒體實作

網際網路 × 多媒體應用 × Django 實作

用 Django 打造即時泡泡牆
從網際網路理論到多媒體實作

一個泡泡牆看起來像是把文字變成彩色圓泡泡,背後其實串起了瀏覽器、HTTP、JSON、伺服器、資料庫與即時推播。本文以 Django 製作一套可在教室展示的泡泡牆,帶大一新生透過完整作品認識網際網路與多媒體應用的基礎。






一、設計動機

大一新生初次接觸網際網路與多媒體應用時,常會分別學到網頁、伺服器、資料格式、資料庫和視覺設計等名詞,卻不容易看見它們如何合作。泡泡牆把這些概念放進一個能親手操作、能立即看見結果的情境:學生輸入一句想法,按下送出,教室投影畫面就多出一顆彩色泡泡。

因此,這個作品的動機是讓大一新生瞭解網際網路以及多媒體應用的理論基礎。學生可以沿著一則資料的旅程,觀察瀏覽器如何發出請求、Django 如何驗證與處理資料、資料如何存進資料庫,以及伺服器如何把新內容即時送到投影畫面。

二、教學目的

  • 認識用戶端與伺服器的分工,理解瀏覽器如何透過 HTTP 與網站溝通。
  • 分辨 GET 與 POST 的用途,讀懂 200、201、400、401 等狀態碼。
  • 理解 JSON 如何承載文字、顏色、隊伍等結構化資料。
  • 了解 Django URL 路由、View、Model 與 SQLite 資料庫的關係。
  • 體驗 Server-Sent Events(SSE)如何將新泡泡由伺服器推送到投影瀏覽器。
  • 連結多媒體的文字、色彩、動態效果與全螢幕展示,思考科技如何支持課堂互動。
學習成果:完成後,學生不只會看到泡泡牆的畫面,也能說明一顆泡泡如何從投稿表單出發,經過網路傳輸、伺服器處理與資料儲存,最後出現在投影幕上。

三、系統如何運作

學生瀏覽器HTTP POSTDjango API寫入SQLite
投影瀏覽器GET /api/state載入既有泡泡
Django SSE推送新事件投影瀏覽器新增泡泡

學生端將文字、顏色、隊伍名稱組成 JSON,並在 HTTP 標頭附上 Bearer token。伺服器檢查 token 和欄位後,建立資料記錄,回傳 201 Created。同時,SSE 連線會收到新泡泡事件,讓投影畫面無須重新整理就能更新。

介面方法與路徑用途成功/錯誤回應
載入泡泡GET /api/state取得既有泡泡200 OK + JSON
新增泡泡POST /api/bubbles驗證並儲存投稿201 Created;格式錯誤 400;token 無效 401
即時事件GET /api/events維持 SSE 連線、接收新泡泡text/event-stream

四、重要程式解析

1. Model:定義泡泡資料

Django Model 描述一筆泡泡要保存哪些欄位。執行資料庫遷移後,Django 會依此建立資料表。

from django.db import models

class Bubble(models.Model):
    text = models.CharField(max_length=160)
    color = models.CharField(max_length=7)
    team = models.CharField(max_length=40)
    created_at = models.DateTimeField(auto_now_add=True, db_index=True)

    class Meta:
        ordering = ['-created_at']

    def as_event(self):
        return {
            'id': self.pk,
            'text': self.text,
            'color': self.color,
            'team': self.team,
            'created_at': self.created_at.isoformat(),
        }

CharField 用來保存文字欄位,auto_now_add=True 會在建立記錄時自動記下時間。as_event() 將 Model 物件轉成適合 JSON 傳輸的 Python 字典。

2. URL 路由:把網址交給對應功能

from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='home'),
    path('projector/', views.projector, name='projector'),
    path('api/state', views.state, name='state'),
    path('api/bubbles', views.create_bubble, name='create_bubble'),
    path('api/events', views.events, name='events'),
]

路由可以想成網站的分機表:收到不同路徑,Django 就呼叫不同的 View。一般頁面負責回傳 HTML;以 /api/ 開頭的路徑主要交換資料。

3. View:驗證資料、存入資料庫並回傳狀態碼

以下節錄新增泡泡的關鍵邏輯。實際專案也會檢查 JSON 格式、欄位長度與色碼格式。

@require_POST
def create_bubble(request):
    token = request.headers.get('Authorization', '')
    token = token.removeprefix('Bearer ').strip()

    if not token or token not in settings.BUBBLE_TOKENS:
        return JsonResponse(
            {'error': 'unauthorized'}, status=401
        )

    data = json.loads(request.body)
    text = data.get('text', '').strip()
    color = data.get('color', '')
    team = data.get('team', '').strip()

    # 正式程式在此檢查欄位內容與格式
    bubble = Bubble.objects.create(
        text=text, color=color.upper(), team=team
    )
    return JsonResponse(bubble.as_event(), status=201)

Authorization: Bearer ... 是 HTTP 標頭中的通行憑證。驗證失敗回傳 401;資料欄位無效回傳 400;建立成功回傳 201。這些狀態碼是瀏覽器或其他用戶端判斷結果的依據。

安全提醒:教學原型使用環境變數設定 token;正式活動應將預設示範 token 換掉,避免把 token 寫在公開程式碼中。登入表單送出的 token 會由瀏覽器傳送到網站,因此公開部署須使用 HTTPS。

4. SSE:讓投影畫面收到新泡泡

SSE 是由伺服器單向推送資料到瀏覽器的機制。它適合「學生送出、投影端即時顯示」這種更新方向單純的情境。

def events(request):
    q = Queue()
    with _subscribers_lock:
        _subscribers.add(q)

    def stream():
        try:
            yield 'retry: 3000\n\n'
            while True:
                try:
                    payload = q.get(timeout=15)
                    yield f'event: bubble\ndata: {payload}\n\n'
                except Empty:
                    yield ': keep-alive\n\n'
        finally:
            with _subscribers_lock:
                _subscribers.discard(q)

    response = StreamingHttpResponse(
        stream(), content_type='text/event-stream'
    )
    response['Cache-Control'] = 'no-cache'
    response['X-Accel-Buffering'] = 'no'
    return response

投影瀏覽器先呼叫 /api/state 載入舊泡泡,再使用 EventSource 訂閱新事件:

fetch('/api/state')
  .then(response => response.json())
  .then(data => data.bubbles.forEach(addBubble));

const stream = new EventSource('/api/events');
stream.addEventListener('bubble', event => {
  const bubble = JSON.parse(event.data);
  addBubble(bubble);
});

收到資料後,JavaScript 建立泡泡元素、套用顏色,並將文字放入頁面。CSS 負責圓形外觀、陰影、浮動動畫和手機版排版,構成多媒體呈現的一部分。

五、執行步驟

步驟 1:準備環境

先安裝 Python 3.10 以上版本,將 bubblewall-django.zip 解壓縮到一個工作資料夾。開啟 Windows 命令提示字元(CMD),使用 cd 切換到含有 manage.py 的專案目錄。

cd C:\\Users\\學生\\Desktop\\bubblewall-django

步驟 2:建立虛擬環境並安裝 Django

python -m venv .venv
.venv\\Scripts\\activate
pip install -r requirements.txt

看到命令提示字元前面出現 (.venv),表示虛擬環境已啟用。

步驟 3:設定活動 token

原型預設 token 是 demo-token,直接啟動也能測試。若要指定活動用 token,可在同一個 CMD 視窗執行:

set BUBBLE_TOKENS=class-a-2026

此 token 會用來驗證學生的投稿請求。CMD 視窗關閉後,這項設定會消失;正式部署需改用適當的環境變數管理方式。

步驟 4:建立資料表並啟動伺服器

python manage.py migrate
python manage.py runserver

看到 Django 的開發伺服器已啟動後,在同一個 Wi-Fi 網路中,可先用本機網址測試 http://127.0.0.1:8000/。開發伺服器預設只供本機使用;若要讓教室其他設備連線,需設定主機位址、防火牆及允許的網域。

步驟 5:開啟投稿頁與投影頁

  • 學生投稿頁:http://127.0.0.1:8000/
  • 投影展示頁:http://127.0.0.1:8000/projector/

用兩個瀏覽器視窗開啟這兩個頁面。在投稿頁輸入想法、隊伍、選擇顏色和 token,送出後觀察投影頁是否即時出現泡泡。

步驟 6:用 curl 測試 API

開啟另一個 CMD 視窗,以 curl 直接送出 JSON。若將 token 設定為示範值,命令如下:

curl.exe -X POST http://127.0.0.1:8000/api/bubbles ^
  -H "Authorization: Bearer demo-token" ^
  -H "Content-Type: application/json" ^
  -d "{\"text\":\"讓想法一起發光\",\"color\":\"#61D7C5\",\"team\":\"海洋隊\"}"

成功時會回傳泡泡資料及 HTTP 201。把 token 改錯可觀察 401;把色碼改成不符合格式的內容,則可觀察 400。

六、課堂討論與延伸

  1. 如果投稿資料只有文字、沒有顏色,JSON 欄位與表單要如何調整?
  2. 為什麼投影端要先載入既有泡泡,再訂閱 SSE?如果順序相反,可能發生什麼情況?
  3. GET 和 POST 在這個系統中各自負責什麼?為什麼新增資料不使用 GET?
  4. 除了隊伍名稱與文字,還可以加入圖片、表情符號或音效嗎?前端需要做哪些變更?
  5. 目前資料庫記錄會保留。若每次活動要建立獨立泡泡牆,可加入活動代碼或活動模型來分流資料。

七、原型限制與下一步

目前版本使用 SQLite,適合課堂示範和小型單機活動。SSE 訂閱者清單保存在單一 Django 程序的記憶體中,因此多程序、多台主機部署時,事件不會自動跨程序同步;正式擴充時可改用 Redis Pub/Sub 或 Django Channels。正式上線也應使用 HTTPS、妥善管理 token,並搭配正式的 Django 部署伺服器。

這個作品的價值,在於把抽象網路概念變成可觀察的資料流程。學生每送出一顆泡泡,就能看見用戶端、伺服器、資料庫與多媒體畫面如何協同工作,逐步建立對網際網路與互動應用的整體理解。

2026年10月14日 星期三

從資料夾到網站:HTML、CSS、JavaScript 與 Python 網頁伺服器

網站製作入門・從零開始

從資料夾到網站:HTML、CSS、JavaScript 與 Python 網頁伺服器

想做一個自己的網頁,不需要一開始就架設正式主機。先建立網站資料夾、製作首頁,再用 Python 內建的簡易 HTTP Server,就能在自己的電腦上預覽網站。

一、先認識網站資料夾與目錄

網站通常由多個檔案組成,例如網頁、樣式表、圖片和程式。把它們放在同一個專案資料夾,再依用途建立子資料夾,可以讓檔案容易管理。資料夾在命令列中也常稱為「目錄」。

website_demo/
├── index.html       ← 網站首頁
├── css/
│   └── style.css    ← 網頁樣式
├── js/
│   └── main.js      ← 互動程式
└── images/
    └── banner.jpg   ← 圖片

範例先把所有內容放在 website_demo,只建立 index.html 也能開始。日後內容變多,再將 CSS、JavaScript 和圖片分別整理到子目錄。

二、首頁為什麼常叫 index.html?

index.html 是常見的預設首頁檔名。當伺服器收到某個目錄的請求,例如瀏覽器開啟網站根網址時,通常會優先尋找該目錄中的 index.html,因此不必在網址後面輸入檔名。

「首頁」是網站入口頁面的角色;index.html 則是大家常用來命名這個檔案的名稱。若改成 hello.html,瀏覽器就要用完整路徑開啟該檔案。

三、HTML、CSS、JavaScript 各自負責什麼?

技術主要工作可以想成
HTML安排標題、段落、圖片、連結等內容結構房子的結構與房間配置
CSS設定顏色、字型、間距、版面與響應式呈現室內裝潢與外觀
JavaScript處理按鈕、表單、選單等互動行為讓設備動起來的控制系統

三者可以寫在同一個 HTML 檔案,也可以拆成不同檔案。初學時可以先在 HTML 裡寫少量 CSS;網站長大後,再分檔管理,閱讀與維護會更清楚。

四、操作步驟:建立並編輯首頁

  1. 在電腦中建立一個資料夾,例如 website_demo。
  2. 開啟 Notepad++,選擇「檔案」→「新增」。
  3. 輸入以下 HTML 範例。
  4. 選擇「檔案」→「另存新檔」,存入 website_demo,檔名輸入 index.html。確認副檔名是 .html,不要存成 index.html.txt。
<!doctype html>
<html lang="zh-Hant">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>我的第一個網站</title>
  <style>
    body {
      font-family: "Microsoft JhengHei", sans-serif;
      max-width: 720px;
      margin: 4rem auto;
      padding: 0 1.25rem;
      color: #24352f;
      background: #f4f8f5;
    }
    h1 { color: #176b52; }
    button { padding: .6rem 1rem; cursor: pointer; }
  </style>
</head>
<body>
  <h1>你好,網站!</h1>
  <p>這是我的第一個 HTML 網頁。</p>
  <button id="helloButton">按我看看</button>
  <p id="message"></p>

  <script>
    document.getElementById("helloButton").addEventListener("click", function () {
      document.getElementById("message").textContent = "JavaScript 讓網頁可以互動!";
    });
  </script>
</body>
</html>

這個範例以 HTML 建立標題、段落與按鈕;<style> 裡的 CSS 負責外觀;<script> 裡的 JavaScript 則在按鈕被點擊時改變文字。

五、在命令提示字元切換目錄

按下 Windows 鍵,輸入 cmd 並開啟「命令提示字元」。使用 cd(change directory)切換目前所在目錄。

cd /d "C:\Users\你的帳號\Documents\website_demo"

請把範例路徑換成自己資料夾的實際位置。Windows 下若目錄在不同磁碟,使用 /d 也會一併切換磁碟。路徑中有空格時,請保留雙引號。

切換後可輸入 dir 查看檔案清單,確認目前目錄內有 index.html。

六、啟動 Python 網頁伺服器:解讀指令

在網站目錄中執行:

python -m http.server 8000
指令部分意思
python啟動 Python 解譯器。
-m要求 Python 以模組方式執行指定模組。
http.serverPython 內建的簡易 HTTP 伺服器模組,用來提供目前目錄中的檔案。
8000伺服器使用的埠號;瀏覽器會透過這個埠連線。

成功啟動時,命令提示字元會顯示類似 Serving HTTP on ... port 8000 的訊息。伺服器啟動期間,這個視窗要保持開啟。

找不到 python 指令時:Windows 有些環境需要使用 py -m http.server 8000。若 8000 埠已被其他程式使用,可以改用其他埠號,例如 python -m http.server 8080,並在瀏覽器改開 8080。

七、用瀏覽器測試網站

  1. 保持命令提示字元中的伺服器執行。
  2. 開啟 Chrome、Edge 或其他瀏覽器。
  3. 在網址列輸入 http://localhost:8000,或 http://127.0.0.1:8000,按 Enter。
  4. 確認畫面出現「你好,網站!」,再按一下按鈕,檢查 JavaScript 互動是否正常。
  5. 修改並儲存 index.html 後,回到瀏覽器重新整理,即可檢視更新結果。

網址中的 localhost 代表目前這台電腦;8000 是伺服器埠號。Python 的簡易伺服器適合本機練習與預覽,並不是完整的正式網站部署方案。

八、停止伺服器與常見檢查

  • 要停止伺服器時,切回命令提示字元視窗,按 Ctrl + C。
  • 若瀏覽器顯示目錄清單或找不到首頁,確認檔名是 index.html,而且命令列目前所在目錄正確。
  • 若瀏覽器無法連線,確認伺服器仍在執行、網址埠號與指令一致,並檢查是否有其他程式占用該埠。
  • 若中文文字亂碼,確認 HTML 的 <head> 中有 <meta charset="utf-8">,並以 UTF-8 編碼儲存檔案。

完成以上步驟後,你已經建立一個由 HTML、CSS 和 JavaScript 組成,並透過 Python 本機伺服器開啟的入門網站。接著可以加入圖片、導覽列、多個網頁,再逐步整理 CSS 與 JavaScript 檔案。

2026年10月12日 星期一

Python Closure 閉包:5 個簡單又經典的範例

PYTHON 入門|函式如何記住資料

Python Closure 閉包:5 個簡單又經典的範例

Closure(閉包)讓內層函式在外層函式結束後,仍能記住並使用外層函式建立的資料。它常用來保存狀態、建立可設定的函式,也能幫助我們理解裝飾器的運作方式。

簡單記法:閉包通常包含外層函式、內層函式,以及內層函式所記住的外層變數。

一、閉包的運作流程

外層函式建立資料和內層函式,接著把內層函式回傳給外部。外層函式結束後,回傳的函式仍保有它需要的外層資料。

呼叫外層函式make_greeter("小明") 建立外層資料name = "小明" 回傳內層函式greet 記住 name 之後呼叫 greet() 外層函式已結束,閉包仍保留所需資料
閉包把內層函式與它需要的外層變數關聯起來,因此外層函式結束後仍可使用。

二、範例 1:閉包記住名字

def make_greeter(name):
    def greet():
        print(f"你好,{name}!")
    return greet


hello = make_greeter("小明")
hello()

執行結果

你好,小明!

程式解說

  1. 呼叫 make_greeter("小明") 時,外層函式的參數 name 取得「小明」。
  2. 內層函式 greet() 使用了外層的 name。
  3. return greet 回傳函式本身,注意這裡沒有加括號;若寫成 return greet(),就會立刻執行它,而不是回傳函式。
  4. 回傳的函式存入 hello。之後呼叫 hello(),它仍能讀取閉包保留的名字。

三、範例 2:建立不同的問候函式

def make_greeter(name):
    def greet():
        print(f"你好,{name}!")
    return greet


greet_ming = make_greeter("小明")
greet_hua = make_greeter("小華")

greet_ming()
greet_hua()

執行結果

你好,小明!
你好,小華!

程式解說

  • 第一次呼叫 make_greeter,建立一個記住「小明」的閉包。
  • 第二次呼叫時,建立另一個記住「小華」的閉包。
  • greet_ming 和 greet_hua 是不同的函式物件,各自保存自己的 name。
  • 因此呼叫其中一個,不會改變另一個閉包記住的資料。

四、範例 3:建立會累加的計數器

def make_counter():
    count = 0

    def counter():
        nonlocal count
        count += 1
        return count

    return counter


counter = make_counter()

print(counter())
print(counter())
print(counter())

執行結果

1
2
3

程式解說

  • make_counter() 把 count 初始化為 0,並定義內層函式 counter()。
  • 每次呼叫內層函式,count += 1 就把原本記住的值加一,再回傳新值。
  • nonlocal count 告訴 Python:這裡要修改最近一層外部函式中的 count,而不是建立新的區域變數。
  • 如果移除 nonlocal,因為程式在函式內對 count 賦值,Python 會把它視為區域變數;在讀取它之前尚未設定值,會發生 UnboundLocalError。

五、範例 4:各自保存起始值的計數器

def make_counter(start):
    count = start

    def counter():
        nonlocal count
        count += 1
        return count

    return counter


counter_a = make_counter(0)
counter_b = make_counter(10)

print(counter_a())
print(counter_a())
print(counter_b())

執行結果

1
2
11

程式解說

  • 這個版本讓建立計數器時指定起始值,count = start 把起始值放進閉包。
  • counter_a 記住自己的 count = 0;counter_b 記住自己的 count = 10。
  • 呼叫 counter_a() 兩次,得到 1、2;呼叫 counter_b() 一次,則從 10 加到 11。
  • 每次執行 make_counter() 都會建立一組新的外層環境,所以兩個計數器的狀態互不干擾。

六、範例 5:建立簡單的乘法器

def make_multiplier(number):
    def multiply(value):
        return number * value
    return multiply


double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))
print(triple(5))

執行結果

10
15

程式解說

  • make_multiplier(2) 回傳一個記住 number = 2 的函式,存入 double。
  • make_multiplier(3) 回傳另一個記住 number = 3 的函式,存入 triple。
  • 呼叫 double(5) 時,內層函式把 2 與 5 相乘;呼叫 triple(5) 時,則把 3 與 5 相乘。
  • 這種寫法可以建立「已預先設定部分資料」的函式。

七、閉包重點整理

  • 內層函式使用了外層函式的變數,並且被保留下來,就形成閉包。
  • 閉包可以在外層函式結束後,繼續使用外層變數。
  • 每次呼叫外層函式都可以建立一個有獨立狀態的新閉包。
  • 要在內層函式修改外層函式的變數,使用 nonlocal。
  • 閉包常用於保存狀態、建立函式工廠、設定回呼行為,以及實作裝飾器。

學習閉包時,可以先追蹤三件事:外層函式建立了什麼資料、內層函式使用了哪些資料,以及外層函式結束後是哪個回傳函式仍保有這些資料。掌握這個關係,就能看懂許多 Python 程式中的閉包寫法。

Python Decorator 裝飾器:5 個簡單又經典的範例

PYTHON 入門|函式的額外能力

Python Decorator 裝飾器:5 個簡單又經典的範例

裝飾器(Decorator)可以在不改動原函式主要程式碼的情況下,替它加上「執行前處理」或「執行後處理」。常見用途包括顯示訊息、記錄執行時間、檢查權限與保留函式資訊。

先記住一句話:裝飾器接收一個函式,回傳一個包裝函式;呼叫原函式時,就會先經過這層包裝。

一、裝飾器的運作流程

在函式定義上方寫 @my_decorator,Python 會把函式交給裝飾器處理。下面流程圖以「前後訊息」為例:

呼叫 say_hello()使用者呼叫函式 wrapper()裝飾器包裝層 func()原函式內容 回傳結果交回呼叫端 執行前處理執行後處理也可放在 func() 之後
呼叫流程:使用者呼叫包裝後的函式,wrapper 再呼叫原函式,最後把結果交回。

最常見的結構如下。*args 與 **kwargs 讓包裝函式可以接收不同數量的位置參數與關鍵字參數;return 則把原函式的結果傳回去。

def decorator(func):
    def wrapper(*args, **kwargs):
        # 額外處理:執行原函式之前
        result = func(*args, **kwargs)
        # 額外處理:執行原函式之後
        return result
    return wrapper

二、範例 1:在函式前後顯示訊息

def my_decorator(func):
    def wrapper():
        print("開始執行")
        func()
        print("執行結束")
    return wrapper


@my_decorator
def say_hello():
    print("Hello, Python!")


say_hello()

執行結果

開始執行
Hello, Python!
執行結束

程式解說

  1. my_decorator(func) 收到原本的 say_hello 函式。
  2. 裝飾器建立 wrapper,在呼叫原函式前印出「開始執行」,呼叫後再印出「執行結束」。
  3. return wrapper 把包裝函式交回去,因此之後呼叫 say_hello(),實際上執行的是 wrapper。
  4. @my_decorator 是簡寫,等同於 say_hello = my_decorator(say_hello)。

三、範例 2:讓裝飾器支援函式參數

def show_call(func):
    def wrapper(*args, **kwargs):
        print(f"正在呼叫:{func.__name__}")
        return func(*args, **kwargs)
    return wrapper


@show_call
def greet(name):
    print(f"你好,{name}!")


greet("小明")

執行結果

正在呼叫:greet
你好,小明!

程式解說

  • *args 收集位置參數,例如這裡的 "小明";**kwargs 收集像 name="小明" 這種關鍵字參數。
  • func.__name__ 取得原函式名稱,方便記錄目前呼叫哪個函式。
  • func(*args, **kwargs) 再把收到的參數完整傳給原函式。
  • return 會保留原函式的回傳值;若少了它,呼叫端通常會收到 None。

四、範例 3:計算函式執行時間

import time


def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()

        print(f"{func.__name__} 執行時間:{end - start:.4f} 秒")
        return result
    return wrapper


@timer
def count_numbers():
    total = sum(range(1_000_000))
    return total


print(count_numbers())

執行結果(時間會因電腦而不同)

count_numbers 執行時間:0.0284 秒
499999500000

程式解說

  • start 記錄函式執行前的時間;原函式完成後用 end 記錄時間。
  • end - start 是執行所花的秒數,:.4f 表示顯示到小數點後四位。
  • 原函式回傳總和,包裝函式將結果存在 result,印出時間後再回傳,因此外層的 print 仍能印出總和。
  • 範例中的時間只是示意;實際數值會受電腦與當時負載影響。

五、範例 4:執行函式前檢查登入狀態

def login_required(func):
    def wrapper(is_logged_in):
        if not is_logged_in:
            print("請先登入")
            return

        return func(is_logged_in)
    return wrapper


@login_required
def view_profile(is_logged_in):
    print("顯示個人資料")


view_profile(False)
view_profile(True)

執行結果

請先登入
顯示個人資料

程式解說

  • wrapper 先檢查 is_logged_in 的值。
  • 若值是 False,印出提示後用 return 結束,不再執行原函式。
  • 若值是 True,程式呼叫 func(is_logged_in),於是顯示個人資料。
  • 這是權限檢查的教學示範;實際網站應由登入系統與伺服器端驗證使用者身分。

六、範例 5:用 wraps 保留原函式資訊

from functools import wraps


def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("執行前")
        return func(*args, **kwargs)
    return wrapper


@my_decorator
def say_hello():
    """向使用者打招呼"""
    print("Hello!")


print(say_hello.__name__)
print(say_hello.__doc__)
say_hello()

執行結果

say_hello
向使用者打招呼
執行前
Hello!

程式解說

  • 經過裝飾後,函式名稱可能被包裝層的名稱 wrapper 取代,文件字串也可能不再指向原函式。
  • from functools import wraps 匯入標準函式庫提供的工具。
  • 把 @wraps(func) 放在 wrapper 定義上方,可以將原函式的名稱、說明文件等資訊保留下來。
  • 這是撰寫一般裝飾器時很好的習慣,尤其當函式會被文件工具、除錯工具或其他程式檢查時。

七、快速整理

  • 核心概念:裝飾器接收函式,並回傳包裝後的函式。
  • 支援參數:多數通用裝飾器使用 *args、**kwargs。
  • 保留回傳值:用 return func(...) 把原函式的結果傳回。
  • 保留函式資訊:使用 functools.wraps。
  • 常見用途:日誌、計時、權限檢查、快取與輸入驗證。

熟悉這幾個例子後,可以試著把「執行前後顯示訊息」改成記錄每次函式呼叫的參數,或替計時裝飾器加上不同函式的測試。從 wrapper 的進入、原函式的執行,到結果回傳,便能掌握裝飾器的基本運作方式。