Study

User
詳細解釋一下API中client和server所扮演的角色和功能

ChatGPT
當談到API時,通常涉及到兩個主要組成部分:客戶端(Client)和伺服器(Server)。它們各自扮演不同的角色並執行特定的功能。

客戶端(Client):
發出請求: 客戶端是對API的使用者。它可以是應用程式、網站、行動應用、甚至其他伺服器。客戶端通常發送請求給伺服器,要求資源或執行特定操作。
處理回應: 客戶端負責處理從伺服器返回的響應。這可能包括解析資料、顯示在使用者介面上或者進行進一步的處理。
傳送請求: 發送不同類型的請求,比如GET(獲取資料)、POST(新增資料)、PUT(更新資料)、DELETE(刪除資料)等,根據所需的操作。
伺服器(Server):
接收請求: 伺服器是API的提供者。它接收來自客戶端的請求,並根據這些請求針對資料庫、其他系統或資源進行操作。
處理請求: 根據請求的類型和內容,伺服器會執行相應的操作,比如檢索資料、更新資料庫、發送通知等。
回覆響應: 伺服器會向客戶端發送回應,這可能是請求的結果、錯誤訊息或其他相關的資訊。
功能關聯:
客戶端和伺服器的關係是相互的,它們透過互相的請求和響應來實現特定的功能。客戶端需要發送明確的請求以獲取所需的資訊或執行操作,而伺服器則回應這些請求並提供相應的資訊或服務。

例如,假設您使用一個社交媒體平台的API,您的應用程式(客戶端)可以發送請求以獲取特定用戶的資料。伺服器接收這個請求,從資料庫中檢索用戶資料,然後回傳這些資料作為響應,最後由您的應用程式處理和顯示這些資訊給使用者。

總的來說,客戶端和伺服器在API中扮演不同但互相依賴的角色,共同協作以實現資訊交換和服務提供。

User
兩者的程式碼填寫有哪裡不同
ChatGPT
在API客戶端和伺服器端的程式碼中,主要的不同點在於它們執行的功能和相應的操作。下面會列舉一些主要的差異:

客戶端程式碼:
主要功能:
發送請求: 客戶端主要負責向伺服器發送請求。
處理回應: 接收並處理從伺服器返回的響應。
操作應用程序界面(UI): 將從伺服器獲取的資訊顯示在應用程式的介面上,例如網頁、應用程式界面等。
程式碼區別:
使用HTTP請求庫(如Python中的requests)來建立和發送請求。
設置所需的標頭(例如授權憑證、內容類型)以及其他參數。
接收和解析伺服器的響應,通常以JSON或其他格式。
伺服器端程式碼:
主要功能:
接收請求: 接收來自客戶端的請求。
處理請求: 根據請求的類型和內容執行相應的操作,例如從資料庫中檢索、更新資料等。
回覆響應: 向客戶端發送回應,包括所需的資訊或操作的結果。
程式碼區別:
監聽和處理來自客戶端的請求。
實現API端點(endpoint)以接收特定類型的請求(GET、POST、PUT、DELETE等)。
執行相應的邏輯操作並生成適當的響應。
基本上,客戶端程式碼主要集中在向伺服器發送請求和處理回應,而伺服器端則專注於接收、處理請求並提供相應的回應。兩者的重點和功能不同,但它們需要彼此配合以實現完整的API功能。

Prerequisite

  • Port forwarding on 192.168.0.1 : 192.168.0.2:5000 - 140.118.121.93:1112
  • API server
    • Python 3
    • MySQL
    • VScode
    • Receive data based on Spec.
  • API client
    • POST data based on Spec.
  • Spec

Python

sudo apt-get install python3 python3-pip pip3 install Flask pip install pymysql==0.9.3 sudo apt-get install python3-pymysql pip install flask-cors pip3 install python3 api.py
Traceback (most recent call last): File "/home/jimmy/.local/lib/python3.6/site-packages/pymysql/connections.py", line 614, in connect (self.host, self.port), self.connect_timeout, **kwargs File "/usr/lib/python3.6/socket.py", line 724, in create_connection raise err File "/usr/lib/python3.6/socket.py", line 713, in create_connection sock.connect(sa) ConnectionRefusedError: [Errno 111] Connection refused During handling of the above exception, another exception occurred: Traceback (most recent call last): File "api.py", line 15, in <module> connection = pymysql.connect(**db_config) File "/home/jimmy/.local/lib/python3.6/site-packages/pymysql/connections.py", line 353, in __init__ self.connect() File "/home/jimmy/.local/lib/python3.6/site-packages/pymysql/connections.py", line 664, in connect raise exc pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '127.0.0.1' ([Errno 111] Connection refused)")
  • Need to install mysql-server, not just pymysql.

MySQL

sudo apt update && sudo apt install mysql-server sudo mysql ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password by 'rtlab111'; sudo mysql -u root -p CREATE DATABASE db_1; USE db_1; CREATE TABLE IF NOT EXISTS table_1 ( id INT AUTO_INCREMENT PRIMARY KEY, ... ); /* 1. INT (整數) 2. CHAR (1~255字元字串) 3. VARCHAR (不超過255字元不定長度字串) 4. TEXT (不定長度字串最多65535字元) */ SELECT * FROM table_1; INSERT INTO your_table_name (column1, column2, column3) VALUES ('value1', 'value2', 'value3'); show <databases,tables>; drop databases, table; describe table_2; truncate table table_2; alter table table_account change time_register time_register timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP; exit

Testing methods

Telnet

telnet 127.0.0.1 5000
  • Connected if there is a service using the port, or the port is not connected.
  • 127.0.0.X is for PC to see, so you cant use this as port forwarding in api code.
    • So if you run API server on 127.0.0.1, you cant connect to port 5000 by telnet.
    • Use 0.0.0.0 or 192.168.0.2.
    • If you use API, the running adress will not be the local adress.

Curl

