owned this note
owned this note
Published
Linked with GitHub
## 0804
> 明天簡報問題: IATA NDC/ IATA One Record
> 開始陸續詢問各單位有沒有填寫清冊上的問題。
資料庫:
[flask+mysql](https://medium.com/seaniap/python-web-flask-flask-sqlalchemy%E6%93%8D%E4%BD%9Cmysql%E8%B3%87%E6%96%99%E5%BA%AB-2a799acdec4c)
[py cache](https://ithelp.ithome.com.tw/articles/10363747)
[sql 修改資料](https://ithelp.ithome.com.tw/articles/10220040)
[sqlite&mysql的區別](https://medium.com/erens-tech-book/sqlite-%E8%88%87-mysql-%E7%9A%84%E5%B7%AE%E5%88%A5-a14926030ddd)
[sqlite教程](https://www.runoob.com/sqlite/sqlite-commands.html)
[ETCD入門](https://hackmd.io/@AmdAc990TDm3EkP4EmImTA/BygRA9q7P)
錯誤訊息:
> [sqlalchemy.exc.IntegrityError: UNIQUE constraint failed](https://stackoverflow.com/questions/62688192/sqlalchemy-exc-integrityerror-unique-constraint-failed) -- 這個是id類的重複(id是不能重複的);避免這個問題,以Username舉例可以這樣確認
```python
# 新增前先查詢
@app.route('/')
def index():
user = User.query.filter_by(username='alice').first()
if not user:
user = User(username='alice')
db.session.add(user)
db.session.commit()
return "新增成功"
else:
return "使用者已存在"
```
> 問題:sqlite的檔名到底是什麼(還有他的註解方式,能不能用vscode寫)
感覺要想的很多 瓜張
## 0805
好讚,修掉回到上一頁還會有資訊的問題了。
在main.py裡面加上no_cache
```python
def no_cache(rendered_template):
response = make_response(rendered_template)
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1'
return response
```
*在開發軟體之前,要先了解並確認相關的需求才能進行下一步。
(看要不要找腿庫朋友確認一下)
**- 軟體開發需求分析**
- 文件資訊
|項目|說明|
|----|----|
|專案名稱|會員管理系統|
|文件版本|v0.1|
|撰寫日期|2025-08-05|
|撰寫人|閃嘉珮|
1. 專案背景與目標
本統旨在提供一套簡易而完整的會員資訊管理平台,能夠支援**會員新增、查詢、修改與刪除**,並具備登入控管與基本資料驗證功能,以協助管理者維護會員清單、聯絡資訊與基本統計。
2. 使用者角色
|角色|權限描述|
|----|----|
|後台端使用者|可登入、檢視、編輯、刪除與新增會員資訊|
3. 功能需求
|編號|功能名稱|描述|
|----|----|----|
|FR-001|使用者登入|1.需要有能夠輸入帳號密碼的位置 <br>2.密碼輸入需要遮罩<br>3. 登入需做驗證,登入失敗需要跳出錯誤訊息|
|FR-002|登出功能|點選登出會回到登入頁面,並清除登入資訊。|
|FR-003|查詢會員資料|使用者可以查詢會員清單,查詢的機制:<br>1.會員卡號的搜尋必須要有完全一樣的卡號。<br>2.姓名搜尋可以模糊比對的方式找出符合條件的會員資料。<br>查詢成功後須於下方廠出表格並提供有連結可點選的會員卡號|
|FR-004|編輯/刪除會員資料|1.使用者可編輯/刪除會員資料<br>2.任何資料異動皆須跳出確認視窗,按下確後才會進行後續動作<br>3.異動成功後會回到「查詢會員資訊」頁面|
|FR-005|新增會員資料|1.使用者可新增會員資料<br>2.任何資料異動皆須跳出確認視窗,按下確後才會進行後續動作<br>3.新增成功後會回到「首頁」頁面|
4. 非功能需求
|編號|功能名稱|描述|
|----|----|----|
|NFR-001|資料驗證|欄位格式需正確|
|NFR-002|安全性|使用session控管登入狀態|
5. 資料欄位與格式
|欄位名稱|類型|描述|限制|
|----|----|----|----|
|id|INTEGER|系統流水號|PRIMARY KEY, AUTOINCREMENT|
|member_number|TEXT|會員編號|UNIQUE, NOT NULL|
|family_name|TEXT|姓氏|NOT NULL|
|given_name|TEXT|名字|NOT NULL|
|sex|TEXT|性別|M或F|
|date_of_birth|DATE|出生日期|NOT NULL|
|email|TEXT|信箱|格式須合法|
|phone_home|TEXT|住家電話|可空值|
|phone_office|TEXT|公司電話|可空值|
|phone_mobile|TEXT|行動電話|可空值|
|country|TEXT|國家|可空值|
|address|TEXT|地址|可空值|
6. 權限與安全需求
7. 後續擴充建議
[DDoS attack](https://www.cloudflare.com/zh-tw/learning/ddos/what-is-a-ddos-attack/)
- DDoS attack
分散式阻斷服務攻擊是一種惡意嘗試,它利用大量的網際網路流量使目標伺服器或其周圍的基礎架構不堪重負,從而阻斷目標伺服器、服務或網路的正常流量。
- 如何識別DDoS攻擊
最明顯的症狀是,網站或服務突然變慢或不可用,但由於多種原因也有可能歲的效能問題;從流量分析工具可以幫助我們發現DDoS攻擊的一些明顯跡象。
- 來自單個IP位址或IP範圍的可疑流量
- 大量流量來自擁有單一行為特徵(例如裝置類型、地理位置或Web瀏覽器版本)的使用者
- 對單一頁面或端點的請求出現無法解釋的激增
- 奇怪的流量模式: 如在非正常時段激增,或不自然的模式(例如每10分鐘就激增)
- OSI模型
神奇7層,整理一下每層在幹嘛。
[secure by design](https://blog.cloudflare.com/secure-by-design-principles/)
[DDoS阻斷服務攻擊](https://zh.wikipedia.org/wiki/%E9%98%BB%E6%96%B7%E6%9C%8D%E5%8B%99%E6%94%BB%E6%93%8A)
> 代辦:
> 資訊人員ppt
> 軟體開發需求分析
## 0808
原本的IRM (Information Rights Management)
- 是早期Office (2010、2013、2016)支援的權限控管技術。
- 可以限制文件的操作例如:
- 禁止轉寄、列印、複製
- 設定存取過期時間
- 功能較單一,缺乏智慧分類與可視化監控
PIP (Purview Information Protection)
是IRM的雲端延伸與整合體
- Microsoft 將原本的 AIP(Azure Information Protection) 與 Microsoft Compliance、DLP、MIP整合進 Microsoft Purview 平台。
- 新一代資料標籤+敏感性分類+權限控管的整合方案。
#### Why 這次M365整合需要安裝PIP呢?
答:
1. IRM 的技術已過時,無法與雲端協作應用(如Teams, OneDrive)完美整合。
2. IRM不支援自動分類、自動標籤、審核日誌等功能。
3. PIP 支援機密等級標籤,可應用於郵件、文件、Teams訊息等。
4. PIP 支援自動分類、條件式存取、審計追蹤。
### MITRE ATT&CK
[MITRE ATT&CK](https://attack.mitre.org/)
[D3FEND MATRIX](https://d3fend.mitre.org/)
[OSINT framework](https://osintframework.com/)
[CVSS Score Distribution for Top 50 Product By Total Number of Distinct Vulnerabilities](https://www.cvedetails.com/top-50-product-cvssscore-distribution.php)
## 0812
好久沒記,東西有點多
0813 - TCP/IP 介紹
0815 - 電腦系統作業環境介紹、Winmatrix、會員系統Demo
[winmatrix](http://www.simopro.com/Standard/Standard.htm)
0812端點防護review
1. EADM除了資料交換的機制,還包含公司內部提權的操作,以及檢核當天裝了什麼軟體(無論是有沒有提權都會檢核)
2. Symantec 防毒是不包含防火牆跟IPS的!
3. 我們只有使用D-Security的浮水印功能,在這頁列出的**檔案加密、權限控管、外洩防護、裝置管控跟離線保護**實際上都是用**IRM來操作**

*補充: 後續改版的MIP僅提供雲端檔案保護,超刺激。
要思考的問題:
1. 是不是層層防護(把SEP跟D-Security加在一起)是不是就百毒不侵?會不會有沒擋住造成一線天的問題?要怎麼處理?
2. Winmatrix的功能、目前有用到的功能、你覺得哪些會需要用(下次簡報)。
## 0821 0827更新
零件打包場:
main.py
```
from flask import Flask, render_template, request, redirect, url_for, session, make_response
from werkzeug.security import generate_password_hash, check_password_hash
import sqlite3, os, re
app = Flask(__name__)
app.secret_key = os.environ.get('FLASK_SECRET_KEY','fallback_key')
DB_PATH = os.path.join(os.path.dirname(__file__), 'member.db')
def no_cache(content):
response = make_response(content)
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '-1'
return response
def is_valid_input(data):
return bool(re.match(r"^[a-zA-Z0-9_]{2,30}$", data))
def is_valid_password(password):
count = 0
if re.search(r'[A-Z]', password): count += 1
if re.search(r'[a-z]', password): count += 1
if re.search(r'[0-9]', password): count += 1
if re.search(r'[!@#$%^&*(),.?\":{}|<>]', password): count += 1
return count >= 3 and len(password) >= 8
def authenticate_user(account, password):
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("SELECT password FROM users WHERE account = ?", (account,))
row = c.fetchone()
conn.close()
return row and check_password_hash(row[0], password)
@app.route('/login.html')
def login_page():
return no_cache(render_template('login.html'))
@app.route('/login', methods=['POST'])
def login():
account = request.form.get('account')
password = request.form.get('password')
if not account or not password or not is_valid_input(account) or not is_valid_password(password):
return no_cache(render_template('login.html', error="格式錯誤"))
if authenticate_user(account, password):
session['user'] = account
return redirect('/homepage.html')
return no_cache(render_template('login.html', error="帳號或密碼錯誤"))
@app.route('/homepage.html')
def homepage():
if 'user' in session:
return no_cache(render_template('homepage.html', user=session['user']))
return redirect('/login.html')
@app.route('/logout', methods=['POST'])
def logout():
session.clear()
return redirect('/login.html')
@app.route('/member_Add.html')
def member_add_page():
if 'user' not in session:
return redirect('/login.html')
return no_cache(render_template('member_Add.html', user=session['user']))
@app.route('/add_member', methods=['POST'])
def add_member():
if 'user' not in session:
return redirect('/login.html')
data = {k: request.form.get(k, '').strip() for k in [
'family_name', 'given_name', 'sex', 'date_of_birth',
'email', 'phonenum_h', 'phonenum_o', 'phonenum_m',
'country', 'address', 'id_number'
]}
# 基本必填檢查
if not data['family_name'] or not data['given_name'] or not data['sex'] or not data['id_number']:
return no_cache(render_template('member_Add.html', user=session['user'], success="欄位不可空白"))
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
# 以 id_number 防重複新增
c.execute("SELECT 1 FROM members WHERE id_number = ?", (data['id_number'],))
if c.fetchone():
conn.close()
return no_cache(render_template('member_Add.html', user=session['user'], success="此身分/護照號碼已存在,請勿重複新增"))
c.execute("SELECT MAX(id) FROM members")
max_id = c.fetchone()[0]
next_id = (max_id or 0) + 1
member_number = f"M{next_id:05d}"
c.execute('''
INSERT INTO members (member_number, family_name, given_name, sex, dob, email,
phone_h, phone_o, phone_m, country, address, id_number)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (member_number, data['family_name'], data['given_name'], data['sex'], data['date_of_birth'],
data['email'], data['phonenum_h'], data['phonenum_o'], data['phonenum_m'],
data['country'], data['address'], data['id_number']))
conn.commit()
conn.close()
return f'''
<script>
alert("✅ 會員新增成功!會員編號為 {member_number}");
window.location.href = "/homepage.html";
</script>
'''
@app.route('/member_Info.html')
def member_info_page():
if 'user' not in session:
return redirect('/login.html')
return no_cache(render_template('member_Info.html', user=session['user']))
@app.route('/search_member', methods=['POST'])
def search_member():
if 'user' not in session:
return redirect('/login.html')
number = request.form.get('member_num', '').strip()
family = request.form.get('family_name', '').strip()
given = request.form.get('given_name', '').strip()
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
c = conn.cursor()
rows = []
if number:
c.execute("SELECT member_number, family_name, given_name FROM members WHERE member_number = ?", (number,))
rows = c.fetchall()
elif family and given:
query = '''
SELECT member_number, family_name, given_name FROM members WHERE
(family_name = ? AND given_name = ?) OR
(family_name = ? AND substr(given_name, 1, 2) LIKE ?) OR
(given_name = ? AND family_name = ?) OR
(substr(given_name, 1, 2) LIKE ? AND family_name = ?)
'''
args = (family, given, family, given[:2], family, given, family[:2], given)
c.execute(query, args)
rows = c.fetchall()
elif family:
c.execute("SELECT member_number, family_name, given_name FROM members WHERE family_name LIKE ?", (f"%{family}%",))
rows = c.fetchall()
elif given:
c.execute("SELECT member_number, family_name, given_name FROM members WHERE given_name LIKE ?", (f"%{given}%",))
rows = c.fetchall()
conn.close()
if rows:
return no_cache(render_template('member_Info.html', user=session['user'], results=rows))
else:
return no_cache(render_template('member_Info.html', user=session['user'], error="找不到符合的會員"))
# 修正:使用路由參數,不再讀 request.args
@app.route('/member_Update.html/<member_number>')
def member_update(member_number):
if 'user' not in session:
return redirect('/login.html')
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute("SELECT * FROM members WHERE member_number = ?", (member_number,))
result = c.fetchone()
conn.close()
return no_cache(render_template('member_Update.html', user=session['user'], member=result))
@app.route('/update_member', methods=['POST'])
def update_member():
if 'user' not in session:
return redirect('/login.html')
member_number = request.form['member_number']
# 先抓舊資料,檢查 id_number 是否更動/衝突
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
c = conn.cursor()
c.execute("SELECT id, id_number FROM members WHERE member_number = ?", (member_number,))
current = c.fetchone()
if not current:
conn.close()
return redirect(url_for('member_info_page'))
new_values = {
'family_name': request.form.get('family_name','').strip(),
'given_name': request.form.get('given_name','').strip(),
'sex': request.form.get('sex','').strip(),
'dob': request.form.get('dob','').strip(),
'email': request.form.get('email','').strip(),
'phone_h': request.form.get('phone_h','').strip(),
'phone_o': request.form.get('phone_o','').strip(),
'phone_m': request.form.get('phone_m','').strip(),
'country': request.form.get('country','').strip(),
'address': request.form.get('address','').strip(),
'id_number': request.form.get('id_number','').strip() or None
}
# 若新 id_number 與其他會員衝突 → 拒絕
if new_values['id_number'] is not None:
c.execute("SELECT member_number FROM members WHERE id_number = ? AND member_number != ?", (new_values['id_number'], member_number))
conflict = c.fetchone()
if conflict:
conn.close()
return f'''
<script>
alert("此身分/護照號碼已被會員 {conflict['member_number']} 使用,請更換後再試。");
window.history.back();
</script>
'''
c.execute('''
UPDATE members SET
family_name=?, given_name=?, sex=?, dob=?, email=?,
phone_h=?, phone_o=?, phone_m=?, country=?, address=?, id_number=?
WHERE member_number=?
''', (new_values['family_name'], new_values['given_name'], new_values['sex'], new_values['dob'],
new_values['email'], new_values['phone_h'], new_values['phone_o'], new_values['phone_m'],
new_values['country'], new_values['address'], new_values['id_number'], member_number))
conn.commit()
conn.close()
return redirect(url_for('member_info_page'))
# 刪除:統一路徑為 /delete_member/<member_number> 搭配 POST
@app.route('/delete_member/<member_number>', methods=['POST'])
def delete_member(member_number):
if 'user' not in session:
return redirect('/login.html')
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("DELETE FROM members WHERE member_number = ?", (member_number,))
conn.commit()
conn.close()
return redirect(url_for('member_info_page'))
@app.route('/session_check')
def session_check():
if 'user' not in session:
return '', 401
return '', 200
if __name__ == '__main__':
app.run(debug=True)
```
db_init.py
```
import sqlite3
from werkzeug.security import generate_password_hash
conn = sqlite3.connect('member.db')
c = conn.cursor()
c.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
account TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)
''')
c.execute('''
CREATE TABLE IF NOT EXISTS members (
id INTEGER PRIMARY KEY AUTOINCREMENT,
member_number TEXT UNIQUE NOT NULL,
family_name TEXT NOT NULL,
given_name TEXT NOT NULL,
sex TEXT NOT NULL,
dob TEXT,
email TEXT,
phone_h TEXT,
phone_o TEXT,
phone_m TEXT,
country TEXT,
address TEXT,
id_number TEXT
)
''')
# 只對非空值做唯一限制
c.execute('''
CREATE UNIQUE INDEX IF NOT EXISTS ux_members_id_number_notnull
ON members(id_number) WHERE id_number IS NOT NULL
''')
hashed_pw = generate_password_hash('!Claire0320')
c.execute('INSERT OR IGNORE INTO users (account, password) VALUES (?, ?)', ('Claire', hashed_pw))
conn.commit()
conn.close()
print("✅ 資料庫建立完成(含 id_number 唯一索引);預設帳號 Claire / !Claire0320")
```
insert_test_data.py
```
import sqlite3
conn = sqlite3.connect("member.db")
c = conn.cursor()
test_data = [
("M00001", "Shan", "Albert", "M", "1985-01-01", "a@example.com", "02-12345678", "03-87654321", "0912-345678", "Taiwan", "Taipei", "A123456789"),
("M00002", "Shan", "Claire", "F", "1986-01-01", "b@example.com", "02-11111111", "03-22222222", "0912-000000", "Taiwan", "Kaohsiung", "B223456789"),
("M00003", "Chen", "Janna", "F", "1987-01-01", "c@example.com", "02-33333333", "03-44444444", "0912-111111", "Taiwan", "Tainan", None),
("M00004", "Shan", "Kelly", "F", "1988-01-01", "d@example.com", "02-55555555", "03-66666666", "0912-222222", "Taiwan", "Taichung", "K123456789"),
("M00005", "Chen", "Kelly", "F", "1988-01-01", "e@example.com", "02-77777777", "03-88888888", "0912-333333", "Taiwan", "Taichung", None),
]
for row in test_data:
c.execute('''
INSERT INTO members (
member_number, family_name, given_name, sex, dob,
email, phone_h, phone_o, phone_m, country, address, id_number
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
''', row)
conn.commit()
conn.close()
print("✅ 測試資料新增完成(含 id_number)")
```
view_db.py
```
import sqlite3
DB_PATH = 'member.db'
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
c = conn.cursor()
print("📋 users 表:")
for row in c.execute("SELECT * FROM users"):
print(dict(row))
print("\n📋 members 表:")
for row in c.execute("SELECT * FROM members"):
print(dict(row))
conn.close()
```
homepage.html
```
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Home</title>
</head>
<body>
<p>User ID: {{ user }}</p>
<ul>
<li><a href="/member_Info.html">Member Query</a></li>
<li><a href="/member_Add.html">Add New Member</a></li>
</ul>
<form action="/logout" method="post"><button type="submit">Logout</button></form>
<script>window.addEventListener("pageshow", function () {
fetch('/session_check', { cache: 'no-store' }).then(res => {
if (!res.ok) window.location.href = '/login.html';
});
});</script>
</body>
</html>
```
login.html
```
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Login</title>
</head>
<body>
<form action="/login" method="post" autocomplete="off">
<label>Account:</label><input type="text" name="account" autocomplete="off"><br><br>
<label>Password:</label><input type="password" name="password" autocomplete="off"><br><br>
<button type="submit">Login</button>
</form>
<script>window.onload = () => document.querySelectorAll("input").forEach(i => i.value = '');</script>
{% if error %}
<script>alert("{{ error }}");</script>
{% endif %}
</body>
</html>
```
member_Add.html
```
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Member Add</title>
</head>
<body>
<p>User ID: {{ user }}</p>
<form action="/add_member" method="post" onsubmit="return confirm('請確認以下資料是否要送出');">
<label>Family Name:</label>
<input type="text" name="family_name" pattern="^[a-zA-Z]{2,30}$" required><br><br>
<label>Given Name:</label>
<input type="text" name="given_name" pattern="^[a-zA-Z]{2,30}$" required><br><br>
<label>Sex:</label>
<input type="text" name="sex" pattern="^(M|F)$" required><br><br>
<label>Date of Birth:</label>
<input type="text" name="date_of_birth"
pattern="^\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12]\d|3[01])$"><br><br>
<label>Email:</label>
<input type="text" name="email" pattern="^[\w.-]+@[\w.-]+\.[A-Za-z]{2,}$"><br><br>
<label>Phone H:</label>
<input type="text" name="phonenum_h" pattern="^[0-9-]{7,20}$"><br><br>
<label>Phone O:</label>
<input type="text" name="phonenum_o" pattern="^[0-9-]{7,20}$"><br><br>
<label>Phone M:</label>
<input type="text" name="phonenum_m" pattern="^[0-9-]{7,20}$"><br><br>
<label>Country:</label>
<input type="text" name="country" pattern="^[a-zA-Z0-9\u4e00-\u9fa5\s]{2,50}$"><br><br>
<label>Address:</label>
<input type="text" name="address" pattern="^[a-zA-Z0-9\u4e00-\u9fa5\s,#-]{2,100}$"><br><br>
<label>身分證/護照號碼 (ID/Passport):</label>
<input type="text" name="id_number" pattern="^[A-Z0-9]{6,12}$" required
placeholder="僅限大寫英數 6–12 碼"><br><br>
<button type="submit">Save</button>
</form>
{% if success %}
<script>alert("{{ success }}");</script>
{% endif %}
<br><button onclick="history.back()">Back</button>
</body>
</html>
```
member_Info.html
```
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Member Info</title>
</head>
<body>
<p>User ID: {{ user }}</p>
<form action="/search_member" method="post">
<label>Member Number:</label><input type="text" name="member_num"><br><br>
<label>Family Name:</label><input type="text" name="family_name"><br><br>
<label>Given Name:</label><input type="text" name="given_name"><br><br>
<button type="submit">Search</button>
</form>
<hr>
{% if results %}
<table border="1" cellpadding="5">
<tr><th>Member Number</th><th>Family Name</th><th>Given Name</th></tr>
{% for row in results %}
<tr>
<td><a href="{{ url_for('member_update', member_number=row.member_number) }}">{{ row.member_number }}</a></td>
<td>{{ row.family_name }}</td>
<td>{{ row.given_name }}</td>
</tr>
{% endfor %}
</table>
{% elif error %}
<p style="color: red;">{{ error }}</p>
{% endif %}
<br><button onclick="window.location.href='/homepage.html'">Back</button>
</body>
</html>
```
member_Update.html
```
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Update Member</title>
</head>
<body>
<p>User ID: {{ user }}</p>
<h3>Update Member Info</h3>
{% if member %}
<form action="/update_member" method="post" onsubmit="return confirm('確認更新此會員資料?');">
<p>Member Number: <strong>{{ member.member_number }}</strong></p>
<input type="hidden" name="member_number" value="{{ member.member_number }}">
<label>Family Name:</label>
<input type="text" name="family_name" value="{{ member.family_name }}" pattern="^[a-zA-Z]{2,30}$" required><br><br>
<label>Given Name:</label>
<input type="text" name="given_name" value="{{ member.given_name }}" pattern="^[a-zA-Z]{2,30}$" required><br><br>
<label>Sex (M/F):</label>
<input type="text" name="sex" value="{{ member.sex }}" pattern="^(M|F)$" required><br><br>
<label>Date of Birth:</label>
<input type="text" name="dob" value="{{ member.dob }}"
pattern="^\d{4}-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01])$"> <br><br>
<label>Email:</label>
<input type="text" name="email" value="{{ member.email }}" pattern="^[\\w.-]+@[\\w.-]+\\.[A-Za-z]{2,}$"><br><br>
<label>Phone Number (H):</label>
<input type="text" name="phone_h" value="{{ member.phone_h }}" pattern="^[0-9-]{7,20}$"><br><br>
<label>Phone Number (O):</label>
<input type="text" name="phone_o" value="{{ member.phone_o }}" pattern="^[0-9-]{7,20}$"><br><br>
<label>Phone Number (M):</label>
<input type="text" name="phone_m" value="{{ member.phone_m }}" pattern="^[0-9-]{7,20}$"><br><br>
<label>Country:</label>
<input type="text" name="country" value="{{ member.country }}" pattern="^[a-zA-Z0-9\\u4e00-\\u9fa5\\s]{2,50}$"><br><br>
<label>Address:</label>
<input type="text" name="address" value="{{ member.address }}" pattern="^[a-zA-Z0-9\\u4e00-\\u9fa5\\s,#-]{2,100}$"><br><br>
<label>身分證/護照號碼 (ID/Passport):</label>
<input type="text" name="id_number" value="{{ member.id_number }}" pattern="^[A-Z0-9]{6,12}$" required><br><br>
<button type="submit">Update</button>
</form>
<form action="/delete_member/{{ member.member_number }}" method="post" onsubmit="return confirm('確定要刪除會員?');">
<button type="submit" style="margin-top:10px;">Delete Member</button>
</form>
{% else %}
<p style="color:red;">查無此會員</p>
{% endif %}
<br>
<button onclick="window.location.href='/member_Info.html'">Back</button>
</body>
</html>
```
## 0826

[OU參考資料](https://learn.microsoft.com/zh-tw/windows-server/identity/ad-ds/plan/delegating-administration-of-account-ous-and-resource-ous)