# Study :::spoiler 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](https://1drv.ms/w/s!AndI1JV-h1Q5jMwMeD9P2Z4qYCEdvA?e=p5TX2U) ## Python ```linux= 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 ``` :::danger Error from python3 api.py ```linux= 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)") ``` ::: :::success * Need to install mysql-server, not just pymysql. ::: ## MySQL ```linux= 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 ```linux= telnet 127.0.0.1 5000 ``` * Connected if there is a service using the port, or the port is not connected. :::info * 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 ```linux= 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" } ``` :::success Result ![Screenshot from 2023-11-17 14-18-14](https://hackmd.io/_uploads/HJ0LUt4Na.png) ::: ### 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](https://hackmd.io/_uploads/rklbqquV6.png) * 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](https://hackmd.io/_uploads/HyxJ29_Vp.png) * Spec(API-8) * Client POST the data 10 seconds for once. ![unnamed](https://hackmd.io/_uploads/rk5sjO4Ea.png) ## DB ```linux= 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](https://hackmd.io/_uploads/SkO8G4v46.png) ## Code ```python= 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 :::spoiler 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](https://hackmd.io/_uploads/BJ4JVEwET.png) ::: :::spoiler 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](https://hackmd.io/_uploads/SJmuS4DET.png) ::: :::spoiler GET amf ![image](https://hackmd.io/_uploads/Hk_rUEw4p.png) ::: ### Server side :::spoiler POST 沒有少標題、包含空值 ![image](https://hackmd.io/_uploads/SknS4NPVT.png) ::: :::spoiler POST 少標題 ![image](https://hackmd.io/_uploads/Byn6S4PNT.png) ::: :::spoiler GET amf ![image](https://hackmd.io/_uploads/BJas84PET.png) ::: ### DB side :::spoiler POST 沒有少標題、包含空值 ![image](https://hackmd.io/_uploads/HJUHBVvVT.png) ::: :::spoiler POST 少標題 ![image](https://hackmd.io/_uploads/HJy5HEwVa.png) ::: :::spoiler GET amf (有多筆只會得到最新一筆) ![image](https://hackmd.io/_uploads/rkobwVwV6.png) ::: # API server --- Web client ## Sign up ![Flow2.drawio](https://hackmd.io/_uploads/By6T29d46.png) * Spec(API-1) ### DB ```linux= 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](https://hackmd.io/_uploads/rJtgZHDNT.png) ### Code ```python= # 新的端點,用於接收 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 :::spoiler 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](https://hackmd.io/_uploads/B1PTfBDE6.png) ::: :::spoiler POST email已註冊 ![image](https://hackmd.io/_uploads/SkDtXrvVT.png) ::: :::spoiler 輸入空值或只有輸入空白 ![image](https://hackmd.io/_uploads/SJ2N4SPEa.png) ![image](https://hackmd.io/_uploads/S1DH4HwNp.png) ::: :::spoiler 密碼確認不一樣 ![image](https://hackmd.io/_uploads/HJBt4SvNp.png) ::: #### Server side :::spoiler POST 正常格式 ![image](https://hackmd.io/_uploads/ByfWmHv4a.png) ::: :::spoiler POST email已註冊 ![image](https://hackmd.io/_uploads/r18s7rPV6.png) ::: :::spoiler 輸入空值或只有輸入空白 ![image](https://hackmd.io/_uploads/r1VINSwN6.png) ::: :::spoiler 密碼確認不一樣 ![image](https://hackmd.io/_uploads/SyJcEBPE6.png) ::: #### DB side :::spoiler POST 正常格式 ![image](https://hackmd.io/_uploads/ryVMXSwEp.png) ::: :::spoiler POST email已註冊 ![image](https://hackmd.io/_uploads/ryVMXSwEp.png) ::: :::spoiler 輸入空值或只有輸入空白 ![image](https://hackmd.io/_uploads/ryVMXSwEp.png) ::: :::spoiler 密碼確認不一樣 ![image](https://hackmd.io/_uploads/ryVMXSwEp.png) ::: ## Log in (Google Open ID) ![Flow3.drawio (1)](https://hackmd.io/_uploads/rkpF1sdVa.png) * Spec(API-2) -> 用GET不是會把帳密放在URL?不安全? -> 改用POST(放在body) --- :::spoiler 問題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 在身份驗證和授權中廣泛使用,因為它們提供了一種安全、簡單和標準的方式來表示和交換這些信息。 ::: :::spoiler 問題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,之後該令牌可以用來驗證和授權用戶的請求。 2. 我看後面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,可能是因為目前的邏輯還不需要它,但在更複雜的應用中可能會用到。 3. 所以當前如果只是要從資料庫對照登入資訊是用不到的嗎 是的,當前的登入邏輯中,你主要是透過 authenticate 函式來從資料庫中對比使用者提供的帳號密碼是否正確。在這個過程中,identity 函式並沒有直接使用到。 identity 函式通常在你需要在每個請求中取得當前使用者的資訊時使用。例如,當你的應用需要在請求中驗證使用者的權限,或者在請求中記錄使用者的操作歷史時,就可能會使用到 identity 函式。 總的來說,identity 函式的使用取決於你的應用邏輯,如果目前的邏輯不需要直接取得當前使用者的資訊,那麼你可以在不使用它的情況下繼續進行。 4. 那為何還需要使用JWT 我直接像sign up的檢查一樣使用資料庫指令對照就好了呀 還是說和token有關?或是和google登入有關??可是現在login API的部分已經去除掉google token了??為甚麼? JWT(JSON Web Token)的主要目的是在網路應用中提供一種安全的方法來傳遞資訊,特別是用於驗證和資訊交換。在你的情境中,JWT 通常用於驗證使用者的身份,以及在使用者成功登入後發送安全的令牌。 下面是 JWT 的一些主要優勢: 安全的身份驗證: JWT 可以在使用者驗證成功後生成一個安全的令牌,該令牌包含了使用者的身份信息。這樣,當使用者在之後的請求中提供這個令牌,你的應用可以驗證該令牌,確保該使用者是有效的,而不需要再次查詢資料庫。 無需每次都查詢資料庫: 一旦使用者登入,你可以使用 JWT 來在之後的請求中驗證使用者身份,而不必每次都去查詢資料庫。這有助於提高效能,減少對資料庫的負擔。 避免明文傳輸密碼: JWT 通常不包含使用者的密碼等敏感資訊,避免了在請求中傳輸明文密碼的風險。 ::: :::spoiler 問題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)。 ::: :::spoiler 問題4 : id使用方法 實際上我資料庫中的id只是資料筆數和順序而已 這樣是否需要更改程式碼 ChatGPT id 欄位通常是資料表的主鍵,用來唯一識別每一筆資料。在你的程式碼中,id 欄位被用作 JWT 中的 identity,這是 Flask-JWT 擴展預期的用法。這樣的做法基本上是合理的,因為 JWT 需要一個唯一的識別符號來代表每個使用者。 如果你的 id 欄位不是唯一的,你可能需要考慮使用其他唯一的識別符號來替代,或者針對不同的使用者建立自訂的 identity 生成邏輯。 總的來說,如果你的 id 欄位確實是唯一的並且足夠用作使用者的唯一標識符,則沒有必要修改這部分的程式碼。如果有其他適合的唯一標識符,可以考慮修改 identity 函式以反映這一點。 ::: :::spoiler 問題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 直接取得使用者的手機號碼。 如果你仍然想要收集使用者的手機號碼,有以下一些方式可以考慮: 在應用程序中要求手機號碼: 在註冊或登錄流程中,要求使用者提供手機號碼。這可能需要一個額外的表單或步驟,但這是獲取手機號碼的常見方式。 使用其他身份驗證方式: 例如,使用簡訊驗證或其他身份驗證方式,來確認使用者的手機號碼。 使用其他身份提供者: 如果手機號碼對你的應用程序很重要,可以考慮使用其他身份提供者,其中一些可能提供手機號碼作為資訊。 當然,上述的方式需要你在應用程序中進行相應的修改,包括前端和後端的程式碼。具體的實現方式取決於你的應用程序的需求和架構。 ::: :::spoiler 問題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](https://hackmd.io/_uploads/HJZlcRPVp.png) ![image](https://hackmd.io/_uploads/HJcdtAvE6.png) ::: :::spoiler 問題8 : 重新導向URI設定 在使用 Google OAuth 2.0 時,Google 要求在授權 JavaScript 來源和重新導向 URI 時使用有效的網域,而不僅僅是 IP 地址。這是為了提高安全性,避免潛在的風險。 ![image](https://hackmd.io/_uploads/SyhJhTwET.png) 如果你在本地測試時遇到這個問題,有幾種方法可以解決: - 使用 localhost: 在開發階段,你可以將 JavaScript 來源和重新導向 URI 設置為 http://localhost:your-port。這是因為 localhost 在大多數情況下被視為有效的網域。 - 使用 ngrok: ngrok 是一個工具,可以將本地伺服器公開到互聯網上。你可以在 ngrok 中建立一個公共 URL,然後將這個 URL 用於 Google OAuth 的 JavaScript 來源和重新導向 URI。然後,將 ngrok 提供的 URL 用於 Google OAuth。 - 設定有效的網域: 如果你有一個有效的域名,你可以在本機開發環境中模擬這個域名。這可能需要在你的本地 hosts 文件中進行一些設定,並在路由器或防火牆上進行一些設定,以允許外部流量進入。 選擇哪一種方法取決於你的需求和開發環境。 ::: :::spoiler 問題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" 以發送你的請求。 ::: :::spoiler 問題8 POST有認證成功拿到Token但沒有得到return,server停留在302,拿不到200。 ::: --- ### Neccesory command ```linux= 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](https://hackmd.io/_uploads/B1cc4kOE6.png) ### DB * Same as Signup API. ### Code ```python= 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 :::spoiler 一般登入、帳密正確 ```= http://140.118.121.93:1112/user/login { "s_name": "jimmy", "s_password": "rtlab111" } ``` ![image](https://hackmd.io/_uploads/BJPMoAPET.png) ::: :::spoiler 一般登入、帳密錯誤 ![image](https://hackmd.io/_uploads/Sy0jo0v4p.png) ::: :::spoiler 使用Google帳戶登入、使用者已存在 ![image](https://hackmd.io/_uploads/rJbKVxuNp.png) ![image](https://hackmd.io/_uploads/HJW5Vg_4a.png) ![image](https://hackmd.io/_uploads/Hk9jVg_4a.png) ![image](https://hackmd.io/_uploads/Syv3Ng_ET.png) ![image](https://hackmd.io/_uploads/r1q64x_Np.png) ![image](https://hackmd.io/_uploads/Bk_REgON6.png) ![image](https://hackmd.io/_uploads/BknfrldV6.png) ![image](https://hackmd.io/_uploads/Bk8mSeuNa.png) ![image](https://hackmd.io/_uploads/rJUBBgd4a.png) ![image](https://hackmd.io/_uploads/SyfOUedNa.png) ![image](https://hackmd.io/_uploads/ByItIldET.png) ![image](https://hackmd.io/_uploads/HyWBvx_4T.png) ![image](https://hackmd.io/_uploads/r1j6_eOE6.png) ::: :::spoiler 使用Google帳戶登入、使用者不存在 ::: **不能使用瀏覽器POSTMAN測試,redirect uri會無法輸入** #### Server side :::spoiler 一般登入、帳密正確 ![image](https://hackmd.io/_uploads/Sk-BjAwVp.png) ::: :::spoiler 一般登入、帳密錯誤 ![image](https://hackmd.io/_uploads/rynnoCvVp.png) ::: ![image](https://hackmd.io/_uploads/Hk_6Vy_ET.png) ![image](https://hackmd.io/_uploads/r1qxSyd4T.png) :::spoiler 使用Google帳戶登入、使用者已存在 ![image](https://hackmd.io/_uploads/ByrKDluVa.png) ::: :::spoiler 使用Google帳戶登入、使用者不存在 ::: #### DB side :::spoiler 一般登入、帳密正確 ![image](https://hackmd.io/_uploads/By7UiRwVT.png) ::: :::spoiler 一般登入、帳密錯誤 ![image](https://hackmd.io/_uploads/By7UiRwVT.png) ::: :::spoiler 使用Google帳戶登入、使用者已存在 ::: :::spoiler 使用Google帳戶登入、使用者不存在 ::: # API server --- Small data, big data, error ![Flow4.drawio](https://hackmd.io/_uploads/SkbNZjdNa.png) ## Neccesory command ```linux= pip3 install flask-socketio pip3 install schedule ``` ## Code ### Server side ```python= ... 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 ```javascript= // 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 ```linux= npm install socket.io-client node socketio_client.js ``` ### Postman -> DB ![image](https://hackmd.io/_uploads/ryJpXq_46.png) ![image](https://hackmd.io/_uploads/SJR9Q9O4a.png) ### Server side ![image](https://hackmd.io/_uploads/HynlE5_Ep.png) ### Client side ![image](https://hackmd.io/_uploads/HJKYVcuVp.png) # 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 :::success ### User right #### Different main page /user/access_right ![right](https://hackmd.io/_uploads/B1rlQgCV6.png) #### We are figuring out other methods now. ### Search on home page V Y /user/search ![Serch](https://hackmd.io/_uploads/ByNe8bC4p.png) ```python= @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 ```python= @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](https://hackmd.io/_uploads/By7aGZ0N6.png) ### Google ID log in ![google](https://hackmd.io/_uploads/S1ZW5RTE6.png) ### Log out V /user/logout ![Log out](https://hackmd.io/_uploads/r1VJfZ04p.png) ### Sign up V /user/signup ![sign up](https://hackmd.io/_uploads/HJeHQbR4T.png) ### Streaming, chatroom * 1 -> 2 -> 3 -> 4 ![webrtc](https://hackmd.io/_uploads/Hy_KVxA4T.png) ### 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](https://hackmd.io/_uploads/HJmC3G0Np.png) #### 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](https://hackmd.io/_uploads/r1tA2fRVT.png) #### 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](https://hackmd.io/_uploads/r1cfyQ0VT.png) ### Play the music, Lyrics reminder * Try to call [KKBOX open api](https://docs-zhtw.kkbox.codes/#overview--%E6%88%91%E5%8F%AF%E4%BB%A5%E9%80%8F%E9%81%8E-kkbox-open-api-%E4%B8%8B%E8%BC%89%E9%9F%B3%E6%A8%82%E6%AA%94%E6%A1%88%E5%97%8E%EF%BC%9F) * Youtube api ### End the streaming V ![end](https://hackmd.io/_uploads/BJ_zT6ZBa.png) ### User data(Follower number, Total number of earned bonus money, Total streaming time) /user/ueinfo ![user data](https://hackmd.io/_uploads/SyHIaTZra.png) ![end](https://hackmd.io/_uploads/BJ_zT6ZBa.png) ### Alert /user/alert * Send the current time and server checks if it is bigger than 6 hrs. ![alert](https://hackmd.io/_uploads/rybhel046.png) :::