curl -X POST -H "Content-Type: application/json" -d '{"dt_infotime": "2023-11-17 12:00:00", "s_attemp": 25, "s_cpu": 80, "s_disk": "", "s_diskin": 100, "s_diskout": 50, "s_error": "No errors", "s_load": 3.5, "s_loss": 0.5, "s_memory": 70, "s_nf": 10, "s_size": 1024, "s_system": "Linux", "s_traffic": 1000, "s_transfer": 500}' http://192.168.0.2:5000/nfs/resource // Back message { "message": "JSON received successfully" }

Result
Screenshot from 2023-11-17 14-18-14

Postman

  • Download and you can test in local domain.
http://192.168.0.2:5000/ http://140.118.121.93:1112/

From client

Goal

  • System Architecture、System Configuration

API_server.drawio

  • Parameter in Header
    • 在註冊階段標頭中加入user權限等級設定
    • 或是可以改成加入權限請求,在server決定要不要給予
  • Sample for request and response
    • 如下各項Test result
  • Error detection
    • 可以在小數運算中做,用平均和瞬間值去判斷
  • Error Notification
    • API一定要有requesth才有response
    • 開websocket
  • The Level of User Access right
    • 前端可以加在註冊api client的標頭,目前以數字設定,不同高低的存取權到哪裡還需要詳細spec
    • 標頭有加Authorization: 3,代表給予等級3的權限等級,如果沒寫數字會報錯。
    • 標頭沒加這一項會預設為等級0。
    • 之後可以改成寫是否要加權限,由server判斷要不要給藥給多少。
  • Google ID login
    • 只做在login、沒做在signup
  • Multi-pages for user data request
  • Small Data computation (small data),Big Data Computation (Big data)
    • 只做amf的cpu使用率
    • 可以增加邏輯: 持續高於90%就直接寄email

API server - Log client

Flow1.drawio

  • Spec(API-8)
  • Client POST the data 10 seconds for once.
    unnamed

DB

CREATE TABLE IF NOT EXISTS table_2 ( id INT AUTO_INCREMENT PRIMARY KEY, dt_infotime VARCHAR(255), s_attemp VARCHAR(255), s_cpu VARCHAR(255), s_disk VARCHAR(255), s_diskin VARCHAR(255), s_diskout VARCHAR(255), s_error VARCHAR(255), s_load VARCHAR(255), s_loss VARCHAR(255), s_memory VARCHAR(255), s_nf VARCHAR(255), s_size VARCHAR(255), s_system VARCHAR(255), s_traffic VARCHAR(255), s_transfer VARCHAR(255) );

image

Code

from flask import Flask, jsonify, request import pymysql from flask_cors import cross_origin app = Flask(__name__) # MySQL 連接資訊 db_config = { 'host': '127.0.0.1', 'user': 'root', 'password': 'rtlab111', 'database': 'db_1', #'port': 3306 } # 處理 404 錯誤 @app.errorhandler(404) def not_found_error(error): return jsonify({'error': 'Not Found'}), 404 # 創建 MySQL 連接 connection = pymysql.connect(**db_config) # 創建游標 cursor = connection.cursor() # 新的端點,用於接收 POST 請求並添加新的 log @app.route('/nfs/resource', methods=['POST']) @cross_origin() # 使用 @cross_origin 裝飾器啟用 CORS def add_log(): try: json_data = request.get_json() # 檢查是否包含所需的標題 required_titles = ['dt_infotime', 's_attemp', 's_cpu', 's_disk', 's_diskin', 's_diskout', 's_error', 's_load', 's_loss', 's_memory', 's_nf', 's_size', 's_system', 's_traffic', 's_transfer'] # 準備用於插入的標題和值 insert_titles = [] insert_values = [] for title in required_titles: if title in json_data: insert_titles.append(title) insert_values.append(json_data[title]) elif title not in json_data: return jsonify({'error': f'Missing title: {title}'}), 404 # 檢查是否有任何可插入的標題 #if not insert_titles: # return jsonify({'error': 'No valid data to insert'}), 400 # 插入資料到 MySQL 表格 insert_query = f"INSERT INTO table_2 ({', '.join(insert_titles)}) VALUES ({', '.join(['%s']*len(insert_titles))})" cursor.execute(insert_query, insert_values) connection.commit() # 加入 print 語句以記錄成功接收並插入資料 print(f"Received JSON: {json_data}") return jsonify({'message': 'JSON received successfully'}), 200 except Exception as e: return jsonify({'error': str(e)}), 404 # 新的端點,用於接收 GET 請求 @app.route('/nfs/resource', methods=['GET']) @cross_origin() # 使用 @cross_origin 裝飾器啟用 CORS def get_s_nf(): try: # 從 GET 參數中獲取 s_nf 的值 s_nf_value = request.args.get('s_nf') # 檢查是否有 s_nf 參數 if s_nf_value is None: return jsonify({'error': 'Missing s_nf parameter'}), 404 # 執行 SELECT 查詢,找到最後一筆 s_nf=amf 的資料按照 id 降序排序 select_query = "SELECT * FROM table_2 WHERE s_nf = %s ORDER BY id DESC LIMIT 1" cursor.execute(select_query, (s_nf_value,)) # 獲取查詢結果 result = cursor.fetchone() # 將結果轉換為 JSON 格式並返回 data = {"dt_infotime": result[0], "s_attemp": result[1], "s_cpu": result[2], "s_disk": result[3], "s_diskin": result[4], "s_diskout": result[5] , "s_error": result[6], "s_load": result[7], "s_loss": result[8], "s_memory": result[9] #, "s_nf": result[10] , "s_size": result[11], "s_system": result[12], "s_traffic": result[13], "s_transfer": result[14]} # 根據實際情況修改列名 return jsonify({'data': data}), 200 except Exception as e: return jsonify({'error': str(e)}), 404

Test

(400->404)

Postman

