def _error_page(message: str, sentry_capture: bool = False, log: str = None): """return a error page with a message. if sentry is available, tell user that they can report the problem.""" sentry_param = {} if sentry_capture and plugin_available("sentry"): sentry.captureException() sentry_param.update({ "event_id": g.sentry_event_id, "public_dsn": sentry.client.get_public_dsn('https') }) if log: logger.info(log) return render_template('common/error.html', message=message, **sentry_param)
def register_by_email_token_check(token: str) -> str: """检查邮件验证token有效性,并返回verification requestID""" with tracer.trace('verify_email_token'): rpc_result = Auth.verify_email_token(token=token) if not rpc_result.success: raise InvalidTokenError request = VerificationRequest.find_by_id(uuid.UUID(rpc_result.request_id)) if not request: logger.error( f"can not find related verification request of email token {token}" ) if request.status != VerificationRequest.STATUS_TKN_PASSED: request.set_status_token_passed() student_id = request.identifier if user_exist(student_id): logger.info( f"User {student_id} try to register again by email token. Request filtered." ) raise AlreadyRegisteredError return rpc_result.request_id
def __init__(self, campus: str, building: str, date: datetime.date, time: str): _, week, day = get_semester_date(date) self.date = f"{date.year}-{date.month}-{date.day}" resp = Entity.get_available_rooms(week, f"{day + 1}{time}", campus, building) logger.info( f"get available rooms for week={week}, session={day + 1}{time}, campus={campus}, building={building}" ) self.rooms: List[Room] = [] for r in resp: # 反馈占用计数 feedback_cnt = redis.get( f"{redis_prefix}:avail_room_occupy_fb:{week}:{day}:{time}:{r['code']}" ) self.rooms.append( Room.make(name=r['name'], room_id=r['code'], feedback_cnt=int(feedback_cnt.decode()) if feedback_cnt else 0))
def query(): """ All in one 搜索入口,可以查询学生、老师、教室,然后跳转到具体资源页面 正常情况应该是 post 方法,但是也兼容 get 防止意外情况,提高用户体验 埋点: - `query_resource_type`, 查询的资源类型: classroom, single_student, single_teacher, multiple_people, or not_exist. - `query_type`, 查询方式(姓名、学工号): by_name, by_id, other """ # if under maintenance, return to maintenance.html if app.config["MAINTENANCE"]: return render_template("maintenance.html") keyword = request.values.get('id') if not keyword or len(keyword) < 2: flash('请输入需要查询的姓名、学号、教工号或教室名称,长度不要小于2个字符') return redirect(url_for('main.main')) # 调用 api-server 搜索 with elasticapm.capture_span('rpc_search'): try: rpc_result = APIServer.search(keyword) except Exception as e: return handle_exception_with_error_page(e) # 不同类型渲染不同模板 if len(rpc_result.classrooms) >= 1: # 优先展示教室 # 我们在 kibana 中使用服务名过滤 apm 文档,所以 tag 不用增加服务名前缀 elasticapm.tag(query_resource_type='classroom') elasticapm.tag(query_type='by_name') if len(rpc_result.classrooms) > 1: # 多个教室选择 return render_template('query/multipleClassroomChoice.html', name=keyword, classrooms=rpc_result.classrooms) return redirect('/classroom/{}/{}'.format( rpc_result.classrooms[0].room_id_encoded, rpc_result.classrooms[0].semesters[-1])) elif len(rpc_result.students) == 1 and len( rpc_result.teachers) == 0: # 一个学生 elasticapm.tag(query_resource_type='single_student') if contains_chinese(keyword): elasticapm.tag(query_type='by_name') else: elasticapm.tag(query_type='by_id') if len(rpc_result.students[0].semesters) < 1: flash('没有可用学期') return redirect(url_for('main.main')) return redirect('/student/{}/{}'.format( rpc_result.students[0].student_id_encoded, rpc_result.students[0].semesters[-1])) elif len(rpc_result.teachers) == 1 and len( rpc_result.students) == 0: # 一个老师 elasticapm.tag(query_resource_type='single_teacher') if contains_chinese(keyword): elasticapm.tag(query_type='by_name') else: elasticapm.tag(query_type='by_id') if len(rpc_result.teachers[0].semesters) < 1: flash('没有可用学期') return redirect(url_for('main.main')) return redirect('/teacher/{}/{}'.format( rpc_result.teachers[0].teacher_id_encoded, rpc_result.teachers[0].semesters[-1])) elif len(rpc_result.teachers) >= 1 or len(rpc_result.students) >= 1: # multiple students, multiple teachers, or mix of both elasticapm.tag(query_resource_type='multiple_people') if contains_chinese(keyword): elasticapm.tag(query_type='by_name') else: elasticapm.tag(query_type='by_id') return render_template('query/peopleWithSameName.html', name=keyword, students=rpc_result.students, teachers=rpc_result.teachers) else: logger.info("No result for user search", {"keyword": request.values.get('id')}) elasticapm.tag(query_resource_type='not_exist') elasticapm.tag(query_type='other') flash('没有找到任何有关 {} 的信息,如果你认为这不应该发生,请联系我们。'.format( escape(request.values.get('id')))) return redirect(url_for('main.main'))
def email_verification(): """注册:邮箱验证""" if request.method == 'POST': # 设置密码表单提交 if not session.get(SESSION_EMAIL_VER_REQ_ID, None): return render_template("common/error.html", message=MSG_400) req = IdentityVerification.get_request_by_id( session[SESSION_EMAIL_VER_REQ_ID]) if not req: return render_template("common/error.html", message=MSG_TOKEN_INVALID) # 此处不是一定需要验证状态,但是为了保险还是判断一下 if req["status"] != ID_STATUS_TKN_PASSED: return render_template("common/error.html", message=MSG_TOKEN_INVALID) if any( map(lambda x: not request.form.get(x, None), ("password", "password2"))): # check if empty password flash(MSG_EMPTY_PASSWORD) return redirect(url_for("user.email_verification")) if request.form["password"] != request.form["password2"]: flash(MSG_PWD_DIFFERENT) return redirect(url_for("user.email_verification")) sid_orig = req['sid_orig'] # 密码强度检查 pwd_strength_report = zxcvbn(password=request.form["password"]) if pwd_strength_report['score'] < 2: SimplePassword.new(password=request.form["password"], sid_orig=sid_orig) flash(MSG_WEAK_PASSWORD) return redirect(url_for("user.email_verification")) try: User.add_user(sid_orig=sid_orig, password=request.form['password']) except ValueError: flash(MSG_ALREADY_REGISTERED) logger.info( f"User {sid_orig} try to register again by email token. Filtered when posting." ) return redirect(url_for("user.email_verification")) del session[SESSION_EMAIL_VER_REQ_ID] IdentityVerification.set_request_status(str(req["request_id"]), ID_STATUS_PASSWORD_SET) flash(MSG_REGISTER_SUCCESS) # 查询 api-server 获得学生基本信息 try: student = Entity.get_student(sid_orig) except Exception as e: return handle_exception_with_error_page(e) # 登录态写入 session session[SESSION_CURRENT_USER] = StudentSession( sid_orig=student.student_id, sid=student.student_id_encoded, name=student.name) return redirect(url_for("user.main")) else: # 设置密码页面 if not session.get(SESSION_EMAIL_VER_REQ_ID, None): if not request.args.get("token", None): return render_template("common/error.html", message=MSG_400) with tracer.trace('verify_email_token'): try: rpc_result = Auth.verify_email_token( token=request.args.get("token", None)) except Exception as e: return handle_exception_with_error_page(e) if rpc_result.success: session[SESSION_EMAIL_VER_REQ_ID] = rpc_result.request_id IdentityVerification.set_request_status( rpc_result.request_id, ID_STATUS_TKN_PASSED) req = IdentityVerification.get_request_by_id( rpc_result.request_id) student_id = req["sid_orig"] if User.exist(student_id): flash(MSG_ALREADY_REGISTERED) logger.info( f"User {student_id} try to register again by email token. Request filtered." ) return redirect(url_for("main.main")) return render_template('user/emailVerificationProceed.html') else: return render_template("common/error.html", message=MSG_TOKEN_INVALID) else: # have session return render_template('user/emailVerificationProceed.html')
def ics_download(calendar_token: str): """ iCalendar ics 文件下载 2019-8-25 改为预先缓存文件而非每次动态生成,降低 CPU 压力。如果一小时内两次访问则强刷缓存。 """ import time from everyclass.server import statsd if not is_valid_uuid(calendar_token): return 'invalid calendar token', 404 result = CalendarToken.find_calendar_token(token=calendar_token) if not result: return 'invalid calendar token', 404 CalendarToken.update_last_used_time(calendar_token) cal_dir = calendar_dir() cal_filename = f"{result['type']}_{result['identifier']}_{result['semester']}.ics" cal_full_path = os.path.join(cal_dir, cal_filename) # 有缓存、且缓存时间小于一天,且不用强刷缓存 if os.path.exists(cal_full_path) \ and time.time() - os.path.getmtime(cal_full_path) < SECONDS_IN_ONE_DAY \ and Redis.calendar_token_use_cache(calendar_token): logger.info("ics cache hit") statsd.increment("calendar.ics.cache.hit") return send_from_directory(cal_dir, cal_filename, as_attachment=True, mimetype='text/calendar') statsd.increment("calendar.ics.cache.miss") # 无缓存、或需要强刷缓存 with tracer.trace('rpc'): # 获得原始学号或教工号 if result['type'] == 'student': rpc_result = Entity.get_student_timetable(result['identifier'], result['semester']) else: # teacher rpc_result = Entity.get_teacher_timetable(result['identifier'], result['semester']) semester = Semester(result['semester']) cards: Dict[Tuple[int, int], List[Dict]] = defaultdict(list) for card in rpc_result.cards: cards[lesson_string_to_tuple(card.lesson)].append( dict(name=card.name, teacher=teacher_list_to_name_str(card.teachers), week=card.weeks, week_string=card.week_string, classroom=card.room, cid=card.card_id_encoded)) ics_generator.generate(name=rpc_result.name, cards=cards, semester=semester, filename=cal_filename) return send_from_directory(cal_dir, cal_filename, as_attachment=True, mimetype='text/calendar')
def _return_string(status_code, string, sentry_capture=False, log=None): if sentry_capture and plugin_available("sentry"): sentry.captureException() if log: logger.info(log) return string, status_code
def query(): """ All in one 搜索入口,可以查询学生、老师、教室,然后跳转到具体资源页面 正常情况应该是 post 方法,但是也兼容 get 防止意外情况,提高用户体验 埋点: - `query_resource_type`, 查询的资源类型: classroom, single_student, single_teacher, multiple_people, or not_exist. - `query_type`, 查询方式(姓名、学工号): by_name, by_id, other """ # if under maintenance, return to maintenance.html if app.config["MAINTENANCE"]: return render_template("maintenance.html") keyword = request.values.get('id') # todo 简拼积分制 if not keyword or len(keyword) < 2: flash('请输入需要查询的姓名、学号、教工号或教室名称,长度不要小于2个字符') return redirect(url_for('main.main')) # 调用 api-server 搜索 with tracer.trace('rpc_search'): try: rpc_result = entity_service.search(keyword) except Exception as e: return handle_exception_with_error_page(e) # 不同类型渲染不同模板 if len(rpc_result.classrooms) >= 1: # 优先展示教室 # 我们在 kibana 中使用服务名过滤 apm 文档,所以 tag 不用增加服务名前缀 tracer.current_root_span().set_tag("query_resource_type", "classroom") tracer.current_root_span().set_tag("query_type", "by_name") if len(rpc_result.classrooms) > 1: # 多个教室选择 return render_template('entity/multipleClassroomChoice.html', name=keyword, classrooms=rpc_result.classrooms) return redirect( url_for("query.get_classroom", url_rid=rpc_result.classrooms[0].room_id_encoded, url_semester=rpc_result.classrooms[0].semesters[-1] if rpc_result.classrooms[0].semesters else URL_EMPTY_SEMESTER)) elif len(rpc_result.students) == 1 and len(rpc_result.teachers) == 0: # 一个学生 tracer.current_root_span().set_tag("query_resource_type", "single_student") if contains_chinese(keyword): tracer.current_root_span().set_tag("query_type", "by_name") else: tracer.current_root_span().set_tag("query_type", "by_id") if len(rpc_result.students[0].semesters) < 1: flash('没有可用学期') return redirect(url_for('main.main')) return redirect( url_for("query.get_student", url_sid=rpc_result.students[0].student_id_encoded, url_semester=rpc_result.students[0].semesters[-1] if rpc_result.students[0].semesters else URL_EMPTY_SEMESTER)) elif len(rpc_result.teachers) == 1 and len(rpc_result.students) == 0: # 一个老师 tracer.current_root_span().set_tag("query_resource_type", "single_teacher") if contains_chinese(keyword): tracer.current_root_span().set_tag("query_type", "by_name") else: tracer.current_root_span().set_tag("query_type", "by_id") if len(rpc_result.teachers[0].semesters) < 1: flash('没有可用学期') return redirect(url_for('main.main')) return redirect( url_for("query.get_teacher", url_tid=rpc_result.teachers[0].teacher_id_encoded, url_semester=rpc_result.teachers[0].semesters[-1] if rpc_result.teachers[0].semesters else URL_EMPTY_SEMESTER)) elif len(rpc_result.teachers) >= 1 or len(rpc_result.students) >= 1: # multiple students, multiple teachers, or mix of both tracer.current_root_span().set_tag("query_resource_type", "multiple_people") if contains_chinese(keyword): tracer.current_root_span().set_tag("query_type", "by_name") else: tracer.current_root_span().set_tag("query_type", "by_id") students = [] student_filtered = False for student in rpc_result.students: if len(student.klass) >= 6 and student.klass[-4:].isdigit() and int(student.klass[-4:-2]) <= 12: # 过滤<13级的学生 student_filtered = True continue students.append(student) # todo 找校友功能 return render_template('entity/peopleWithSameName.html', name=keyword, students=students, teachers=rpc_result.teachers, student_filtered=student_filtered) else: logger.info("No result for user search", extra={"keyword": request.values.get('id')}) tracer.current_root_span().set_tag("query_resource_type", "not_exist") tracer.current_root_span().set_tag("query_type", "other") flash('没有找到任何有关 {} 的信息,如果你认为这不应该发生,请联系我们。'.format(escape(request.values.get('id')))) return redirect(url_for('main.main'))