Ejemplo n.º 1
0
    def search(cls, keyword: str) -> SearchResult:
        """在 API Server 上搜索

        :param keyword: 需要搜索的关键词
        :return: 搜索结果列表
        """
        keyword = keyword.replace("/", "")

        resp = HttpRpc.call(
            method="GET",
            url='{}/search/query?key={}&page_size={}'.format(
                app.config['API_SERVER_BASE_URL'], keyword, 100),
            retry=True,
            headers={'X-Auth-Token': get_config().API_SERVER_TOKEN})
        if resp["status"] != "success":
            raise RpcException('API Server returns non-success status')
        page_num = resp['info']['page_num']
        search_result = SearchResult.make(resp)

        # 多页结果
        if page_num > 1:
            for page_index in range(2, page_num + 1):
                resp = HttpRpc.call(
                    method="GET",
                    url='{}/search/query?key={}&page_size={}&page_index={}'.
                    format(app.config['API_SERVER_BASE_URL'], keyword, 100,
                           page_index),
                    retry=True,
                    headers={'X-Auth-Token': get_config().API_SERVER_TOKEN})
                if resp["status"] != "success":
                    raise RpcException('API Server returns non-success status')
                search_result.append(resp)

        return search_result
Ejemplo n.º 2
0
def _get_datetime(week: int, day: int, time: Tuple[int, int],
                  semester: Tuple[int, int, int]) -> datetime:
    """
    根据学期、周次、时间,生成 `datetime` 类型的时间

    :param week: 周次
    :param day: 星期
    :param time: 时间tuple(时,分)
    :param semester: 学期
    :return: datetime 类型的时间
    """
    config = get_config()
    tz = pytz.timezone("Asia/Shanghai")
    dt = datetime(*(config.AVAILABLE_SEMESTERS[semester]['start'] + time),
                  tzinfo=tz)  # noqa: T484
    dt += timedelta(days=(week - 1) * 7 + (day - 1))  # 调整到当前周

    if 'adjustments' in config.AVAILABLE_SEMESTERS[semester]:
        ymd = (dt.year, dt.month, dt.day)
        adjustments = config.AVAILABLE_SEMESTERS[semester]['adjustments']
        if ymd in adjustments:
            if adjustments[ymd]['to']:
                # 调课
                dt = dt.replace(year=adjustments[ymd]['to'][0],
                                month=adjustments[ymd]['to'][1],
                                day=adjustments[ymd]['to'][2])
            else:
                # 冲掉的课年份设置为1984,返回之后被抹去
                dt = dt.replace(year=1984)

    return dt
Ejemplo n.º 3
0
 def get_level(cls, student_id: str) -> int:
     with pg_conn_context() as conn, conn.cursor() as cursor:
         select_query = "SELECT level FROM privacy_settings WHERE student_id=%s"
         cursor.execute(select_query, (student_id, ))
         result = cursor.fetchone()
     return result[0] if result is not None else get_config(
     ).DEFAULT_PRIVACY_LEVEL
Ejemplo n.º 4
0
 def get_level(cls, sid_orig: str) -> int:
     """获得学生的隐私级别。0为公开,1为实名互访,2为自己可见。默认为配置文件中定义的 DEFAULT_PRIVACY_LEVEL"""
     db = get_mongodb()
     doc = mongo_with_retry(db[cls.collection_name].find_one,
                            {"sid_orig": sid_orig},
                            num_retries=1)
     return doc["level"] if doc else get_config().DEFAULT_PRIVACY_LEVEL
Ejemplo n.º 5
0
def plugin_available(plugin_name: str) -> bool:
    """
    check if a plugin (Sentry, apm, logstash) is available in the current environment.
    :return True if available else False
    """
    config = get_config()
    mode = os.environ.get("MODE", None)
    if mode:
        return mode.lower() in getattr(
            config, "{}_AVAILABLE_IN".format(plugin_name).upper())
    else:
        raise EnvironmentError("MODE not in environment variables")