POST 沒有少標題、包含空值
http://192.168.0.2:5000/nfs/resource http://140.118.121.93:1112/nfs/resource { "dt_infotime": "2023-11-17 12:00:00", "s_attemp": 25, "s_cpu": 80, "s_disk": "", "s_diskin": 100, "s_diskout": 50, "s_error": "No errors", "s_load": 3.5, "s_loss": 0.5, "s_memory": 70, "s_nf": 10, "s_size": 1024, "s_system": "Linux", "s_traffic": 1000, "s_transfer": 500 }

image

POST 少標題
http://192.168.0.2:5000/nfs/resource http://140.118.121.93:1112/nfs/resource { "dt_infotime": "2023-11-17 12:00:00", "s_attemp": 25, "s_cpu": 80, "s_disk": "", "s_diskin": 100, "s_diskout": 50, "s_error": "No errors", "s_load": 3.5, "s_loss": 0.5, "s_memory": 70, "s_nf": 10, "s_size": 1024, "s_system": "Linux", "s_traffic": 1000, }

image

GET amf

image

Server side

POST 沒有少標題、包含空值

image

POST 少標題

image

GET amf

image

DB side

POST 沒有少標題、包含空值

image

POST 少標題

image

GET amf (有多筆只會得到最新一筆)

image

API server - Web client

Sign up

Flow2.drawio

  • Spec(API-1)

DB

CREATE TABLE IF NOT EXISTS table_sign_up ( id INT AUTO_INCREMENT PRIMARY KEY, s_name VARCHAR(255), s_email VARCHAR(255), s_password VARCHAR(255), s_password_confirmation VARCHAR(255), s_phone VARCHAR(255) );

image

Code

# 新的端點,用於接收 POST 請求並添加新的 user @app.route('/user/signup', methods=['POST']) @cross_origin() # 使用 @cross_origin 裝飾器啟用 CORS def sign_up(): try: json_data = request.get_json() # 檢查是否包含所需的標題 required_titles = ['s_name', 's_email', 's_password', 's_password_confirmation', 's_phone'] # 檢查是否有任何可插入的標題為空 if any(title not in json_data or not json_data[title].strip() for title in required_titles): return jsonify({'Error': 'have some parameter null'}), 403 # 取得 'access_level' 的值,如果不存在,預設值為 0 access_level = request.headers.get('Authorization', 0) # 準備用於插入的標題和值 insert_titles = required_titles + ['access_level'] insert_values = [json_data[title] for title in required_titles] + [access_level] # 檢查 's_password' 和 's_password_confirmation' 是否相同 if 's_password' in json_data and 's_password_confirmation' in json_data: if json_data['s_password'] != json_data['s_password_confirmation']: return jsonify({'Error': 'need identical password'}), 401 # 檢查 's_email' 是否已經存在於表格中 check_email_query = "SELECT COUNT(*) FROM table_sign_up WHERE s_email = %s" cursor.execute(check_email_query, (json_data['s_email'],)) email_exists = cursor.fetchone()[0] if email_exists: return jsonify({'Error': 'email already registers'}), 402 # 插入資料到 MySQL 表格 insert_query = f"INSERT INTO table_sign_up ({', '.join(insert_titles)}) VALUES ({', '.join(['%s']*len(insert_titles))})" cursor.execute(insert_query, insert_values) connection.commit() # 加入 print 語句以記錄成功接收並插入資料 print(f"Received JSON: {json_data}") return jsonify({'message': 'JSON received successfully'}), 200 except Exception as e: return jsonify({'error': str(e)}), 400

Test result

Postman

POST 正常格式
http://192.168.0.2:5000/user/signup http://140.118.121.93:1112/user/signup { "s_name": "jimmy", "s_email": "jimmy20152811@gmail.com", "s_password": "rtlab111", "s_password_confirmation": "rtlab111", "s_phone": "0903396991" }

image

POST email已註冊

image

輸入空值或只有輸入空白

image
image

密碼確認不一樣

image

Server side

POST 正常格式

image

POST email已註冊

image

輸入空值或只有輸入空白

image

密碼確認不一樣

image

DB side

POST 正常格式

image

POST email已註冊

image

輸入空值或只有輸入空白

image

密碼確認不一樣

image

Log in (Google Open ID)

Flow3.drawio (1)

  • Spec(API-2) -> 用GET不是會把帳密放在URL?不安全? -> 改用POST(放在body)

問題1
  1. 使用 Postman 或其他 API 測試工具發送 HTTP 請求,google登入部分該如何測試?
    • Google 登入部分需要在 Google 開發者控制台中進行以下設定:
    • 在 Google 開發者控制台中建立 OAuth 2.0 憑證,並設定正確的重定向 URI(通常是你應用程式的登入回調 URI)。
    • 在 Postman 中建立一個 OAuth 2.0 請求。請確保你的請求包含正確的客戶端 ID、客戶端密鑰、授權端點、令牌端點和範疇。使用正確的重定向 URI 發送請求。
    • 在 Postman 中執行這個請求,會彈出一個 Google 登入的視窗,請求用戶登入 Google 帳戶,並同意你的應用程式訪問其資訊。完成後,Postman 會收到包含訪問令牌的回應。
    • 使用訪問令牌向你的 API 發送請求。在這裡,你的 API 可以驗證令牌,並根據令牌中的信息識別用戶。
  2. 所以token是使用JWT嗎?這部分我需要詳細解釋是甚麼意思?
    • 關於 JWT(JSON Web Token),在 Google 登入的情境中,通常使用 OAuth 2.0 協議獲取的令牌是一個包含用戶訊息和授權信息的 JSON Web Token。JWT 是一種輕量且自包含的標準,通常用於在不同實體之間安全地傳輸信息。
    • 在 Google 登入中,你可能會獲得一個 ID 令牌,其中包含了用戶的識別信息。這個令牌可能是 JWT 格式的,但也可能是其他格式。當你收到令牌後,你的應用程式需要驗證該令牌的簽名,以確保它來自 Google 並未被竄改。在驗證成功後,你可以提取其中的信息,例如用戶的電子郵件、ID 等。
    • JWT 在身份驗證和授權中廣泛使用,因為它們提供了一種安全、簡單和標準的方式來表示和交換這些信息。
