心得小記:
我從學術界成功轉職進入業界後,這是我成為雲端系統工程師後直屬主管交付給我的第一項任務。
撰寫目的
透過自動化根據規則「發送通知信」並「停權或移除使用者帳戶」,避免手動操作所帶來的風險。
功能要求
- 對於「超過一年未登入」的使用者帳戶
- 發送通知信
- 對於「超過一年又一個月未登入」的使用者帳戶
- 移除帳戶:若「上一次登入時間(last_login)」和「帳戶被創建日期(date_joined)」相同
- 停權帳戶:若「上一次登入時間(last_login)」和「帳戶被創建日期(date_joined)」不同
原始程式碼
import os, sys, time, socket, smtplib
import logging, logging.config
# Database: sqlite
import sqlite3
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
from email.header import Header
from datetime import datetime
from dateutil.relativedelta import relativedelta
from dateutil import parser
# Email Content
email_subject = 'Notification Email'
body_msg = 'It has been more than a year since you last logged into your account.'
file_path = './account_info.txt'
# Database: sqlite
db_path = './mysite/'
db_name = 'db.sqlite3'
# Time Settings
today = datetime.now()
one_year_ago = today - relativedelta(years=1)
one_year_and_one_month_ago = today - relativedelta(years=1, month=1)
#def get_current_time():
# now = datetime.now()
# dt_date = now.strftime("%Y-%m-%d")
# dt_time = now.strftime("%H:%M:%S")
# return dt_date, dt_time
def get_account_info():
con = sqlite3.connect(db_path + db_name)
cur = con.cursor()
res = cur.execute("SELECT username, email, date_joined, last_login, is_active FROM users_customuser")
#print(res.fetchall())
account_info_list = list(res.fetchall())
with open(file_path, 'w') as f:
f.write(str(account_info_list))
return account_info_list
def send_email_rules(account_info_list):
acc_email_list = []
for accinfo in account_info_list:
acc_username = accinfo[0]
acc_email = accinfo[1]
acc_date_joined = parser.parse(accinfo[2])
acc_last_login = parser.parse(accinfo[3])
acc_is_active = accinfo[4]
if acc_last_login < one_year_ago:
# Over 1 year
print('Over 1 year not login !', acc_username, acc_last_login, acc_email)
if acc_email != '':
acc_email_list.append(acc_email)
else:
pass
else:
# Under 1 year
print('Under 1 year:', acc_username, acc_last_login)
send_email(email_subject, body_msg, acc_email_list, file_path=file_path)
def action_remove_account(username, email, is_active):
con = sqlite3.connect(db_path + db_name)
cur = con.cursor()
cur.execute("DELETE FROM users_customuser WHERE username = '{}' AND email = '{}';".format(username, email))
con.commit()
def action_inactivate_account(username, email, is_active):
con = sqlite3.connect(db_path + db_name)
cur = con.cursor()
cur.execute("UPDATE users_customuser SET is_active = 0 WHERE username = '{}' AND email = '{}';".format(username, email))
con.commit()
def manage_account_rules(account_info_list):
for accinfo in account_info_list:
acc_username = accinfo[0]
acc_email = accinfo[1]
acc_date_joined = parser.parse(accinfo[2])
acc_last_login = parser.parse(accinfo[3])
acc_is_active = accinfo[4]
if acc_last_login < one_year_and_one_month_ago:
# Over 1 year and 1 month
print('Over 1 year and 1 month not login !', acc_username, acc_last_login, acc_email)
if acc_date_joined == acc_last_login:
print(acc_username, acc_date_joined, acc_last_login, '--------- DEL ACCOUNT')
action_remove_account(acc_username, acc_email, acc_is_active)
else:
print(acc_username, acc_date_joined, acc_last_login, 'INACTIVATE')
action_inactivate_account(acc_username, acc_email, acc_is_active)
else:
# Under 1 year and 1 month
pass
def send_email(email_subject, body_msg, acc_email_list, file_path=None):
RECEIVERS = acc_email_list
#...
if __name__ == '__main__':
account_info_list = get_account_info()
send_email_rules(account_info_list)
manage_account_rules(account_info_list)
改善建議與理由
- 請將程式碼「模組化」👉👉👉 超級重要!
- 將一份程式碼
SendEmail.py
,拆分為SendEmail.py
和ManageAccount.py
,使得SendEmail.py
可被ManageAccount.py
(主程式碼)引入使用。
- 將一份程式碼
- 不需要
get_account_info()
- 將
get_account_info()
直接寫進manage_account_rules()
即可。 - 而且不要把 DB 內的資料全抓出來,即使用
find({})
,因為當大量 Query Request 的時候會極度耗時、沒效率。
- 將
- 寄送通知信時,請一封封分開寄,不要用 email list。
- 因為使用 email list 寄送,表示其他收件者也會看到這封通知信寄給了誰,易有機敏資料洩漏的疑慮。
- 請將以下程式碼合併:
get_account_info()
的collection.find({})
send_email_rules()
的Over 1 year 區塊
manage_account_rules()
的Over 1 year and 1 month 區塊(Inactivate/Remove Account)
- 執行 DB 的 delete 或 update ... 操作時,必須考慮 collections 之間的關聯性。
關聯性低
或依賴影響程度低
的必須優先處理,避免 delete 或 update 時出現 Error。
DB 內的 collections,包括 authcustomuser、authcustomuser_group 和 authtoken。
DB schema 如下:
authcustomuser
"_id" : ObjectId()
"username" : string
"is_active" : boolean
"last_login" : ISODate()
"date_joined" : ISODate()
"email" : string
authcustomuser_groups
"_id" : ObjectId()
"customuser_id" : ObjectId()
"group_id" : integer
authtoken_token
"_id" : ObjectId()
"key" : string
"user_id" : ObjectId()
PS. authcustomuser._id == authcustomuser_groups.customuser_id == authtoken_token.user_id
結論:
* authcustomuser_group 和 authtoken 必須先刪
* authcustomuser 必須最後才刪,因為該 collection 被兩個 collections 所依賴。