Ejemplo n.º 6
0
def __get_datetime(week, day, time, semester):
    """
    输入周次,星期、时间tuple(时,分),输出datetime类型的时间

    :param week: 周次
    :param day: 星期
    :param time: 时间tuple(时,分)
    :param semester: 学期
    :return: datetime 类型的时间
    """
    return datetime(*get_config().AVAILABLE_SEMESTERS.get(semester)['start'],
                    *time,
                    tzinfo=pytz.timezone("Asia/Shanghai")) + timedelta(
                        days=(int(week) - 1) * 7 + (int(day) - 1))
Ejemplo n.º 7
0
def enter_maintenance():
    config = get_config()
    auth = request.authorization
    if auth \
            and auth.username in config.MAINTENANCE_CREDENTIALS \
            and config.MAINTENANCE_CREDENTIALS[auth.username] == auth.password:
        open(config.MAINTENANCE_FILE, "w+").close()  # maintenance file
        open(os.path.join(os.getcwd(), 'reload'), "w+").close()  # uwsgi reload
        return 'success'
    else:
        return Response(
            'Could not verify your access level for that URL.\n'
            'You have to login with proper credentials', 401,
            {'WWW-Authenticate': 'Basic realm="Login Required"'})
def encrypt(resource_type: str, data: str, encryption_key: str = None) -> Text:
    """
    加密资源标识符

    :param resource_type: student、teacher、klass、room
    :param data: 资源标识符
    :param encryption_key: 加密使用的 key
    :return: 加密后的资源标识符
    """
    if resource_type not in ('student', 'teacher', 'klass', 'room'):
        raise ValueError("resource_type not valid")
    if not encryption_key:
        encryption_key = get_config().RESOURCE_IDENTIFIER_ENCRYPTION_KEY

    return _aes_encrypt(encryption_key, "%s;%s" % (resource_type, data))
Ejemplo n.º 9
0
    def get_student(cls, student_id: str):
        """
        根据学号获得学生课表

        :param student_id: 学号
        :return:
        """
        resp = HttpRpc.call(
            method="GET",
            url='{}/student/{}'.format(app.config['API_SERVER_BASE_URL'],
                                       student_id),
            retry=True,
            headers={'X-Auth-Token': get_config().API_SERVER_TOKEN})
        if resp["status"] != "success":
            raise RpcException('API Server returns non-success status')
        search_result = StudentResult.make(resp)
        return search_result
Ejemplo n.º 10
0
 def get_classroom_timetable(cls, semester: str, room_id: str):
     """
     根据学期和教室ID获得教室课表
     :param semester: 学期,如 2018-2019-1
     :param room_id: 教室ID
     :return:
     """
     resp = HttpRpc.call(
         method="GET",
         url='{}/room/{}/timetable/{}'.format(
             app.config['API_SERVER_BASE_URL'], room_id, semester),
         retry=True,
         headers={'X-Auth-Token': get_config().API_SERVER_TOKEN})
     if resp["status"] != "success":
         raise RpcException('API Server returns non-success status')
     search_result = ClassroomTimetableResult.make(resp)
     return search_result
Ejemplo n.º 11
0
 def get_card(cls, semester: str, card_id: str) -> CardResult:
     """
     根据学期和card ID获得card
     :param semester: 学期,如 2018-2019-1
     :param card_id: card ID
     :return:
     """
     resp = HttpRpc.call(
         method="GET",
         url='{}/card/{}/timetable/{}'.format(
             app.config['API_SERVER_BASE_URL'], card_id, semester),
         retry=True,
         headers={'X-Auth-Token': get_config().API_SERVER_TOKEN})
     if resp["status"] != "success":
         raise RpcException('API Server returns non-success status')
     search_result = CardResult.make(resp)
     return search_result
def decrypt(data: str, encryption_key: str = None, resource_type: str = None):
    """
    解密资源标识符

    :param data: 加密后的字符串
    :param encryption_key: 可选的 key
    :param resource_type: 验证资源类型(student、teacher、klass、room)
    :return:
    """
    if not encryption_key:
        encryption_key = get_config().RESOURCE_IDENTIFIER_ENCRYPTION_KEY

    data = _aes_decrypt(encryption_key, data)

    group = re.match(r'^(student|teacher|klass|room);([\s\S]+)$',
                     data)  # 通过正则校验确定数据的正确性
    if group is None:
        raise ValueError('Decrypted data is invalid: %s' % data)
    else:
        if resource_type and group.group(1) != resource_type:
            raise ValueError('Resource type not correspond')
        return group.group(1), group.group(2)