問題2 : JWT程式碼解說
  1. 這段程式碼是在使用 Flask-JWT 來實現 JSON Web Token (JWT) 驗證。下面是對程式碼的詳細解釋

User 類別:

  • User 類別被定義為表示應用程式中的使用者的類別。它有三個屬性:id、s_email 和 access_level,分別表示使用者的識別 ID、電子郵件地址和存取等級。

authenticate 函式:

  • authenticate 函式是用於一般帳戶登入的回調函式。當使用者提供電子郵件和密碼時,這個函式會查詢資料庫,並檢查是否有符合提供的電子郵件和密碼的使用者。如果找到符合的使用者,則會回傳一個 User 物件,該物件包含該使用者的資訊。

identity 函式:

  • identity 函式是用於生成 JWT 的回調函式。當驗證成功後,JWT 需要將一個唯一的識別符放入令牌中。這個函式根據傳入的 payload 中的識別符查詢資料庫,並返回相對應的 User 物件。
    在 Flask 中啟用 JWT:

這裡使用 JWT 類別來啟用 Flask-JWT。建立 JWT 實例時,需要提供三個參數:Flask 應用程式實例 (app)、用於驗證的回調函式 (authenticate) 和用於生成 JWT 的回調函式 (identity)。
總的來說,這些程式碼協助你在 Flask 應用程式中實現了使用 JWT 進行身分驗證的功能。當使用者提供有效的電子郵件和密碼進行登入時,你可以生成一個包含使用者資訊的 JWT,之後該令牌可以用來驗證和授權用戶的請求。

  1. 我看後面login的API部分沒有使用到identity??是我還不懂嗎

你的觀察是正確的,感謝提醒。在你的登入 API 的程式碼中,確實沒有直接使用到 identity 函式。讓我解釋一下可能的原因:

Flask-JWT 在驗證和生成 JWT 時,會自動調用 authenticate 函式來驗證使用者的身份。而當你在 Flask-JWT 中啟用了 JWT 實例時,它會自動處理驗證流程,包括呼叫 authenticate 函式,這是內建的行為。

在實際的應用中,identity 函式可能用於在每個請求中提取 JWT 中的使用者識別信息,並將其存儲在 Flask 的 current_identity 中,以便稍後在應用中取用。在你的程式碼中,如果你希望在 /user/login 的請求中使用 current_identity,你可以在該請求中使用 current_identity 取得當前使用者的資訊。

總結來說,Flask-JWT 在啟用後會自動處理身份驗證的部分,而 identity 函式則在需要取得當前使用者信息時提供了一個方法。你的登入 API 程式碼中如果沒有直接使用到 identity,可能是因為目前的邏輯還不需要它,但在更複雜的應用中可能會用到。

  1. 所以當前如果只是要從資料庫對照登入資訊是用不到的嗎

是的,當前的登入邏輯中,你主要是透過 authenticate 函式來從資料庫中對比使用者提供的帳號密碼是否正確。在這個過程中,identity 函式並沒有直接使用到。

identity 函式通常在你需要在每個請求中取得當前使用者的資訊時使用。例如,當你的應用需要在請求中驗證使用者的權限,或者在請求中記錄使用者的操作歷史時,就可能會使用到 identity 函式。

總的來說,identity 函式的使用取決於你的應用邏輯,如果目前的邏輯不需要直接取得當前使用者的資訊,那麼你可以在不使用它的情況下繼續進行。

  1. 那為何還需要使用JWT 我直接像sign up的檢查一樣使用資料庫指令對照就好了呀 還是說和token有關?或是和google登入有關??可是現在login API的部分已經去除掉google token了??為甚麼?

JWT(JSON Web Token)的主要目的是在網路應用中提供一種安全的方法來傳遞資訊,特別是用於驗證和資訊交換。在你的情境中,JWT 通常用於驗證使用者的身份,以及在使用者成功登入後發送安全的令牌。

下面是 JWT 的一些主要優勢:

安全的身份驗證: JWT 可以在使用者驗證成功後生成一個安全的令牌,該令牌包含了使用者的身份信息。這樣,當使用者在之後的請求中提供這個令牌,你的應用可以驗證該令牌,確保該使用者是有效的,而不需要再次查詢資料庫。

無需每次都查詢資料庫: 一旦使用者登入,你可以使用 JWT 來在之後的請求中驗證使用者身份,而不必每次都去查詢資料庫。這有助於提高效能,減少對資料庫的負擔。

避免明文傳輸密碼: JWT 通常不包含使用者的密碼等敏感資訊,避免了在請求中傳輸明文密碼的風險。

問題3 : app.config['SECRET_KEY']

在Flask中,app.config['SECRET_KEY'] 是一個用於簽署和驗證 session cookie 或其他資料的金鑰。它是一個安全性設定,用來確保特定的資料是由應用程式生成並未被竄改。

當你使用 Flask 中的某些擴展,如 Flask-WTF(用於處理表單)或 Flask-JWT(用於處理 JSON Web Tokens),它們可能會使用這個密鑰來確保應用的安全性。

密鑰的選擇應該是隨機且複雜的字符串,以增加安全性。你可以自行生成一個安全的密鑰,例如使用 Python 的 secrets 模組,而不是將其設為明文字符串。

這樣的設定通常用於確保某些 Flask 擴展或功能的安全性。在這個例子中,這個密鑰可能會被 Flask-JWT 用來簽署和驗證 JSON Web Tokens(JWT)。

問題4 : id使用方法

