video_monitor/app/views/UserView.py

582 lines
23 KiB
Python
Raw Normal View History

2026-08-30 22:22:11 +08:00
import time
from app.views.ViewsBase import *
from app.utils.Utils import buildPageLabels
from django.shortcuts import render, redirect
from django.contrib.auth import authenticate, login as auth_login, logout as auth_logout
from django.contrib.auth.models import Group, User
from app.utils.Credentials import redact_mapping
from app.utils.OSSystem import OSSystem
from io import BytesIO
from app.utils.LogUtils import LogUtils
from django.http import HttpResponse
import json
# 生成验证码start
import random
from PIL import Image, ImageDraw, ImageFont
def random_color(min_val=50, max_val=200):
"""生成随机RGB颜色"""
return (
random.randint(min_val, max_val),
random.randint(min_val, max_val),
random.randint(min_val, max_val)
)
def load_captcha_font(height):
"""跨平台字体加载优先Linux兼容字体"""
osSystem = OSSystem()
if osSystem.getSystemName() == "Windows":
font_paths = [
g_config.fontPath, # 项目内嵌字体
"C:\\Windows\\Fonts\\arial.ttf" # Windows
]
else:
font_paths = [
g_config.fontPath, # 项目内嵌字体
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" # Linux
]
for font_path in font_paths:
try:
if os.path.exists(font_path):
font_size = int(height * 0.7)
font = ImageFont.truetype(font_path, font_size)
return font,font_size
else:
raise Exception("file not exist")
except Exception as e:
g_logger.error("load_captcha_font() error,font_path=%s,e=%s"%(font_path,str(e)))
font_size = int(height * 2)
return ImageFont.load_default(),font_size # 保底方案
def generate_secure_captcha(length=4):
"""生成带干扰线的验证码图片"""
width = 120
height = 40
font,font_size = load_captcha_font(height)
image = Image.new('RGB', (width, height), (255, 255, 255))
draw = ImageDraw.Draw(image)
# 生成随机文本(排除易混淆字符)
chars = 'ABCDEFGHJKMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789'
captcha_text = ''.join(random.choices(chars, k=length))
# 绘制扭曲字符
x_offset = 10
for char in captcha_text:
angle = random.randint(-10, 10) # 随机旋转角度
char_img = Image.new('RGBA', (font_size, font_size), (0, 0, 0, 0))
char_draw = ImageDraw.Draw(char_img)
char_draw.text((0, 0), char, font=font, fill=random_color(0, 100))
rotated_char = char_img.rotate(angle, expand=True, resample=Image.BILINEAR)
image.paste(rotated_char, (x_offset, 5), rotated_char)
x_offset += rotated_char.width - random.randint(0, 8) # 随机间距
# 添加干扰线(核心防御)
for _ in range(4): # 干扰线数量
x1, y1 = random.randint(0, width), random.randint(0, height)
x2, y2 = random.randint(0, width), random.randint(0, height)
draw.line([x1, y1, x2, y2], fill=random_color(150, 220), width=random.choice([1, 2]))
# 添加噪点30个点
for _ in range(30):
x, y = random.randint(0, width), random.randint(0, height)
draw.point((x, y), fill=random_color(100, 200))
return captcha_text, image
# 生成验证码end
def index(request):
context = {}
return render(request, 'app/user/index.html', context)
def api_openIndex(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
data = []
pageData = {}
if request.method == 'GET':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
page = params.get('p', 1)
page_size = params.get('ps', 10)
try:
page = int(page)
except:
page = 1
try:
page_size = int(page_size)
if page_size < 1:
page_size = 1
except:
page_size = 10
skip = (page - 1) * page_size
sql_data = ("select id,username,email,is_active,is_superuser,is_staff,date_joined,last_login "
"from auth_user order by id desc limit %s,%s")
sql_data_num = "select count(id) as count from auth_user "
count = g_database.select(sql_data_num)
if len(count) > 0:
count = int(count[0]["count"])
data = g_database.select(sql_data, [skip, page_size])
else:
count = 0
# 格式化日期字段
for d in data:
user_roles = Group.objects.filter(user__id=d["id"]).values_list("name", flat=True)
d["role"] = next(iter(user_roles), "viewer")
if d.get("date_joined"):
try:
d["date_joined"] = d["date_joined"].strftime("%Y-%m-%d %H:%M:%S")
except:
pass
else:
d["date_joined"] = ""
if d.get("last_login"):
try:
d["last_login"] = d["last_login"].strftime("%Y-%m-%d %H:%M:%S")
except:
pass
else:
d["last_login"] = ""
page_num = int(count / page_size)
if count % page_size > 0:
page_num += 1
pageLabels = buildPageLabels(page=page, page_num=page_num, lang=f_parseRequestLang(request))
pageData = {
"page": page,
"page_size": page_size,
"page_num": page_num,
"count": count,
"pageLabels": pageLabels
}
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"data": data,
"pageData": pageData
}
return f_responseJson(res)
def api_openAdd(request):
__ret = False
__msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("UserView.openAdd() params:%s" % str(redact_mapping(params)))
try:
login_user = f_sessionReadUser(request)
if not login_user:
raise Exception(LANG_VIEWS_T(request, "msg_not_logged_in"))
username = params.get("username", "").strip()
email = params.get("email", "").strip()
password = params.get("password", "").strip()
is_active = params.get("is_active")
is_active = int(is_active)
role = (params.get("role") or "viewer").strip()
if role not in ("system_admin", "algorithm_admin", "operator", "viewer"):
role = "viewer"
if username == "":
raise Exception(LANG_VIEWS_T(request, "user_username_required"))
if email == "":
raise Exception(LANG_VIEWS_T(request, "user_email_required"))
if len(password) < 6 or len(password) > 16:
raise Exception(LANG_VIEWS_T(request, "user_password_length"))
if User.objects.filter(username=username).exists():
raise Exception(LANG_VIEWS_T(request, "user_username_exists"))
else:
now = datetime.now()
user = User()
user.username = username
user.set_password(password)
user.email = email
user.date_joined = now
user.is_superuser = 0 # 表单创建均为非超级管理员
user.is_staff = 1
user.is_active = is_active
user.save()
user.groups.set([Group.objects.get(name=role)])
if user.id > 0:
# 添加日志
lang = f_parseRequestLang(request)
LogUtils.add_user_log(login_user.get("id"), username, LogUtils.LOG_TYPE_ADD, lang=lang)
__ret = True
__msg = LANG_VIEWS_T(request, "msg_add_success")
else:
__msg = LANG_VIEWS_T(request, "msg_add_failed")
except Exception as e:
__msg = str(e)
else:
__msg = __check_msg
else:
__msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if __ret else 0,
"msg": __msg
}
g_logger.info("UserView.openAdd() res=%s" % str(res))
return f_responseJson(res)
def api_openEdit(request):
__ret = False
__msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
g_logger.info("UserView.openEdit() params:%s" % str(redact_mapping(params)))
try:
login_user = f_sessionReadUser(request)
if not login_user:
raise Exception(LANG_VIEWS_T(request, "msg_not_logged_in"))
user_id = params.get("id") # 被操作用户id
is_active = params.get("is_active")
username = params.get("username", "").strip()
email = params.get("email", "").strip()
new_password = params.get("new_password", "")
re_password = params.get("re_password", "")
user_id = int(user_id)
is_active = int(is_active)
role = (params.get("role") or "").strip()
if username == "":
raise Exception(LANG_VIEWS_T(request, "user_username_required"))
if email == "":
raise Exception(LANG_VIEWS_T(request, "user_email_required"))
if re_password == "" and new_password == "":
pass
# 未修改密码
else:
# 修改了密码
if new_password == "":
raise Exception(LANG_VIEWS_T(request, "user_new_password_required"))
if re_password == "":
raise Exception(LANG_VIEWS_T(request, "user_confirm_password_required"))
if new_password != re_password:
raise Exception(LANG_VIEWS_T(request, "user_password_mismatch"))
if len(new_password) < 6 or len(new_password) > 16:
raise Exception(LANG_VIEWS_T(request, "user_new_password_length"))
user = User.objects.filter(id=user_id).first()
if user:
# 验证要修改的用户名是否已经存在start
if user.username == username:
pass
# 用户名未做修改
else:
filter_username = g_database.select(
"select count(1) as count from auth_user where id!=%s and username=%s",
[user_id, username])
filter_username_count = int(filter_username[0]["count"])
if filter_username_count > 0:
raise Exception(LANG_VIEWS_T(request, "user_new_username_exists"))
user.username = username # 修改了用户名
# 验证要修改的用户名是否已经存在end
if re_password == "" and new_password == "":
pass
else:
user.set_password(new_password) # 修改了密码
user.email = email
user.is_active = is_active
user.save()
if role in ("system_admin", "algorithm_admin", "operator", "viewer"):
user.groups.set([Group.objects.get(name=role)])
# 添加日志
lang = f_parseRequestLang(request)
LogUtils.add_user_log(login_user.get("id"), username, LogUtils.LOG_TYPE_EDIT, lang=lang)
__ret = True
__msg = LANG_VIEWS_T(request, "msg_edit_success")
else:
raise Exception(LANG_VIEWS_T(request, "msg_data_not_exist"))
except Exception as e:
__msg = str(e)
else:
__msg = __check_msg
else:
__msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if __ret else 0,
"msg": __msg
}
g_logger.info("UserView.openEdit() res=%s" % str(res))
return f_responseJson(res)
def api_openDel(request):
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
if request.method == 'POST':
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parsePostParams(request)
try:
login_user = f_sessionReadUser(request)
if not login_user:
raise Exception(LANG_VIEWS_T(request, "msg_not_logged_in"))
user_id = int(params.get("id"))
if not user_id:
raise Exception(LANG_VIEWS_T(request, "user_request_params_invalid"))
login_user_id = int(login_user.get("id"))
if login_user_id == user_id:
raise Exception(LANG_VIEWS_T(request, "user_super_admin_no_delete_self"))
user = User.objects.filter(id=user_id)
if len(user) > 0:
user = user[0]
if user.is_superuser == 1:
raise Exception(LANG_VIEWS_T(request, "user_super_admin_no_delete"))
else:
if user.delete():
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = LANG_VIEWS_T(request, "msg_failed_to_delete")
else:
raise Exception(LANG_VIEWS_T(request, "msg_data_not_exist"))
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
g_logger.info("UserView.openDel() res=%s" % str(res))
return f_responseJson(res)
def api_openInfo(request):
"""获取单条用户详情"""
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
info = {}
if request.method == "GET":
__check_ret, __check_msg = f_checkRequestSafe(request)
if __check_ret:
params = f_parseGetParams(request)
user_id = params.get("id", "")
if not user_id:
msg = LANG_VIEWS_T(request, "user_id_required")
else:
try:
user_id = int(user_id)
user = User.objects.filter(id=user_id).first()
if user:
info = {
"id": user.id,
"username": user.username,
"email": user.email,
"is_active": user.is_active,
"is_superuser": user.is_superuser,
"is_staff": user.is_staff,
"date_joined": user.date_joined.strftime("%Y-%m-%d %H:%M:%S") if user.date_joined else "",
"last_login": user.last_login.strftime("%Y-%m-%d %H:%M:%S") if user.last_login else ""
}
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
msg = LANG_VIEWS_T(request, "user_not_exist")
except Exception as e:
msg = str(e)
else:
msg = __check_msg
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg,
"info": info
}
g_logger.info("UserView.openInfo() res=%s" % str(res))
return f_responseJson(res)
def api_openCaptcha(request):
"""生成验证码图片视图"""
# 生成验证码
text,image = generate_secure_captcha()
# 存储到session
cur_timestamp = int(time.time())
request.session[g_session_key_captcha] = {
"captcha_text": text,
"captcha_create_timestamp": cur_timestamp, # 创建秒级时间戳
}
# 创建内存流输出
stream = BytesIO()
image.save(stream, 'PNG')
return HttpResponse(stream.getvalue(), content_type='image/png')
def login(request):
context = {
"projectVersion": PROJECT_VERSION,
"projectFlag": PROJECT_FLAG
}
if request.method == 'POST':
ret = False
msg = LANG_VIEWS_T(request, "msg_unknown_error")
params = f_parsePostParams(request)
username = (params.get("username") or params.get("username_s") or "").strip()
password = (params.get("password") or params.get("password_s") or "").strip()
captcha = params.get("captcha", None)
try:
if g_config.isEnableLoginCaptcha:
if not captcha:
raise Exception(LANG_VIEWS_T(request, "user_captcha_missing"))
# 开启了登录验证码功能
session_captcha = request.session.get(g_session_key_captcha, None)
if not session_captcha:
raise Exception(LANG_VIEWS_T(request, "user_captcha_not_found"))
if session_captcha:
captcha_text = session_captcha.get("captcha_text", "")
captcha_create_timestamp = session_captcha.get("captcha_create_timestamp", 0)
cur_timestamp = int(time.time())
# 验证码过期判断
if (cur_timestamp - captcha_create_timestamp) > 300:
raise Exception(LANG_VIEWS_T(request, "user_captcha_expired"))
# 验证码相同判断
if captcha_text != captcha:
raise Exception(LANG_VIEWS_T(request, "user_captcha_incorrect"))
if username and password:
user = User.objects.filter(username=username).first()
if user:
if user.is_active:
authenticated_user = authenticate(request, username=username, password=password)
if authenticated_user is not None:
auth_login(request, authenticated_user)
user.first_name = "cec=0"
user.last_login = datetime.now()
user.save()
# 保留兼容旧模板的数据;鉴权和授权以 request.user 为准。
request.session[g_session_key_user] = {
"id": user.id,
"username": username,
"email": user.email,
"is_superuser": user.is_superuser,
"is_active": user.is_active,
"is_staff": user.is_staff,
"log_debug": 1 if g_config.logDebug else 0,
}
request.session.pop(g_session_key_captcha, None)
# 记录登录日志
LogUtils.add_log(
user_id=user.id,
log_type=LogUtils.LOG_TYPE_LOGIN,
content=f"用户登录[{username}]",
state=LogUtils.STATE_SUCCESS
)
ret = True
msg = LANG_VIEWS_T(request, "user_login_success")
else:
continuous_error_count = 0
try:
vals = user.first_name.split(",")
for val in vals:
array = val.split("=")
if len(array) == 2:
if array[0] == "cec":
continuous_error_count = int(array[1])
except:
pass
continuous_error_count += 1
if continuous_error_count > 6:
is_active = False
msg = LANG_VIEWS_T(request, "user_password_error_lock") % continuous_error_count
else:
is_active = True
msg = LANG_VIEWS_T(request, "user_password_error_count") % continuous_error_count
user.is_active = is_active
user.first_name = "cec=%d"%continuous_error_count
user.save()
else:
msg = LANG_VIEWS_T(request, "user_account_locked")
else:
msg = LANG_VIEWS_T(request, "user_not_registered")
else:
msg = LANG_VIEWS_T(request, "msg_invalid_parameter")
except Exception as e:
msg = str(e)
res = {
"code": 1000 if ret else 0,
"msg": msg
}
return f_responseJson(res)
else:
context["isEnableLoginCaptcha"] = 1 if g_config.isEnableLoginCaptcha else 0
return render(request, 'app/user/login.html', context)
def logout(request):
# 记录退出登录日志
if request.session.has_key(g_session_key_user):
user_info = request.session.get(g_session_key_user)
user_id = user_info.get('id', 0)
username = user_info.get('username', '未知用户')
# 记录日志
if user_id:
LogUtils.add_log(
user_id=user_id,
log_type=LogUtils.LOG_TYPE_LOGOUT,
content=f"用户退出[{username}]",
state=LogUtils.STATE_SUCCESS
)
del request.session[g_session_key_user]
if request.session.has_key(g_session_key_captcha):
del request.session[g_session_key_captcha]
auth_logout(request)
return redirect("/login")