diff --git a/web-framework/python/flask/publish.yaml b/web-framework/python/flask/publish.yaml index 95bec0803..c62e355ff 100755 --- a/web-framework/python/flask/publish.yaml +++ b/web-framework/python/flask/publish.yaml @@ -3,7 +3,7 @@ Type: Project Name: start-flask-cap Provider: - 阿里云 -Version: 0.0.5 +Version: 0.1.1 Description: 本案例展示了如何将 Flask,一款非常受欢迎的 Web 框架,快捷创建并部署到云原生应用开发平台 CAP。Flask 以其轻量级、简洁性和易用性著称,特别适用于小型、简单的 Web 应用或 API 开发。 HomePage: https://github.com/devsapp/start-web-framework/tree/dipper/web-framework/python/flask Organization: 阿里云函数计算(FC) @@ -11,6 +11,7 @@ Effective: Public Tags: - Web框架 - Flask + - Develop Category: Web框架 Service: 函数计算: @@ -25,6 +26,7 @@ Parameters: required: # 必填项 - region - functionName + - roleArn properties: region: title: 地域 @@ -58,3 +60,17 @@ Parameters: default: flask-${default-suffix} pattern: "^[a-zA-Z_][a-zA-Z0-9-_]{0,127}$" description: 函数名称,只能包含字母、数字、下划线和中划线。不能以数字、中划线开头。长度在 1-128 之间 + roleArn: + title: 服务角色ARN + type: string + default: "AliyunFCDefaultRole" + pattern: "^acs:ram::[0-9]*:role/.*$" + description: "函数计算访问其他云服务时使用的服务角色,需要填写具体的角色ARN,格式为acs:ram::$account-id>:role/$role-name。例如:acs:ram::14310000000:role/aliyunfcdefaultrole。 + \n如果您没有特殊要求,可以使用函数计算提供的默认的服务角色,即AliyunFCDefaultRole。如果您首次使用函数计算,可以访问 https://fcnext.console.aliyun.com 进行授权。 + \n详细文档参考 https://help.aliyun.com/document_detail/181589.html?spm=5176.fcnext.help.dexternal.7bea78c8sVHoRf#section-o93-dbr-z6o" + x-role: + name: AliyunFCDefaultRole + service: fc + authorities: + - AliyunOSSFullAccess + - AliyunFCDefaultRolePolicy diff --git a/web-framework/python/flask/src/.fcignore b/web-framework/python/flask/src/.fcignore new file mode 100644 index 000000000..b2ff5e1e4 --- /dev/null +++ b/web-framework/python/flask/src/.fcignore @@ -0,0 +1,10 @@ +.idea/ +.venv/ +.vscode/ +.DS_Store +.env +s.yaml +readme.md +build.sh +.fcignore +.gitignore \ No newline at end of file diff --git a/web-framework/python/flask/src/.gitignore b/web-framework/python/flask/src/.gitignore new file mode 100644 index 000000000..49fd9ff25 --- /dev/null +++ b/web-framework/python/flask/src/.gitignore @@ -0,0 +1,267 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,intellij+all +# Edit at https://www.toptal.com/developers/gitignore?templates=python,intellij+all + +### Intellij+all ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf + +# AWS User-specific +.idea/**/aws.xml + +# Generated files +.idea/**/contentModel.xml + +# Sensitive or high-churn files +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml + +# Gradle +.idea/**/gradle.xml +.idea/**/libraries + +# Gradle and Maven with auto-import +# When using Gradle or Maven with auto-import, you should exclude module files, +# since they will be recreated, and may cause churn. Uncomment if using +# auto-import. +# .idea/artifacts +# .idea/compiler.xml +# .idea/jarRepositories.xml +# .idea/modules.xml +# .idea/*.iml +# .idea/modules +# *.iml +# *.ipr + +# CMake +cmake-build-*/ + +# Mongo Explorer plugin +.idea/**/mongoSettings.xml + +# File-based project format +*.iws + +# IntelliJ +out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# SonarLint plugin +.idea/sonarlint/ + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +# Editor-based Rest Client +.idea/httpRequests + +# Android studio 3.1+ serialized cache file +.idea/caches/build_file_checksums.ser + +### Intellij+all Patch ### +# Ignore everything but code style settings and run configurations +# that are supposed to be shared within teams. + +.idea/* + +!.idea/codeStyles +!.idea/runConfigurations + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python,intellij+all + +python +.idea \ No newline at end of file diff --git a/web-framework/python/flask/src/alicloud/oss.py b/web-framework/python/flask/src/alicloud/oss.py new file mode 100644 index 000000000..9bf4d7c69 --- /dev/null +++ b/web-framework/python/flask/src/alicloud/oss.py @@ -0,0 +1,8 @@ +import oss2 + +from config import config + +stsAuth = oss2.StsAuth(config.ACCESS_KEY_ID, config.ACCESS_KEY_SECRET, config.SECURITY_TOKEN) +# https://help.aliyun.com/zh/oss/user-guide/regions-and-endpoints +endpoint = "oss-%s-internal.aliyuncs.com" % config.CURRENT_REGION +oss_client = oss2.Service(stsAuth, endpoint) diff --git a/web-framework/python/flask/src/build.yaml b/web-framework/python/flask/src/build.yaml index 89ef27265..e61b059b6 100644 --- a/web-framework/python/flask/src/build.yaml +++ b/web-framework/python/flask/src/build.yaml @@ -4,6 +4,6 @@ start_flask: - python3.10 steps: - run: mkdir -p python - path: ./code + path: ./ - run: pip install -r requirements.txt -t ./python - path: ./code \ No newline at end of file + path: ./ \ No newline at end of file diff --git a/web-framework/python/flask/src/code/index.py b/web-framework/python/flask/src/code/index.py deleted file mode 100644 index 4e0979f27..000000000 --- a/web-framework/python/flask/src/code/index.py +++ /dev/null @@ -1,30 +0,0 @@ -from flask import Flask, request, jsonify -import arrow - -app = Flask(__name__) - - -@app.route("/", defaults={"path": ""}, methods=["GET", "POST", "PUT", "DELETE"]) -@app.route("/", methods=["GET", "POST", "PUT", "DELETE"]) -def hello_world(path): - requestId = request.headers.get("x-fc-request-id", "") - print("FC Invoke Start RequestId: " + requestId) - - response = jsonify( - { - "msg": "Hello, World!" + " at " + arrow.now().format("YYYY-MM-DD HH:mm:ss"), - "request": { - "query": str(request.query_string, "utf-8"), - "path": path, - "data": str(request.stream.read(), "utf-8"), - "clientIp": request.headers.get("x-forwarded-for"), - }, - } - ) - - print("FC Invoke End RequestId: " + requestId) - return response - - -if __name__ == "__main__": - app.run(host="0.0.0.0", port=9000) diff --git a/web-framework/python/flask/src/code/requirements.txt b/web-framework/python/flask/src/code/requirements.txt deleted file mode 100644 index 023f6a616..000000000 --- a/web-framework/python/flask/src/code/requirements.txt +++ /dev/null @@ -1,2 +0,0 @@ -# 建议您使用 pip install -r requirements.txt -t ./python 将依赖安装到 python 目录下。 -arrow==1.3.0 \ No newline at end of file diff --git a/web-framework/python/flask/src/config.py b/web-framework/python/flask/src/config.py new file mode 100644 index 000000000..d3add7214 --- /dev/null +++ b/web-framework/python/flask/src/config.py @@ -0,0 +1,13 @@ +import os + + +class Config: + SECRET_KEY = os.environ.get('SECRET_KEY') or 'my-passwd' + ACCESS_KEY_ID = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_ID") or 'default-access-key-id' + ACCESS_KEY_SECRET = os.environ.get("ALIBABA_CLOUD_ACCESS_KEY_SECRET") or 'default-access-key-secret' + SECURITY_TOKEN = os.environ.get("ALIBABA_CLOUD_SECURITY_TOKEN") or 'default-security-token' + CURRENT_REGION = os.environ.get("FC_REGION") or 'cn-hangzhou' + DEBUG = True + + +config = Config diff --git a/web-framework/python/flask/src/index.py b/web-framework/python/flask/src/index.py new file mode 100644 index 000000000..4948114f1 --- /dev/null +++ b/web-framework/python/flask/src/index.py @@ -0,0 +1,5 @@ +from init import create_app +app = create_app() + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=9000) diff --git a/web-framework/python/flask/src/init.py b/web-framework/python/flask/src/init.py new file mode 100644 index 000000000..8c0ec5b68 --- /dev/null +++ b/web-framework/python/flask/src/init.py @@ -0,0 +1,35 @@ +import logging +import uuid + +from flask import Flask, g, request + +from route.debug import debug_bp +from route.oss import oss_bp +from route.welcome import welcome_bp + + +def create_app(): + app = Flask(__name__) + + # 载入配置 + app.config.from_pyfile('./config.py') + + logging.basicConfig(level=logging.INFO) + with app.app_context(): + # 注册路由 + app.register_blueprint(welcome_bp) + app.register_blueprint(oss_bp) + app.register_blueprint(debug_bp) + + @app.before_request + def before_request(): + # 从请求头中获取 request-id + g.request_id = request.headers.get('x-fc-request-id', uuid.uuid4()) + + @app.after_request + def after_request(response): + # 移除request-id + g.request_id = '' + return response + + return app diff --git a/web-framework/python/flask/src/logger/config.py b/web-framework/python/flask/src/logger/config.py new file mode 100644 index 000000000..915617d53 --- /dev/null +++ b/web-framework/python/flask/src/logger/config.py @@ -0,0 +1,22 @@ +import logging + +from flask import g + + +class RequestIdFilter(logging.Filter): + def filter(self, record): + record.request_id = getattr(g, 'request_id', '') + return True + + +# 配置日志记录器 +handler = logging.StreamHandler() +handler.setLevel(logging.INFO) +fmt = '%(asctime)s - %(name)s - %(levelname)s - %(request_id)s - %(message)s' +formatter = logging.Formatter(fmt=fmt, datefmt='%Y-%m-%d %H:%M:%S') +handler.setFormatter(formatter) + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) +logger.addHandler(handler) +logger.addFilter(RequestIdFilter()) diff --git a/web-framework/python/flask/src/requirements.txt b/web-framework/python/flask/src/requirements.txt new file mode 100644 index 000000000..22f2f7437 --- /dev/null +++ b/web-framework/python/flask/src/requirements.txt @@ -0,0 +1,7 @@ +# 建议您使用bash build.sh将依赖安装到 python 目录下。 +arrow==1.3.0 +Flask==3.0.3 +cryptography==38.0.4 +charset-normalizer==2.1.1 +urllib3==1.26.18 +oss2==2.18.6 \ No newline at end of file diff --git a/web-framework/python/flask/src/route/debug.py b/web-framework/python/flask/src/route/debug.py new file mode 100644 index 000000000..77e84f3bf --- /dev/null +++ b/web-framework/python/flask/src/route/debug.py @@ -0,0 +1,33 @@ +from logger.config import logger +import os + +from flask import Blueprint, jsonify, request + +debug_bp = Blueprint('debug', __name__, url_prefix='/debug') + + +@debug_bp.route('/displayHttpContext', methods=['GET', 'POST', 'PUT', 'DELETE']) +def echo(): + # 构建响应数据 + env = dict(os.environ) + if 'ALIBABA_CLOUD_ACCESS_KEY_ID' in env: + env['ALIBABA_CLOUD_ACCESS_KEY_ID'] = 'encrypted' + if 'ALIBABA_CLOUD_ACCESS_KEY_SECRET' in env: + env['ALIBABA_CLOUD_ACCESS_KEY_SECRET'] = 'encrypted' + if 'ALIBABA_CLOUD_SECURITY_TOKEN' in env: + env['ALIBABA_CLOUD_SECURITY_TOKEN'] = 'encrypted' + + result = { + 'path': request.path, + 'body': request.get_data(as_text=True), + 'method': request.method, + 'queries': request.args, + 'headers': dict(request.headers), + 'env': env, + } + + # 记录日志信息 + logger.info('receive request: %s', result) + + # 返回 JSON 响应 + return jsonify(result) diff --git a/web-framework/python/flask/src/route/oss.py b/web-framework/python/flask/src/route/oss.py new file mode 100644 index 000000000..d32dc8324 --- /dev/null +++ b/web-framework/python/flask/src/route/oss.py @@ -0,0 +1,26 @@ +from flask import Blueprint, jsonify + +from logger.config import logger + +from alicloud.oss import oss_client + +oss_bp = Blueprint('oss', __name__, url_prefix='/oss') + + +@oss_bp.route('/listBuckets', methods=['GET']) +def index(): + try: + resp = oss_client.list_buckets() + return jsonify(list(map(lambda x : { + 'name': x.name, + 'location': x.location, + 'creation_date': x.creation_date, + 'extranet_endpoint': x.extranet_endpoint, + 'intranet_endpoint': x.intranet_endpoint, + 'storage_class': x.storage_class, + 'region': x.region, + 'resource_group_id': x.resource_group_id + }, resp.buckets))) + except Exception as e: + logger.info('failed to list buckets: %s', e) + return jsonify(e) diff --git a/web-framework/python/flask/src/route/welcome.py b/web-framework/python/flask/src/route/welcome.py new file mode 100644 index 000000000..f784a8fae --- /dev/null +++ b/web-framework/python/flask/src/route/welcome.py @@ -0,0 +1,9 @@ +from flask import Blueprint, render_template + +welcome_bp = Blueprint('welcome', __name__, url_prefix='/') + + +@welcome_bp.route('/') +def index(): + return render_template('index.html') + diff --git a/web-framework/python/flask/src/s.yaml b/web-framework/python/flask/src/s.yaml index 13f58d0a3..bb679a82f 100644 --- a/web-framework/python/flask/src/s.yaml +++ b/web-framework/python/flask/src/s.yaml @@ -15,9 +15,9 @@ resources: actions: pre-deploy: - run: mkdir -p python - path: ./code + path: ./ - run: export PATH=/usr/local/envs/py310/bin:$PATH && pip install -r requirements.txt -t ./python - path: ./code + path: ./ props: timeout: 60 cpu: 1 @@ -37,8 +37,9 @@ resources: PYTHONPATH: /opt/python:/code/python:/code TZ: Asia/Shanghai functionName: "{{ functionName }}" - code: ./code + code: ./ logConfig: auto + role: "{{ roleArn }}" layers: - acs:fc:${this.props.region}:official:layers/Python3-Flask2x/versions/2 triggers: diff --git a/web-framework/python/flask/src/static/style.css b/web-framework/python/flask/src/static/style.css new file mode 100644 index 000000000..6e94bdfaf --- /dev/null +++ b/web-framework/python/flask/src/static/style.css @@ -0,0 +1,10 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + text-align: center; + background-color: #f0f0f0; +} +h1 { + color: #333; +} \ No newline at end of file diff --git a/web-framework/python/flask/src/templates/index.html b/web-framework/python/flask/src/templates/index.html new file mode 100644 index 000000000..76c7be0cf --- /dev/null +++ b/web-framework/python/flask/src/templates/index.html @@ -0,0 +1,22 @@ + + + + + Serverless Devs - Powered By Serverless Devs + + + +
+
+

Devsapp

+

这是一个 Flask 项目

+ 通过云原生应用平台进行部署,并展示如何操作云资源(OSS) +

修改代码提交后即可自动构建、部署
+ Serverless Devs 钉钉交流群:33947367


+ + 获取OSS Bucket列表
+ 获取HTTP上下文 +
+
+ + \ No newline at end of file