實際上我資料庫中的id只是資料筆數和順序而已 這樣是否需要更改程式碼
ChatGPT
id 欄位通常是資料表的主鍵,用來唯一識別每一筆資料。在你的程式碼中,id 欄位被用作 JWT 中的 identity,這是 Flask-JWT 擴展預期的用法。這樣的做法基本上是合理的,因為 JWT 需要一個唯一的識別符號來代表每個使用者。

如果你的 id 欄位不是唯一的,你可能需要考慮使用其他唯一的識別符號來替代,或者針對不同的使用者建立自訂的 identity 生成邏輯。

總的來說,如果你的 id 欄位確實是唯一的並且足夠用作使用者的唯一標識符,則沒有必要修改這部分的程式碼。如果有其他適合的唯一標識符,可以考慮修改 identity 函式以反映這一點。

問題5 : 能否透過google登入得到用戶手機號碼

Google 登入路由,導向 Google 登入頁面
@app.route('/user/google-login')
@cross_origin()
def google_login():
# 在這裡包含需要的權限,例如 user.phonenumbers.read
google = OAuth2Session(
GOOGLE_CLIENT_ID,
redirect_uri=GOOGLE_REDIRECT_URI,
scope=['openid', 'email', 'profile', 'https://www.googleapis.com/auth/user.phonenumbers.read']
)
authorization_url, state = google.authorization_url(GOOGLE_AUTHORIZATION_BASE_URL)
return redirect(authorization_url)
和這串有什麼差異
另外怎麼在控制台開啟權限

ChatGPT
這兩段程式碼的差異在於 scope 參數中包含的權限。在你提供的程式碼中,包含了 'https://www.googleapis.com/auth/user.phonenumbers.read',這表示你希望在 Google 登入時取得手機號碼的讀取權限。

至於如何在 Google 開發者控制台中開啟權限,你可以參考以下步驟:

前往 Google Cloud Console.
選擇你的專案(如果還沒有,請建立一個新的專案)。
在左側的導覽欄中,點選 "API 和服務" > "憑證"。
在 "憑證" 頁面中,找到你的 OAuth 2.0 用戶端 ID。
在 "授權的 JavaScript 來源" 和 "已授權的重新導向 URI" 中,確保你的網站的網址被正確設定。
另外,關於手機號碼的權限,Google 可能會有一些限制和審查,請確保你的應用程式遵守 Google 的相關政策和規定。如果需要特定的權限,建議查閱 Google 的官方文件。

User
上面那段只u有加phone沒辦法在 Google 登入時取得手機號碼的讀取權限?
ChatGPT
抱歉我之前的回答有誤,Google API 的 OAuth 2.0 驗證確實不提供直接取得使用者手機號碼的功能。Google 在他們的 OAuth 2.0 API 中並未包含用戶手機號碼的權限。我為混淆的回答感到抱歉。

一般而言,Google 的 OAuth 2.0 主要提供 "openid", "email", "profile" 等標準身份驗證相關的信息,而不包括敏感信息,例如手機號碼。手機號碼通常不是一個直接包含在 Google OAuth 的範疇內的資訊。

如果你需要使用者的手機號碼,你可能需要考慮其他方式,例如在你的應用程序中要求使用者提供手機號碼,並透過其他驗證方法確認其真實性。在一些國家或地區,可能有法規限制取得手機號碼,因此在實施時需確保符合當地法規和隱私政策。

User
那程式碼是否要改掉
ChatGPT
如果你希望使用者的手機號碼能夠透過 Google 登入取得,你需要先確定 Google API 是否支援這樣的功能。根據我之前的了解,Google OAuth 2.0 的範疇(scope)中並未包含用戶手機號碼的權限。因此,無法透過 Google OAuth 直接取得使用者的手機號碼。

如果你仍然想要收集使用者的手機號碼,有以下一些方式可以考慮:

在應用程序中要求手機號碼: 在註冊或登錄流程中,要求使用者提供手機號碼。這可能需要一個額外的表單或步驟,但這是獲取手機號碼的常見方式。

使用其他身份驗證方式: 例如,使用簡訊驗證或其他身份驗證方式,來確認使用者的手機號碼。

使用其他身份提供者: 如果手機號碼對你的應用程序很重要,可以考慮使用其他身份提供者,其中一些可能提供手機號碼作為資訊。

當然,上述的方式需要你在應用程序中進行相應的修改,包括前端和後端的程式碼。具體的實現方式取決於你的應用程序的需求和架構。

問題6 : Google API 設定
  1. 這裡提供的 GOOGLE_CLIENT_ID、GOOGLE_CLIENT_SECRET 和 GOOGLE_REDIRECT_URI 是 Google API Console 中的設定,你需要登入 Google 開發者控制台(Google Developer Console)並創建一個專案,然後啟用 Google+ API 和 Google People API。在這個專案中,你可以設置 OAuth 2.0 用於身份驗證的相關資訊。
    以下是一個簡要的步驟:
  • 登入 Google 開發者控制台。
  • 創建一個新的專案(如果尚未創建)。
  • 在左側的導覽面板中,選擇「API 和服務」 > 「憑證」。
  • 點擊「建立憑證」,選擇「OAuth 用戶端 ID」。
  • 選擇應用程式類型(例如 Web 應用程式)。
  • 在「授權原點」中,輸入你的應用程式的網址。
  • 在「已授權的重新導向 URI」中,輸入你的應用程式的重新導向 URI。
  • 完成配置,然後點擊「建立」。
  • 建立 OAuth 用戶端 ID 後,你將獲得 GOOGLE_CLIENT_ID 和 GOOGLE_CLIENT_SECRET。至於 GOOGLE_REDIRECT_URI,這是你的應用程式在使用者授權後由 Google 返回的 URI。

請注意,上面提到的步驟是基於目前的 Google 開發者控制台介面,該介面可能在未來有所變化。在設置 OAuth 憑證之前,你需要確保你的專案中啟用了 Google+ API 和 Google People API,這兩者都是用於取得用戶資訊的 API。

image
image

問題8 : 重新導向URI設定