Ejemplo n.º 13
0
    def get():
        """
        获取当前学期。进入此模块前必须保证 session 内有 stu_id。
        当 url 中没有显式表明 semester 时,不设置 session,而是在这里设置默认值。
        """
        from everyclass.server.exceptions import IllegalSemesterException
        from everyclass.server.config import get_config
        config = get_config()

        my_available_semesters = get_my_semesters(session.get('stu_id'))[0]
        if config.DEBUG:
            print('[model.Semester.get()] my_available_semesters:',
                  my_available_semesters)

        # 如果 session 中包含学期信息且有效
        if session.get('semester', None) and session.get(
                'semester', None) in my_available_semesters:
            if config.DEBUG:
                print('[model.Semester.get()]have valid session')
            return Semester(session['semester'])

        # 如果没有 session或session无效
        else:
            if config.DEBUG:
                print('[model.Semester.get()] no session or invalid session')
            # 选择对本人有效的最后一个学期
            if my_available_semesters:
                if config.DEBUG:
                    print(
                        '[model.Semester.get()] choose last available semester'
                    )
                return my_available_semesters[-1]

            # 如果本人没有一个有效学期,则引出IllegalSemesterException
            else:
                raise IllegalSemesterException(
                    'No any available semester for this student')
Ejemplo n.º 14
0
def batch_generate():
    """生成当前学期所有学生的 ics 文件,每次更新当前学期数据后使用"""
    from everyclass.server import create_app
    from everyclass.server.db.dao import get_all_students, get_classes_for_student
    from everyclass.server.db.model import Semester

    config = get_config()
    now_semester = Semester(config.DEFAULT_SEMESTER)
    now_semester_str = str(now_semester)

    with create_app(offline=True).app_context():
        students = get_all_students()
        print("Total {} students".format(len(students)))
        for each in students:
            if now_semester_str in each[2]:
                print("Generate .ics for [{}]{}...".format(each[0], each[1]))
                student_classes = get_classes_for_student(
                    each[0], now_semester)
                generate(student_id=each[0],
                         student_name=each[1],
                         student_classes=student_classes,
                         semester_string=now_semester.to_str(simplify=True),
                         semester=now_semester.to_tuple())
        print("Done.")
Ejemplo n.º 15
0
def get_connection() -> database.Database:
    """在连接池中获得连接"""
    if not has_app_context():
        config = get_config()
        return MongoClient(**config.MONGODB).get_database(config.MONGODB_DB)
    return current_app.mongo.get_database(current_app.config['MONGODB_DB'])
