# python-flask-demo **Repository Path**: miswu/python-flask-demo ## Basic Information - **Project Name**: python-flask-demo - **Description**: flask学习demo - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-09-29 - **Last Updated**: 2024-10-30 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # flask框架学习指南 ## 启动前准备(第三方库安装): `备注: 项目虚拟环境中的依赖与全局环境中的依赖不共享,可以配置共享,但不推荐, 避免依赖混乱或不兼容, 因此建议每套虚拟环境相互隔离,相互不受影响!!! ` ```python ## 数据库连接工具,学习推荐用 pip install pymysql ## 项目开发中推荐用,更强大(本次使用) pip install flask-sqlalchemy ## 数据模型迁移 pip install flask_migrate ## 跨域 pip install flask-cors ``` ## 项目debug,host,port配置 ### debug配置 `如果不使用debug模式,项目启动后无法热更新,使用debug模式,方便排查异常` `注:使用第二种方式,如果不生效,因为pycharm识别出你是flask项目,你运行时,可以看到一个flask的图标, 切换为python的运行模式即可,Edit Configurations,左上角 + 号,新增python,将路径、名称复制过去即可。` 配置: ```text 方式1、Edit Configurations: Modify options --> Flask debug ``` ```text 方式2、app.py中 app.run(debug=True, host='0.0.0.0', port=8080) ``` ### host配置 ```text 方式1、app.py中 app.run(debug=True, host='0.0.0.0', port=8080) ``` ```text 方式2、Edit Configurations, Additional options:(添加启动参数) --host=127.0.0.1 ``` ### 端口设置 ```text 方式1、app.py中 app.run(debug=True, host='0.0.0.0', port=8080) ``` ```text 方式2、Edit Configurations, Additional options:(添加启动参数) --host=127.0.0.1 --port=8080 ``` ## 数据模型迁移 flask_migrate ```python // 1、导入依赖 from flask_migrate import Migrate // 2、初始化(导入app,db) migrate = Migrate(app, db) // 3、以下脚本在terminal中执行 创建迁移脚本(注:只执行一次,后续不用再执行) flask db init // 4、添加迁移脚本 flask db migrate // 5、更新数据库 flask db upgrade ``` ## 路由蓝图使用 Blueprint ### 第一步,创建蓝图 ```python #新建一个包 blueprints,底下新建auth.py文件,内容如下: from flask import Blueprint # 创建权限蓝图 bp = Blueprint("auth", __name__, url_prefix="/auth") #requestUrl: http://localhost:8080/auth/login/张三/123 @bp.route("/login//") def login(name, pwd): return f"loginName: {name}, pwd:{pwd}" ``` ### 第二部 注册蓝图 ```python # 在启动程序文件中 app.py 中注册 from blueprints.AuthViews import bp as auth_bp app.register_blueprint(auth_bp) ``` ## flask请求传参及获取参数的方式 ### 方式一 : http://ip:port/url/<参数1>/<参数2> ```python @app.route('/api/json//') def get_json(param1, param2): # 处理逻辑... return f"Hello World: {param1} ===> {param2}" ``` ### 方式二 : http://ip:port/url/param1=v1¶m2=v2 ```python from flask import Flask, request app = Flask(__name__) @app.route('/api/json') def get_json(): param1 = request.args.get('param1') param2 = request.args.get('param2') # 处理逻辑... return 'Success!' ``` ### 方式三:post请求,json数据获取 ```python from flask import Flask, request @app.route('/api/json', methods=['POST']) def post_json(): data = request.get_json() param1 = data.get('param1') param2 = data.get('param2') # 处理逻辑... return 'Success!' ``` ## 常见功能的一些三方依赖 1. 邮件服务:flask-mail ```python pip install flask-mail ``` 2. 验证码:flask-captcha ## 常见钩子函数 ### @app.before_request装饰器 `作用:在每次请求之前执行` 使用 ```python from flask import Flask, session, g, request, current_app @app.before_request def my_before_request(): # 可以通过路径方式来达到拦截器作用 # request_url = request.url # if request_url.startswith("/auth/loginGet"): # return user_id = session.get("user_id") if user_id: user = UserModel.query.get(user_id) setattr(g, "user", user) else: setattr(g, "user", None) ``` ### @app.context_processor装饰器 `作用:添加全局变量,上下文对象处理器,在任何html文件中都可获取其中的数据,一般在前后端不分离的项目中使用较多, 前后端分离项目中使用较少。` 使用 ```python from flask import Flask, session, g @app.context_processor def my_context_processor(): return {"user": g.user} ``` ## 前后端分离解决跨域问题 ```python # 安装依赖 pip install flask_cors ``` ```python # 方案一、不需要给浏览器发送session/cookie数据,跨域配置 app.py中 from flask_cors import CORS CORS(app, resources=r'/*', supports_credentials=True, expose_headers=["Content-Disposition"]) # 方案二、需要给浏览器发送session/cookie凭证 # 1、app.py中: from flask_cors import CORS # 允许特定的源并支持凭证 # CORS(app, origins=["http://localhost:8085"], supports_credentials=True) CORS(app, resources=r'/*', supports_credentials=True) # config.py中 session配置(上面方案都可以加) # PERMANENT_SESSION_LIFETIME = 60 * 60 * 24 * 30 # 30天 SESSION_COOKIE_SAMESITE = None SESSION_COOKIE_SECURE = False # 如果使用 HTTP,设为 False;如果使用 HTTPS,设为 True ``` ## flask项目中一些坑点 1. 根据数据模型查询数据库及外键的配置 ```text 1、数据库表(模型)之间存在依赖关系的,被依赖的模型得先加载,写在最前面。 2、如果数据库模型不在同一个 .py文件中,在使用模型查询数据库即 model.query时,被依赖的模型 也要导入到视图函数(接口)所在文件,否则会报错。 3、db.relationship()说明: 参数一:当前模型要关联(映射)到哪个模型,使用python的模型名,即class名称,字符串 参数 back_populates :指定双向关系,两个模型中相互指定 参数 primaryjoin='BookType.type_id==book_info.type_id'指定外键关联字段 4、外键定义:db.ForeignKey('book_type.type_id') 指定当前模型中定义的关联模型名.外键字段 ``` 2. 数据库配置 ```text 数据库密码中尽量不要包含 @,否则会报错,因为连接数据库的URL中:DB_URI = f"mysql+pymysql://{userName}:{password}@{hostName}:{port}/{dataBase}?charset=utf8" 密码和主机之间使用 @分割,密码中再包含@的话,会分割异常,导致连接失败。 ``` 3. test ## 文件下载 ### 1、以blob形式下载 ```python 发送客户端方式: 1、send_from_directory() 绝对路径 2、requests.get(url) 相对路径 pip install requests 3、send_file() 相对路径 # app.py中 CORS(app, resources=r'/*', supports_credentials=True, expose_headers=["Content-Disposition"]) @bp.route("/downloadImage") def download_image(): url = request.args.get("fileUrl") if not url: return fail_json_result(msg="图片下载异常,图片地址不能为空") relative_url = "/".join(url.split("/")[3:]) print(f"相对路径:{relative_url}") try: # send_from_directory(UPLOAD_FOLDER, relative_url) # file_response = requests.get(url) # print(f"下载返回值:{file_response}") # 方案一、将图片文件blob返回给客户端 file_response = send_file( relative_url, as_attachment=True, mimetype='image/jpeg' ) return file_response except Exception as e: return fail_json_result(msg=f"图片下载失败:{e}") ``` ```vue // 前端处理 axios响应拦截器中: if (response.headers['content-disposition']){ return response } // http请求 downloadImage(url){ this.$http.get('/book/downloadImage', { params: {fileUrl: url}, responseType: 'blob' }).then(res => { const contentDisposition = res.headers['content-disposition'] let filename = this.parseFilenameFromContentDisposition(contentDisposition) filename = filename ? filename : 'downloaded_image.jpg' const blob = new Blob([res.data], { type: 'application/octet-stream' }) const link = document.createElement('a') link.href = URL.createObjectURL(blob) link.download = filename link.click() URL.revokeObjectURL(link.href) }) } parseFilenameFromContentDisposition(contentDisposition) { if (!contentDisposition) { return 'unknown'; } const regex = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/; const matches = regex.exec(contentDisposition); if (matches && matches[1]) { let filename = matches[1].trim(); // 如果 filename 被引号包围,去掉引号 if (filename.startsWith('"') && filename.endsWith('"')) { filename = filename.slice(1, -1); } return filename; } return 'unknown'; } ``` ### 以base64格式下载 ```python @bp.route("/downloadImage") def download_image(): url = request.args.get("fileUrl") if not url: return fail_json_result(msg="图片下载异常,图片地址不能为空") relative_url = "/".join(url.split("/")[3:]) print(f"相对路径:{relative_url}") try: # 方案二、返回图片base64 with open(relative_url, "rb") as f: image_data = f.read() image_base64 = base64.b64encode(image_data).decode('utf-8') result_data = { "imageBase64": image_base64, "fileName": relative_url.split("/")[-1] } return success_json_result(data=result_data) except Exception as e: return fail_json_result(msg=f"图片下载失败:{e}") ``` ```vue // 前端请求 downloadImage(url){ this.$http.get(`/book/downloadImage?fileUrl=${url}`).then(res => { // 方案二、base64下载 const base64Data = res.data.imageBase64 const fileName = res.data.fileName this.downloadBase64File(base64Data, fileName) this.$message.success(res.msg || '下载成功') }) }, base64toBlob(base64, type = 'application/octet-stream') { const bstr = atob(base64) // atob方法是JavaScript的全局函数,用于将Base64编码的字符串解码为原始二进制数据。 let n = bstr.length const u8arr = new Uint8Array(n) //处理二进制数据;new Uint8Array是 JavaScript 中表示8位无符号整型数组的类型 while (n--) { u8arr[n] = bstr.charCodeAt(n) // charCodeAt() 方法可返回指定位置的字符的 Unicode 编码,返回值是 0 - 65535 之间的整数,表示给定索引处的 UTF-16 代码单元 } return new Blob([u8arr], { type }) }, downloadBase64File(base64Data, fileName){ var blob = this.base64toBlob(base64Data) const url = URL.createObjectURL(blob) const a = document.createElement('a') a.style.display = 'none' a.href = url a.download = fileName document.body.appendChild(a) a.click() document.body.removeChild(a) URL.revokeObjectURL(url) }, ```