在使用 Google OAuth 2.0 時,Google 要求在授權 JavaScript 來源和重新導向 URI 時使用有效的網域,而不僅僅是 IP 地址。這是為了提高安全性,避免潛在的風險。

image

如果你在本地測試時遇到這個問題,有幾種方法可以解決:
- 使用 localhost:
在開發階段,你可以將 JavaScript 來源和重新導向 URI 設置為 http://localhost:your-port。這是因為 localhost 在大多數情況下被視為有效的網域。
- 使用 ngrok:
ngrok 是一個工具,可以將本地伺服器公開到互聯網上。你可以在 ngrok 中建立一個公共 URL,然後將這個 URL 用於 Google OAuth 的 JavaScript 來源和重新導向 URI。然後,將 ngrok 提供的 URL 用於 Google OAuth。
- 設定有效的網域:
如果你有一個有效的域名,你可以在本機開發環境中模擬這個域名。這可能需要在你的本地 hosts 文件中進行一些設定,並在路由器或防火牆上進行一些設定,以允許外部流量進入。
選擇哪一種方法取決於你的需求和開發環境。

問題7 : Postman測試方法

在 Postman 中測試 Google 登入部分,你可以使用 OAuth 2.0 授權碼流程進行測試。以下是一個簡單的步驟:

建立 OAuth 2.0 請求:
在 Postman 中建立一個新的請求,選擇 OAuth 2.0 作為授權類型。點擊 "Get New Access Token"。

填寫 OAuth 2.0 資訊:

Token Name: 任意命名你的 token。
Grant Type: 選擇 "Authorization Code".
Callback URL: 這應該是你應用程式的登入回調 URI。
Auth URL: 這是 Google 的授權端點,通常類似於 https://accounts.google.com/o/oauth2/auth.
Access Token URL: 這是 Google 的令牌端點,通常類似於 https://accounts.google.com/o/oauth2/token.
Client ID: 你在 Google 開發者控制台中註冊應用程式時獲得的客戶端 ID.
Client Secret: 你在 Google 開發者控制台中註冊應用程式時獲得的客戶端密鑰.
Scope: 這是你想要的權限範圍,例如 "openid email profile",以及你的應用程式所需的任何其他範圍。
完成後,點擊 "Request Token"。

登入 Google 帳戶:
Postman 會彈出一個新的視窗,要求你登入 Google 帳戶,同時同意你的應用程式所需的權限。

取得 Access Token:
在成功登入並同意授權後,Postman 會收到包含 Access Token 的回應。

使用 Access Token 向 API 發送請求:
將收到的 Access Token 添加到你的 API 請求標頭中,通常是 Authorization: Bearer YOUR_ACCESS_TOKEN。

這樣你就可以使用 Postman 模擬整個 Google 登入的流程,同時檢查你的應用程式是否能夠正確處理授權碼和取得 Access Token。

在 Postman 中添加 Access Token 到 API 請求標頭是相對簡單的。以下是具體步驟:

步驟 1:取得 Access Token
在 Postman 中,你首先需要確保你已經成功登入並同意授權,以便收到包含 Access Token 的回應。

步驟 2:添加 Access Token 到請求標頭
打開 Postman。

創建或打開你想要測試的請求。

在請求的標頭(Headers)部分,你將添加 Authorization 標頭,其值為 "Bearer YOUR_ACCESS_TOKEN",其中 "YOUR_ACCESS_TOKEN" 是你從授權伺服器獲得的實際 Access Token。

選擇 "Authorization" 標頭類型為 "Bearer Token"。
在 "Token" 欄位中輸入你的 Access Token。
點擊 "Send" 以發送你的請求。

問題8

POST有認證成功拿到Token但沒有得到return,server停留在302,拿不到200。


Neccesory command

pip3 install requests-oauthlib pip3 install Flask-JWT pip3 install requests curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc | sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null && echo "deb https://ngrok-agent.s3.amazonaws.com buster main" | sudo tee /etc/apt/sources.list.d/ngrok.list && sudo apt update && sudo apt install ngrok ngrok config add-authtoken 2YPM0OZTQUMtRDd5oEAfha28qDC_23ceq4hHgzDx3RoXQkA4o # 要先在官網註冊才有authtoken ngrok http 192.168.0.2:5000

image

DB

  • Same as Signup API.

Code

