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")