Ejemplo n.º 16
0
def create_app() -> Flask:
    """创建 flask app"""
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.consts import MSG_INTERNAL_ERROR
    from everyclass.server.utils import plugin_available

    app = Flask(__name__,
                static_folder='../../frontend/dist',
                static_url_path='',
                template_folder="../../frontend/templates")

    # load app config
    from everyclass.server.config import get_config
    _config = get_config()
    app.config.from_object(_config)  # noqa: T484
    """
    每课统一日志机制


    规则如下:
    - DEBUG 模式下会输出 DEBUG 等级的日志,否则输出 INFO 及以上等级的日志
    - 日志为 JSON 格式,会被节点上的 agent 采集并发送到 datadog,方便结合 metrics 和 APM 数据分析
    - WARNING 以上级别的输出到 Sentry 做错误聚合


    日志等级:
    critical – for errors that lead to termination
    error – for errors that occur, but are handled
    warning – for exceptional circumstances that might not be errors
    notice – for non-error messages you usually want to see
    info – for messages you usually don’t want to see
    debug – for debug messages


    Sentry:
    https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    - stack 默认是 False
    """
    if app.config['DEBUG']:
        logging.getLogger().setLevel(logging.DEBUG)
    else:
        logging.getLogger().setLevel(logging.INFO)

    # CDN
    CDN(app)

    # moment
    Moment(app)

    # encrypted session
    app.session_interface = EncryptedSessionInterface()

    # 导入并注册 blueprints
    from everyclass.server.calendar.views import cal_blueprint
    from everyclass.server.query import query_blueprint
    from everyclass.server.views import main_blueprint as main_blueprint
    from everyclass.server.user.views import user_bp
    from everyclass.server.course_review.views import cr_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(user_bp, url_prefix='/user')

    # 初始化 RPC 模块
    from everyclass.server.utils.resource_identifier_encrypt import encrypt
    from everyclass.rpc import init as init_rpc
    from everyclass.rpc.entity import Entity
    from everyclass.rpc.auth import Auth
    init_rpc(resource_id_encrypt_function=encrypt
             )  # 为 everyclass.rpc 模块注入 encrypt 函数
    if 'API_SERVER_BASE_URL' in app.config:
        Entity.set_base_url(app.config['API_SERVER_BASE_URL'])
    if 'API_SERVER_TOKEN' in app.config:
        Entity.set_request_token(app.config['API_SERVER_TOKEN'])
    if 'AUTH_BASE_URL' in app.config:
        Auth.set_base_url(app.config['AUTH_BASE_URL'])

    # course review feature gating
    if app.config['FEATURE_GATING']['course_review']:
        app.register_blueprint(cr_blueprint, url_prefix='/course_review')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 APM 标识用户"""
        from everyclass.server.consts import SESSION_CURRENT_USER

        if not session.get('user_id', None) and request.endpoint not in (
                "main.health_check", "static"):
            logger.info(
                f"Give a new user ID for new user. endpoint: {request.endpoint}"
            )
            session['user_id'] = new_user_id_sequence()
        if session.get('user_id', None):
            tracer.current_root_span().set_tag("user_id",
                                               session['user_id'])  # 唯一用户 ID
        if session.get(SESSION_CURRENT_USER, None):
            tracer.current_root_span().set_tag(
                "username", session[SESSION_CURRENT_USER])  # 学号

    @app.before_request
    def log_request():
        """日志中记录请求"""
        logger.info(f'Request received: {request.method} {request.path}')

    @app.before_request
    def delete_old_session():
        """删除旧的客户端 session(长度过长导致无法在 mongodb 中建立索引)"""
        if request.cookies.get("session") and len(
                request.cookies.get("session")) > 50:
            session.clear()
            return redirect(request.url)

    @app.after_request
    def response_minify(response):
        """用 htmlmin 压缩 HTML,减轻带宽压力"""
        if app.config[
                'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8':
            response.set_data(minify(response.get_data(as_text=True)))
        return response

    @app.template_filter('versioned')
    def version_filter(filename):
        """
        模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css'

        :param filename: 文件名
        :return: 新的文件名
        """
        if app.config['STATIC_VERSIONED']:
            if filename[:4] == 'css/':
                new_filename = app.config['STATIC_MANIFEST'][filename[4:]]
                return 'css/' + new_filename
            elif filename[:3] == 'js/':
                new_filename = app.config['STATIC_MANIFEST'][filename[3:]]
                return new_filename
            else:
                return app.config['STATIC_MANIFEST'][filename]
        return filename

    @app.errorhandler(500)
    def internal_server_error(error):
        if plugin_available("sentry"):
            return render_template(
                'common/error.html',
                message=MSG_INTERNAL_ERROR,
                event_id=g.sentry_event_id,
                public_dsn=sentry.client.get_public_dsn('https'))
        return "<h4>500 Error: {}</h4><br>You are seeing this page because Sentry is not available.".format(
            repr(error))

    global __app
    __app = app

    return app
Ejemplo n.º 17
0
 def test_import_config(self):
     from everyclass.server.config import get_config
     config = get_config()
     self.assertTrue(config)
Ejemplo n.º 18
0
def create_app() -> Flask:
    """创建 flask app"""
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.utils.logbook_logstash.formatter import LOG_FORMAT_STRING
    from everyclass.server.consts import MSG_INTERNAL_ERROR
    from everyclass.server.utils import plugin_available

    print("Creating app...")

    app = Flask(__name__,
                static_folder='../../frontend/dist',
                static_url_path='',
                template_folder="../../frontend/templates")

    # load app config
    from everyclass.server.config import get_config
    _config = get_config()
    app.config.from_object(_config)  # noqa: T484
    """
    每课统一日志机制


    规则如下:
    - WARNING 以下 log 输出到 stdout
    - WARNING 以上输出到 stderr
    - DEBUG 以上日志以 json 形式通过 TCP 输出到 Logstash,然后发送到日志中心
    - WARNING 以上级别的输出到 Sentry


    日志等级:
    critical – for errors that lead to termination
    error – for errors that occur, but are handled
    warning – for exceptional circumstances that might not be errors
    notice – for non-error messages you usually want to see
    info – for messages you usually don’t want to see
    debug – for debug messages


    Sentry:
    https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    - stack 默认是 False
    """
    if app.config['CONFIG_NAME'] in app.config['DEBUG_LOG_AVAILABLE_IN']:
        stdout_handler = logbook.StreamHandler(
            stream=sys.stdout, bubble=True, filter=lambda r, h: r.level < 13)
    else:
        # ignore debug when not in debug
        stdout_handler = logbook.StreamHandler(
            stream=sys.stdout,
            bubble=True,
            filter=lambda r, h: 10 < r.level < 13)
    stdout_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stdout_handler)

    stderr_handler = logbook.StreamHandler(stream=sys.stderr,
                                           bubble=True,
                                           level='WARNING')
    stderr_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stderr_handler)

    # CDN
    CDN(app)

    # moment
    Moment(app)

    # 导入并注册 blueprints
    from everyclass.server.calendar.views import cal_blueprint
    from everyclass.server.query import query_blueprint
    from everyclass.server.views import main_blueprint as main_blueprint
    from everyclass.server.user.views import user_bp
    from everyclass.server.course_review.views import cr_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(user_bp, url_prefix='/user')

    # course review feature gating
    if app.config['FEATURE_GATING']['course_review']:
        app.register_blueprint(cr_blueprint, url_prefix='/course_review')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 Elastic APM 记录用户请求"""
        if not session.get('user_id',
                           None) and request.endpoint != "main.health_check":
            session['user_id'] = new_user_id_sequence()

    @app.before_request
    def delete_old_session():
        """删除旧的客户端 session(长度过长导致无法在 mongodb 中建立索引)"""
        if request.cookies.get("session") and len(
                request.cookies.get("session")) > 50:
            session.clear()
            return redirect(request.url)

    @app.after_request
    def response_minify(response):
        """用 htmlmin 压缩 HTML,减轻带宽压力"""
        if app.config[
                'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8':
            response.set_data(minify(response.get_data(as_text=True)))
        return response

    @app.template_filter('versioned')
    def version_filter(filename):
        """
        模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css'

        :param filename: 文件名
        :return: 新的文件名
        """
        if app.config['STATIC_VERSIONED']:
            if filename[:4] == 'css/':
                new_filename = app.config['STATIC_MANIFEST'][filename[4:]]
                return 'css/' + new_filename
            elif filename[:3] == 'js/':
                new_filename = app.config['STATIC_MANIFEST'][filename[3:]]
                return new_filename
            else:
                return app.config['STATIC_MANIFEST'][filename]
        return filename

    @app.errorhandler(500)
    def internal_server_error(error):
        if plugin_available("sentry"):
            return render_template(
                'common/error.html',
                message=MSG_INTERNAL_ERROR,
                event_id=g.sentry_event_id,
                public_dsn=sentry.client.get_public_dsn('https'))
        return "<h4>500 Error: {}</h4><br>You are seeing this page because Sentry is not available.".format(
            repr(error))

    global __app
    __app = app

    return app
Ejemplo n.º 19
0
def create_app(offline=False) -> Flask:
    """创建 flask app
    @param offline: 如果设置为 `True`,则为离线模式。此模式下不会连接到 Sentry 和 ElasticAPM
    """
    app = Flask(__name__,
                static_folder='../../frontend/dist',
                static_url_path='',
                template_folder="../../frontend/templates")

    # load app config
    from everyclass.server.config import get_config
    _config = get_config()
    app.config.from_object(_config)

    # CDN
    CDN(app)

    # logbook handlers
    # 规则如下:
    # - 全部输出到 stdout(本地开发调试、服务器端文件日志)
    # - Elastic APM 或者 LogStash(日志中心)
    # - WARNING 以上级别的输出到 Sentry
    #
    # 日志等级:
    # critical – for errors that lead to termination
    # error – for errors that occur, but are handled
    # warning – for exceptional circumstances that might not be errors
    # notice – for non-error messages you usually want to see
    # info – for messages you usually don’t want to see
    # debug – for debug messages
    #
    # Elastic APM:
    # log.info("Nothing to see here", stack=False)
    # stack 默认是 True,设置为 False 将不会向 Elastic APM 发送 stack trace
    # https://discuss.elastic.co/t/how-to-use-logbook-handler/146209/6
    #
    # Sentry:
    # https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    # - stack 默认是 False,和 Elastic APM 的不一致,所以还是每次手动指定吧...
    # - 默认事件类型是 `raven.events.Message`,设置 `exc_info` 为 `True` 将把事件类型升级为`raven.events.Exception`
    stderr_handler = logbook.StderrHandler(bubble=True)
    logger.handlers.append(stderr_handler)

    if not offline:
        # Sentry
        sentry.init_app(app=app)
        sentry_handler = SentryHandler(
            sentry.client, level='WARNING')  # Sentry 只处理 WARNING 以上的
        logger.handlers.append(sentry_handler)

        # Elastic APM
        apm = ElasticAPM(app)
        elastic_handler = ElasticHandler(client=apm.client, bubble=True)
        logger.handlers.append(elastic_handler)

    # 初始化数据库
    if os.getenv('MODE', None) != "CI":
        init_pool(app)

    # 导入并注册 blueprints
    from everyclass.server.calendar.views import cal_blueprint
    from everyclass.server.query import query_blueprint
    from everyclass.server.views import main_blueprint as main_blueprint
    from everyclass.server.api import api_v1 as api_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(api_blueprint, url_prefix='/api/v1')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 Elastic APM 记录用户请求"""
        if not session.get('user_id', None):
            # 数据库中生成唯一 ID,参考 https://blog.csdn.net/longjef/article/details/53117354
            conn = get_local_conn()
            cursor = conn.cursor()
            cursor.execute(
                "REPLACE INTO user_id_sequence (stub) VALUES ('a');")
            session['user_id'] = cursor.lastrowid
            cursor.close()

    @app.teardown_request
    def close_db(error):
        """结束时关闭数据库连接"""
        if hasattr(g, 'mysql_db') and g.mysql_db:
            g.mysql_db.close()

    @app.after_request
    def response_minify(response):
        """用 htmlmin 压缩 HTML,减轻带宽压力"""
        if app.config[
                'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8':
            response.set_data(minify(response.get_data(as_text=True)))
        return response

    @app.template_filter('versioned')
    def version_filter(filename):
        """
        模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css'

        :param filename: 文件名
        :return: 新的文件名
        """
        if app.config['STATIC_VERSIONED']:
            if filename[:4] == 'css/':
                new_filename = app.config['STATIC_MANIFEST'][filename[4:]]
                return 'css/' + new_filename
            elif filename[:3] == 'js/':
                new_filename = app.config['STATIC_MANIFEST'][filename[3:]]
                return new_filename
            else:
                return app.config['STATIC_MANIFEST'][filename]
        return filename

    @app.errorhandler(500)
    def internal_server_error(error):
        return render_template(
            '500.html',
            event_id=g.sentry_event_id,
            public_dsn=sentry.client.get_public_dsn('https'))

    logger.info('App created with `{}` config'.format(
        app.config['CONFIG_NAME']),
                stack=False)
    return app
Ejemplo n.º 20
0
def get_connection():
    """获得单个 Connection"""
    import pymysql
    from everyclass.server.config import get_config
    config = get_config()
    return pymysql.Connection(config['MYSQL_CONFIG'])
Ejemplo n.º 21
0
def create_app(outside_container=False) -> Flask:
    """创建 flask app
    @param outside_container: 是否不在容器内运行
    """
    from everyclass.server.db.dao import new_user_id_sequence
    from everyclass.server.db.mysql import init_pool
    from everyclass.server.utils.logbook_logstash.formatter import LOG_FORMAT_STRING

    app = Flask(__name__,
                static_folder='../../frontend/dist',
                static_url_path='',
                template_folder="../../frontend/templates")

    # load app config
    from everyclass.server.config import get_config
    _config = get_config()
    app.config.from_object(_config)
    """
    每课统一日志机制


    规则如下:
    - WARNING 以下 log 输出到 stdout
    - WARNING 以上输出到 stderr
    - DEBUG 以上日志以 json 形式通过 TCP 输出到 Logstash,然后发送到日志中心
    - WARNING 以上级别的输出到 Sentry


    日志等级:
    critical – for errors that lead to termination
    error – for errors that occur, but are handled
    warning – for exceptional circumstances that might not be errors
    notice – for non-error messages you usually want to see
    info – for messages you usually don’t want to see
    debug – for debug messages


    Sentry:
    https://docs.sentry.io/clients/python/api/#raven.Client.captureMessage
    - stack 默认是 False
    """
    stdout_handler = logbook.StreamHandler(stream=sys.stdout,
                                           bubble=True,
                                           filter=lambda r, h: r.level < 13)
    stdout_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stdout_handler)

    stderr_handler = logbook.StreamHandler(stream=sys.stderr,
                                           bubble=True,
                                           level='WARNING')
    stderr_handler.format_string = LOG_FORMAT_STRING
    logger.handlers.append(stderr_handler)

    # CDN
    CDN(app)

    # 容器外运行(无 uWSGI)时初始化数据库
    if outside_container and (app.config['CONFIG_NAME'] == "development"):
        init_pool(app)

    # 导入并注册 blueprints
    from everyclass.server.calendar.views import cal_blueprint
    from everyclass.server.query import query_blueprint
    from everyclass.server.views import main_blueprint as main_blueprint
    from everyclass.server.api import api_v1 as api_blueprint
    app.register_blueprint(cal_blueprint)
    app.register_blueprint(query_blueprint)
    app.register_blueprint(main_blueprint)
    app.register_blueprint(api_blueprint, url_prefix='/api/v1')

    @app.before_request
    def set_user_id():
        """在请求之前设置 session uid,方便 Elastic APM 记录用户请求"""
        if not session.get('user_id', None):
            session['user_id'] = new_user_id_sequence()

    @app.after_request
    def response_minify(response):
        """用 htmlmin 压缩 HTML,减轻带宽压力"""
        if app.config[
                'HTML_MINIFY'] and response.content_type == u'text/html; charset=utf-8':
            response.set_data(minify(response.get_data(as_text=True)))
        return response

    @app.template_filter('versioned')
    def version_filter(filename):
        """
        模板过滤器。如果 STATIC_VERSIONED,返回类似 'style-v1-c012dr.css' 的文件,而不是 'style-v1.css'

        :param filename: 文件名
        :return: 新的文件名
        """
        if app.config['STATIC_VERSIONED']:
            if filename[:4] == 'css/':
                new_filename = app.config['STATIC_MANIFEST'][filename[4:]]
                return 'css/' + new_filename
            elif filename[:3] == 'js/':
                new_filename = app.config['STATIC_MANIFEST'][filename[3:]]
                return new_filename
            else:
                return app.config['STATIC_MANIFEST'][filename]
        return filename

    @app.errorhandler(500)
    def internal_server_error(error):
        return render_template(
            '500.html',
            event_id=g.sentry_event_id,
            public_dsn=sentry.client.get_public_dsn('https'))

    global __app
    __app = app

    return app
Ejemplo n.º 22
0
def get_connection() -> MongoClient:
    """在连接池中获得连接"""
    if not has_app_context():
        config = get_config()
        return MongoClient(**config.MONGODB)[config.MONGODB_DB]
    return current_app.mongo[current_app.config['MONGODB_DB']]
Ejemplo n.º 23
0
import redis

from everyclass.server.config import get_config

config = get_config()
redis = redis.Redis(**config.REDIS)
Ejemplo n.º 24
0
 def wrapped(*args, **kwargs):
     config = get_config()
     if config.MAINTENANCE:
         return render_template('maintenance.html')
     return func(*args, **kwargs)