from flask import Flask, jsonify, request, redirect, url_for import pymysql from flask_cors import cross_origin from flask_jwt import JWT, jwt_required, current_identity from requests_oauthlib import OAuth2Session import secrets app = Flask(__name__) app.config['SECRET_KEY'] = secrets.token_hex(16) # MySQL 連接資訊 db_config = { 'host': '127.0.0.1', 'user': 'root', 'password': 'rtlab111', 'database': 'db_1', #'port': 3306 } # 處理 404 錯誤 @app.errorhandler(404) def not_found_error(error): return jsonify({'error': 'Not Found'}), 404 # 創建 MySQL 連接 connection = pymysql.connect(**db_config) # 創建游標 cursor = connection.cursor() # 定義使用者類別,用於 JWT class User: def __init__(self, id, s_name, s_email, s_phone, access_level): self.id = id self.s_name = s_name self.s_email = s_email self.s_phone = s_phone self.access_level = access_level # 用於 JWT 的回調函式 def authenticate(s_name, s_password): query = "SELECT * FROM table_sign_up WHERE s_name = %s AND s_password = %s" cursor.execute(query, (s_name, s_password)) user = cursor.fetchone() if user: return User(id=user[0], s_name=user[1], s_email=user[2], s_phone=user[5], access_level=user[-1]) # 用於生成 JWT 的回調函式 def identity(payload): user_id = payload['identity'] query = "SELECT * FROM table_sign_up WHERE id = %s" cursor.execute(query, (user_id,)) user = cursor.fetchone() if user: return User(id=user[0], s_email=user[2], access_level=user[-1]) # 在 Flask 中啟用 JWT jwt = JWT(app, authenticate, identity) # Google OAuth 資訊 GOOGLE_CLIENT_ID = '92303118686-evca8stap1l4t512m5nobbgaog7pllvk.apps.googleusercontent.com' GOOGLE_CLIENT_SECRET = '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' GOOGLE_REDIRECT_URI = 'https://c22b-140-118-121-93.ngrok-free.app/user/google-callback' GOOGLE_AUTHORIZATION_BASE_URL = 'https://accounts.google.com/o/oauth2/auth' GOOGLE_TOKEN_URL = 'https://accounts.google.com/o/oauth2/token' GOOGLE_USERINFO_URL = 'https://www.googleapis.com/oauth2/v1/userinfo' # Google 登入路由,導向 Google 登入頁面 @app.route('/user/google-login') @cross_origin() def google_login(): google = OAuth2Session(GOOGLE_CLIENT_ID, redirect_uri=GOOGLE_REDIRECT_URI, scope=['openid', 'email', 'profile']) authorization_url, state = google.authorization_url(GOOGLE_AUTHORIZATION_BASE_URL) return redirect(authorization_url) # 這裡沒有加上手機,因為不包含要求這個功能 # Google 登入回調路由 @app.route('/user/google-callback') @cross_origin() def google_callback(): try: google = OAuth2Session(GOOGLE_CLIENT_ID, redirect_uri=GOOGLE_REDIRECT_URI) token = google.fetch_token(GOOGLE_TOKEN_URL, authorization_response=request.url) user_info = google.get(GOOGLE_USERINFO_URL).json() # 在這裡處理 Google 登入成功後的邏輯 # 你可能需要在資料庫中檢查使用者是否存在,如果不存在就創建一個新的使用者 existing_user_query = "SELECT * FROM table_sign_up WHERE s_email = %s" cursor.execute(existing_user_query, (user_info['email'],)) existing_user = cursor.fetchone() if existing_user: # 使用現有使用者的資訊 user = User(id=existing_user[0], s_name=existing_user[1], s_email=existing_user[2], s_phone=existing_user[5], access_level=existing_user[-1]) else: # 創建新的使用者 insert_user_query = "INSERT INTO table_sign_up (s_name, s_email, access_level) VALUES (%s, %s, %s)" cursor.execute(insert_user_query, (user_info['name'], user_info['email'], 0)) # 這裡假設使用者的預設權限是 0 connection.commit() # 取得新使用者的資訊 cursor.execute(existing_user_query, (user_info['email'],)) new_user = cursor.fetchone() user = User(id=new_user[0], s_name=new_user[1], s_email=new_user[2], s_phone=new_user[5], access_level=new_user[-1]) # 在這裡執行重定向 return jsonify({'message': 'Google login successful', 'user_info': {'s_name': user.s_name, 's_email': user.s_email, 's_phone': user.s_phone, 'access_level': user.access_level}}), 200 except Exception as e: return jsonify({'error': str(e)}), 500 # 登入的路由,支援一般登入和使用 Google 帳戶登入 @app.route('/user/login', methods=['POST']) @cross_origin() def login(): try: json_data = request.get_json() # 檢查是否包含所需的標題 required_titles = ['s_name', 's_password'] # 檢查是否有任何可插入的標題為空 if any(title not in json_data or not json_data[title].strip() for title in required_titles): return jsonify({'Error': 'Missing required parameters'}), 400 # 使用一般帳戶登入 if 's_name' in json_data and 's_password' in json_data: user = authenticate(json_data['s_name'], json_data['s_password']) if user: # 生成 JWT access_token = jwt.jwt_encode_callback(user) # 返回使用者所有資訊,不包括密碼 return jsonify({'message': 'Login successful', 'user_info': {'s_name': user.s_name, 's_email': user.s_email, 's_phone': user.s_phone, 'access_level': user.access_level}, 'access_token': access_token.decode('utf-8')}), 200 else: return jsonify({'Error': 'Invalid credentials'}), 400 else: return jsonify({'error'}), 400 except Exception as e: return jsonify({'error': str(e)}), 400 # Run the app if __name__ == '__main__': app.run(host='192.168.0.2',port=5000, debug=True)

Test result

Postman

一般登入、帳密正確
http://140.118.121.93:1112/user/login { "s_name": "jimmy", "s_password": "rtlab111" }

image

一般登入、帳密錯誤

image

使用Google帳戶登入、使用者已存在

image
image
image
image
image
image
image
image
image
image
image
image
image

使用Google帳戶登入、使用者不存在

不能使用瀏覽器POSTMAN測試,redirect uri會無法輸入

Server side

一般登入、帳密正確

image

一般登入、帳密錯誤

image

image
image

使用Google帳戶登入、使用者已存在

image

使用Google帳戶登入、使用者不存在

DB side

一般登入、帳密正確

image

一般登入、帳密錯誤

image

使用Google帳戶登入、使用者已存在
使用Google帳戶登入、使用者不存在

API server - Small data, big data, error

Flow4.drawio

Neccesory command

pip3 install flask-socketio pip3 install schedule

Code

Server side

