• python 学习笔记(6)—— Flask 、MySql


    目录

    Flask

    1、起步

    2、渲染项目的首页

    3、处理无参数的 GET 请求

    4、处理有 query 参数的 GET 请求

    6、处理 params 参数的 GET 请求 

    6、处理 application/json 类型请求体的 POST 请求

    7、根据参数渲染模板页面

     8、上传文件

    9、路由处理函数的返回内容 和 配置内容安全策略

    数据库操作(mysql)


    Flask

    1、起步

            先创建一个项目的文件夹,然后在该文件夹下初始化一个虚拟环境。虚拟环境建设好后,文件夹下将有一个 venv 的目录。

    1. # python 3版本 创建虚拟环境:
    2. py -3 -m venv venv
    3. # 激活虚拟环境
    4. venv\Scripts\activate
    5. # 安装 flask
    6. pip install flask

            依次创建 flaskr、flaskr/static、flaskr/templates、tests目录:

            flaskr目录:包含应用代码和文件的 Python 包;

                    /static:存放静态文件,如图片等;

                    /templates:可以用来存放 html 文件,用于服务端渲染;

            test 目录:用来存放测试类型的文件;

    2、渲染项目的首页

    1. from flask import Flask
    2. from flask import render_template
    3. from flask_cors import CORS # pip install flask_cors
    4. # 创建该类的实例
    5. app = Flask(__name__)
    6. CORS(app, resources={r'/*': {'origins' :'*'}}) # 同源策略解决方法
    7. # 使用app.route()进行装饰的函数称为路由处理函数
    8. # 使用装饰器设定匹配的URL,如果命中,则执行下面的函数;默认是 get 请求
    9. @app.route('/')
    10. def default_page():
    11. # 返回指定的页面,是项目下 templates 下的文件
    12. return render_template('index.html')
    13. if __name__ == '__main__':
    14. # 运行该服务,监听的端口为 9090,host=‘0.0.0.0’表示接受公开访问
    15. app.run(port=9090, host='0.0.0.0')
    1. # templates/index.js
    2. html>
    3. <html lang="en">
    4. <head>
    5. <meta charset="UTF-8">
    6. <title>Titletitle>
    7. <style>
    8. h1{ color: brown; }
    9. style>
    10. head>
    11. <body>
    12. <h1>Hello, welcome to the first page.h1>
    13. body>
    14. html>

    3、处理无参数的 GET 请求

             改造 index.html 文件,让请求和结果展示在当前界面完成:

    1. # templates/index.js
    2. <body>
    3. <h1>Hello, welcome to the first page.h1>
    4. <ul>
    5. <li>
    6. <button onclick="get_request('get_datetime', '.datetime')">click to get datetimebutton>
    7. <p class="datetime">p>
    8. li>
    9. ul>
    10. <script>
    11. function get_request(path, renderElement, query='', callback){
    12. const xhr = new XMLHttpRequest()
    13. xhr.open('GET', `http://192.168.145.135:9090/${path}${query ? `?${query}` : ''}`)
    14. xhr.onreadystatechange = () => {
    15. if(xhr.readyState === 4 && xhr.status){
    16. document.querySelector(renderElement).innerText = xhr.response
    17. callback ? callback(xhr.response) : ''
    18. }
    19. }
    20. xhr.send()
    21. }
    22. script>
    23. body>
    1. # 规定匹配的路径为 /get_time 或 /get_time?
    2. @app.route('/get_datetime')
    3. def get_datetime():
    4. return datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    4、处理有 query 参数的 GET 请求

            /templates/index.html 中新增的内容: 

    1. <li>
    2. <input class="name" type="text" placeholder="please input you name">
    3. <select class="gender" name="" id="">
    4. <option value="man">manoption>
    5. <option value="woman">womanoption>
    6. select>
    7. <button onclick="request_args()">click to get datetimebutton>
    8. <p class="name-gender">p>
    9. li>
    10. <script>
    11. function request_args(){
    12. let name = document.querySelector('.name').value
    13. let gender = document.querySelector('.gender').value
    14. get_request('get_with_args', '.name-gender', `name=${name}&gender=${gender}`)
    15. }
    16. script>
    1. from flask import Flask,request
    2. @app.route('/get_with_args')
    3. def get_with_args():
    4. args = request.args
    5. # 各参数可以从 request 实例上去获取
    6. # print(args['name'], args['gender'])
    7. name = args['name']
    8. gender = args['gender']
    9. return f'你好,帅气的{name}先生' if gender == 'man' else f'你好,美丽的{name}小姐'

            关于 request 更多内容可以参考: https://flask.net.cn/api.html#incoming-request-data

    6、处理 params 参数的 GET 请求 

             html 文件新增的测试内容:

    1. <li>
    2. <input class="nickname" type="text" placeholder="input nickname">
    3. <input class="age" type="number" placeholder="input age">
    4. <button onclick="sendParams()">click to send paramsbutton>
    5. <p class="nickname-and-age">p>
    6. li>
    7. <script>
    8. const sendParams = () => {
    9. let nickname = document.querySelector('.nickname').value
    10. let age = document.querySelector('.age').value
    11. get_request(`test-params/${nickname}/${age}`, '.nickname-and-age', '', (resdata)=>{
    12. let strong = document.createElement('strong')
    13. let obj = JSON.parse(resdata)
    14. strong.innerText = `response data: nickname: ${obj.nickname}; age: ${obj.age}`
    15. document.querySelector('.nickname-and-age').append(strong)
    16. })
    17. }
    18. script>
    1. # 两个 params 参数都必须传, 预定义 nickname 参数为字符串,age 为整型
    2. @app.route('/test-params//')
    3. def test_params(nickname, age):
    4. return {'nickname':nickname,'age':age}

    6、处理 application/json 类型请求体的 POST 请求

            index.html 文件中新增的内容: 

    1. <li>
    2. <input class="username" type="text">
    3. <input class="passwd" type="password">
    4. <button class="post-btn">click to postbutton>
    5. <p class="post-res">p>
    6. li>
    7. <script>
    8. document.querySelector('.post-btn').addEventListener('click',()=>{
    9. fetch('http://localhost:9090/test-post',{
    10. method:'post',
    11. body:JSON.stringify({
    12. username:document.querySelector('.username').value,
    13. passwd:document.querySelector('.passwd').value
    14. }),
    15. headers:{
    16. 'Content-Type':'application/json',
    17. 'auth':'abcdefg---6789'
    18. }
    19. }).then(res => res.text()).then(data=>{
    20. document.querySelector('.post-res').innerText = data
    21. })
    22. })
    23. script>
    1. @app.route('/test-post', methods=['post'])
    2. def test_post():
    3. str = (request.data).decode()
    4. print(str)
    5. obj = json.loads(str)
    6. print(obj['username'])
    7. print(request.json['username'])
    8. # print(request.get_data()) # 与 request.data 一致,是字符串类型
    9. # print(request.json) # 是dict类型
    10. return 'ok'

    7、根据参数渲染模板页面

            index.js 中添加的内容: 

    1. <li>
    2. <input type="text" placeholder="your address" class="_address">
    3. <input type="text" placeholder="your name" class="_name">
    4. <button class="to-new-page">click to new page with databutton>
    5. li>
    6. <script>
    7. document.querySelector('.to-new-page').addEventListener('click', ()=>{
    8. address = document.querySelector('._address').value
    9. name = document.querySelector('._name').value
    10. # 在新的窗口中访问
    11. window.open(`http://localhost:9090/new-page?address=${address}&name=${name}`)
    12. })
    13. script>

            新创建两个 html: 

    1. # new-page.html 当有 name 时返回的页面,可以没有address
    2. <body>
    3. {% if address %}
    4. <h1>Hi {{name}}, we know your address is {{address}}.h1>
    5. {% else %}
    6. <h1>Hello {{name}}!h1>
    7. {% endif %}
    8. <img src="https://tse3-mm.cn.bing.net/th/id/OIP-C.bVb769JBdzVZYuksxZ2Y-AHaEo?pid=ImgDet&rs=1" alt="">
    9. body>
    1. # 404.html
    2. <head>
    3. <meta charset="UTF-8">
    4. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    5. <title>404title>
    6. <style>
    7. body,html{
    8. margin: 0;
    9. padding: 0;
    10. }
    11. .outer{
    12. width: 100vw;
    13. height: 100vh;
    14. display: flex;
    15. justify-content: center;
    16. align-items: center;
    17. }
    18. .num{
    19. width: 100px;
    20. height: 100px;
    21. font-size: 5rem;
    22. font-weight: 900;
    23. text-shadow: 3px 3px 3px #aaa;
    24. }
    25. .num:nth-child(2n){ color: brown; }
    26. .num:nth-child(2n + 1){ color: chocolate; }
    27. .num:nth-child(4){ width: auto; }
    28. style>
    29. head>
    30. <body>
    31. <div class="outer">
    32. <div class="num">4div>
    33. <div class="num">0div>
    34. <div class="num">4div>
    35. <div class="num">no founddiv>
    36. div>
    37. body>
    1. # 可以直接访问
    2. @app.route('/new-page')
    3. def new_page():
    4. # 设置 address 和 name 都不是必传参数
    5. address = request.args['address'] if request.args else ''
    6. name = request.args['name'] if request.args else ''
    7. if name: # 往模板页面传递两个参数
    8. return render_template('another.html', address=address, name=name)
    9. else:
    10. # 如果没有名字,则返回 404 的页面
    11. return render_template('404.html')

    只有 name 时
    只有 name 时

    name 和 address 都没有时

    name 和 address 都具备时

     8、上传文件

            index.html 中新增的内容: 

    1. <li>
    2. <input type="file" accept=".jpg,.png,.gif" id="file">
    3. <button class="upload-file" onclick="uploadFile()">upload filebutton>
    4. <p class="upload-file-res">p>
    5. li>
    6. <script>
    7. function uploadFile(){
    8. let files = document.querySelector('#file').files
    9. const formdata = new FormData()
    10. formdata.append('file', files[0])
    11. formdata.append('time', new Date().getTime())
    12. fetch('http://localhost:9090/upload-file',{
    13. method:'post',
    14. body:formdata
    15. }).then(res=>res.text()).then(data=>{
    16. console.log(data)
    17. })
    18. }
    19. script>
    1. @app.route('/upload-file', methods=['post'])
    2. def upload_file():
    3. if 'file' not in request.files:
    4. return 'upload fail'
    5. file = request.files['file']
    6. if file.filename == '':
    7. return 'filename no found'
    8. file.save(f'./{file.filename}') # 保存图片到本地
    9. return 'ok'

    关于 flask 文件上传的更安全的处理:上传文件_Flask中文网

    9、路由处理函数的返回内容 和 配置内容安全策略

            每一个使用 app.route() 进行装饰的路由处理函数都需要有返回值,是服务端返回给客户端的响应体内容。该返回值可以是一个字符串,也可以是一个元组(包含状态码、响应体等的信息)等的形式。

            可以使用 make_response() 来构造响应内容。

    1. from flask import request, make_response
    2. @app.route('/')
    3. def website():
    4. ip = request.remote_addr # 获取用户的ip
    5. agent = request.user_agent # 获取用户访问时的设备信息(浏览器信息)
    6. print('ip:',ip)
    7. print('agent:',agent)
    8. # 设置内容安全策略 report-uri /CSP-feedback 将违反安全策略的情况使用post请求发送到当前项目的 /CSP-feedback 路由处理函数中进行处理
    9. csp_header = {
    10. 'Content-Security-Policy': "default-src 'self' 'unsafe-inline';script-src 'self';report-uri /CSP-feedback",
    11. 'X-Content-Type-Options': 'nosniff',
    12. 'X-Frame-Options': 'DENY',
    13. 'X-XSS-Protection': '1; mode=block'
    14. }
    15. temp = render_template('index.html')
    16. res = make_response(temp, 200) # 设置响应的html内容和状态码
    17. res.headers.extend(csp_header) # 将内容安全策略添加到响应头
    18. return res
    19. @app.route('/CSP-feedback',methods=['post'])
    20. def CSP_feedback():
    21. data = request.data.decode() # 获取响应体的内容
    22. ip = request.remote_addr
    23. print(data, ip)
    24. return {}
    25. @app.route('/test')
    26. def hello_world():
    27. response = make_response('Hello, World!') # 设置响应内容
    28. response.status_code = 200 # 单独设置状态码
    29. response.headers['Content-Type'] = 'text/plain' # 设置响应体类型
    30. return response

            访问时 控制台显示出违反安全策略的详情内容: 

    1. {
    2. "csp-report":{
    3. "blocked-uri":"inline",
    4. "column-number":78,
    5. "disposition":"enforce",
    6. "document-uri":"http://127.0.0.1:9090/",
    7. "effective-directive":"script-src-elem",
    8. "line-number":249,
    9. "original-policy":"default-src 'self' 'unsafe-inline'; script-src 'self'; report-uri http://127.0.0.1:9090/CSP-feedback",
    10. "referrer":"",
    11. "source-file":"moz-extension",
    12. "status-code":200,
    13. "violated-directive":"script-src-elem"
    14. }
    15. }

    "blocked-uri":被CSP阻止的资源URI。"inline"意味着CSP阻止了在HTML文档中直接插入内联脚本(即