... def calculate_average_cpu_usage(): # 執行 SQL 查詢以獲取 s_cpu 數據 select_query = "SELECT s_cpu FROM table_2 WHERE s_nf = 'amf'" cursor.execute(select_query) # 獲取查詢結果 cpu_data = cursor.fetchall() # 轉換 s_cpu 的數據為數字並計算總和 total_cpu = sum(float(data[0]) for data in cpu_data) # 計算 s_cpu 的平均值 if cpu_data: average_cpu = total_cpu / len(cpu_data) return average_cpu else: return 0 # 或者返回其他預設值 # 定義 Socket.IO 事件處理程序(用於向前端發送警告) @socketio.on('alert') def handle_alert(alert_data): print('Received alert data:', alert_data) # 在這裡可以向前端發送警告 def job(): # 計算平均 s_cpu 使用率 average_cpu = calculate_average_cpu_usage() # 檢查是否超過 90% if average_cpu > 90: # 發送警告到前端 alert_data = {'message': 'High CPU Usage', 'average_cpu': average_cpu} socketio.emit('alert', alert_data) # 定義定時任務,每分鐘執行一次 schedule.every(1).minutes.do(job) def run_flask_app(): # 啟動 Socket.IO socketio.run(app, host='192.168.0.2', port=5000, debug=True, use_reloader=False) # 在一個新的執行緒中運行 Flask 應用程式 flask_thread = Thread(target=run_flask_app) flask_thread.start() # 在主執行緒中執行定時任務 while True: try: schedule.run_pending() time.sleep(1) except KeyboardInterrupt: break # 中斷程式 # 等待 Flask 應用程式執行緒完成 flask_thread.join()

Client side

// socketio_client.js const io = require('socket.io-client'); const socket = io('http://192.168.0.2:5000'); // 監聽 "alert" 事件 socket.on('alert', function (data) { console.log('Received alert:', data); // 在這裡處理接收到的警告信息 });

Test result

npm install socket.io-client node socketio_client.js

Postman -> DB

image
image

Server side

image

Client side

image

Streaming platform

CREATE TABLE IF NOT EXISTS table_account ( time_register TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP PRIMARY KEY, s_name VARCHAR(255), s_email VARCHAR(255), s_password VARCHAR(255), s_phone CHAR(10), i_follower INT, i_donate INT, time_streaming INT, access_right TINYINT, i_status TINYINT ); CREATE TABLE IF NOT EXISTS table_streaming ( s_name VARCHAR(255) PRIMARY KEY, i_people INT, hours TIMESTAMP, i_follower INT, i_donate INT ); CREATE TABLE IF NOT EXISTS table_sourceinfo ( s_name VARCHAR(255) PRIMARY KEY, ip_source VARCHAR(255), time_login TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, time_logout TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); alter table table_account change time_register time_register timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP; INSERT INTO table_streaming (s_name, i_people , i_follower , i_donate ) VALUES ('ymmijJ', '5', '4','1000'); ALTER TABLE table_account MODIFY COLUMN i_status INT DEFAULT 0; -- 在已有表上更改主键约束 ALTER TABLE table_sourceinfo DROP PRIMARY KEY; -- 在 s_email 字段上创建普通索引 CREATE INDEX idx_s_email ON table_sourceinfo (s_email);

API Call Flow

  • V : has API format
  • Y : has writed a API

User right

Different main page

/user/access_right
right

We are figuring out other methods now.

Search on home page V Y

/user/search
Serch

@app.route('/user/search', methods=['POST']) @cross_origin() def search(): json_data = request.get_json() # 檢查是否包含所需的標題 required_titles = ['s_name'] # 檢查是否有任何可插入的標題為空 if any(title not in json_data or not json_data[title].strip() for title in required_titles): return jsonify({'Error': 'Missing required parameters'}), 400 s_name = json_data.get('s_name', '') search_name = f"SELECT * FROM table_streaming WHERE s_name LIKE %s" cursor.execute(search_name, (f"%{s_name}%",)) results = cursor.fetchall() print(results) # 如果有结果,则返回状态码 200 和查询结果 if results: name_list = [{'s_name': result[0]} for result in results] return jsonify(name_list), 200 else: # 如果没有结果,则返回状态码 400 和错误消息 return jsonify({'Error': f"No results found for s_name: {s_name}"}), 400

少一個直播主按下直播按鈕後POST開始直播時間到table_streaming的API Y

@app.route('/user/live', methods=['POST']) @cross_origin() def live(): json_data = request.get_json() # 檢查是否包含所需的標題 required_titles = ['s_name', 'i_people', 'i_follower', 'i_donate'] # 檢查是否有任何可插入的標題為空 if any(title not in json_data or (type(json_data[title]) == str and not json_data[title].strip()) for title in required_titles): return jsonify({'Error': 'Missing required parameters'}), 400 # 準備用於插入的標題和值 insert_titles = required_titles insert_values = [json_data[title] for title in required_titles] # 檢查 's_name' 是否已經存在於表格中 check_email_query = "SELECT COUNT(*) FROM table_streaming WHERE s_name = %s" cursor.execute(check_email_query, (json_data['s_name'],)) email_exists = cursor.fetchone()[0] if email_exists: return jsonify({'Error': 's_name already exists'}), 400 # 插入資料到 MySQL 表格 insert_query = f"INSERT INTO table_streaming ({', '.join(insert_titles)}) VALUES ({', '.join(['%s']*len(insert_titles))})" cursor.execute(insert_query, insert_values) connection.commit() # 加入 print 語句以記錄成功接收並插入資料 print(f"Received JSON: {json_data}") return jsonify({'message': 'JSON received successfully'}), 200

Log in V

/user/login
log in

Google ID log in

google

Log out V

/user/logout
Log out

Sign up V

/user/signup
sign up

Streaming, chatroom

  • 1 -> 2 -> 3 -> 4

webrtc

Streaming with other people

We are figuring out other methods now.

Streaming function

Gift(super chat, give money), New follower V

/user/money_follow

  • POST -> PUT
    Gift

Current number of people, current streaming duration V

/user/current_people

  • Count people throughput at frontend.
    • Get the last value and plus new value to table
    • Count total number at backend.

Current

Current streaming duration V

/user/current_time

  • PUT at the start and the end of streaming to count total time in table.
    • Start time will be stored in variable, so we can cover it with new time(Like the end time or alert check.).
  • Catch the current time in frontend and directly display it.

current1

Play the music, Lyrics reminder

End the streaming V

end

User data(Follower number, Total number of earned bonus money, Total streaming time)

/user/ueinfo
user data

end

Alert

/user/alert

  • Send the current time and server checks if it is bigger than 6 hrs.
    alert