基于Flask的Web应用开发

打印 上一主题 下一主题

主题 1002|帖子 1002|积分 3006

基于Flask的Web应用开发

项目来源:[【基于Flask的Web应用开发-01.应用先容及Flask安装_s】](【基于Flask的Web应用开发-01.应用先容及Flask安装_s】 https://www.bilibili.com/video/BV1r94y1j7uW/?share_source=copy_web&vd_source=d0886da49a29063777f2956d5780b087)
原作者首页: http://hifengge.com/index.html
我的代码堆栈:https://github.com/hu0701/flask-bootstrap.git
记录学习flask条记代码
一、应用先容及Flask安装

二、使用模板

三、连接MySQL数据库

1、引入模块

window是安装MySQL5.7
https://blog.csdn.net/sunshine7058/article/details/138474991
requirements.txt文件追加模板
  1. mysqlclient==2.2.0
  2. SQLAlchemy==2.0.23
  3. Flask-SQLAlchemy==3.1.1
复制代码
2、配置数据库连接参数

https://docs.sqlalchemy.org/en/20/dialects/mysql.html#module-sqlalchemy.dialects.mysql.mysqldb
routes/__init__.py
  1. mysql+mysqldb://<user>:<password>@<host>[:<port>]/<dbname>
复制代码
  1. from flask import Flask
  2. from flask_sqlalchemy import SQLAlchemy
  3. app = Flask(__name__,
  4.             template_folder='../templates',
  5.             static_folder='../assets',
  6.             static_url_path='/assets')
  7. app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://root:root@127.0.0.1/myblog_db'
  8. db = SQLAlchemy(app)
  9. from routes import user_routes
  10. from routes import admin_routes
复制代码
3、定义数据库映射类

models/article.py
  1. from datetime import datetime
  2. from routes import db
  3. from sqlalchemy import Integer, String, BLOB, TIMESTAMP
  4. from sqlalchemy.orm import Mapped, mapped_column
  5. class Article(db.Model):
  6.     """
  7.     踩坑,
  8.     1、nullable参数写错
  9.     2、格式不对齐
  10.     """
  11.     __tablename__ = 'articles'
  12.     id: Mapped[int] = mapped_column(Integer, primary_key=True)
  13.     title: Mapped[str] = mapped_column(String(255), unique=True, nullable=False)
  14.     __content: Mapped[bytes] = mapped_column(BLOB, name="content", nullable=False)
  15.     create_time: Mapped[datetime] = mapped_column(TIMESTAMP, nullable=False)
  16.     update_time: Mapped[datetime] = mapped_column(TIMESTAMP, nullable=True)
  17.     @property
  18.     def content(self):
  19.         return self.__content.decode('utf-8')
复制代码
4、前端渲染

index.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3. 博客主页
  4. {% endblock %}
  5. <--! 拼写错误:在 index.html 文件中,你在循环部分写成了 acticles,应该是 articles。这个拼写错误会导致循环内容无法正确显示。 -->
  6. {% block content %}
  7. <table border="1">
  8.     <tr>
  9.         <th>标题</th>
  10.         <th>时间</th>
  11.     </tr>
  12.     {% for article in  articles %}
  13.     <tr>
  14.         <td><a target="_blank" href="https://www.cnblogs.com/article/{{ article.id }}.html">{{ article.title }}</a></td>
  15.         <td>{{ article.create_time }}123</td>
  16.     </tr>
  17.     {% endfor %}
  18. </table>
  19. {% endblock %}
复制代码
结果:




四、实现用户登录

1、添加新的模块

requirements.txt文件追加模板
  1. flask-WTF==1.2.1
  2. flask-login==0.6.3
复制代码
2、定义用户表的映射

modele/user.py
  1. from flask_login import UserMixin
  2. from routes import db, login_manager
  3. from sqlalchemy import Integer, String, BLOB, TIMESTAMP
  4. from sqlalchemy.orm import Mapped, mapped_column
  5. @login_manager.user_loader
  6. def load_user(user_id):
  7.     return db.session.get(User, user_id)
  8. class User(db.Model, UserMixin):
  9.     __tablename__ = 'user'
  10.     id: Mapped[int] = mapped_column(Integer, primary_key=True)
  11.     username: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
  12.     password: Mapped[str] = mapped_column(String(255), nullable=False)
  13.     fullname: Mapped[str] = mapped_column(String(128), nullable=False)
  14.     description: Mapped[str] = mapped_column(String(255), nullable=True)
  15.     def check_password_correction(self, attempted_password):
  16.         return self.password == attempted_password
复制代码
3、增加login_manager的初始化

routes/__init__.py
  1. from flask import Flask
  2. from flask_sqlalchemy import SQLAlchemy
  3. from flask_login import LoginManager
  4. app = Flask(__name__,
  5.             template_folder='../templates',
  6.             static_folder='../assets',
  7.             static_url_path='/assets')
  8. app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqldb://root:root@127.0.0.1/myblog_db'
  9. app.config['SECRET_KEY'] = 'ec9439cfc6c796ae2029594d'  #初始化配置
  10. db = SQLAlchemy(app)
  11. login_manager = LoginManager(app)                                        #初始化实例
  12. from routes import user_routes
  13. from routes import admin_routes
复制代码
4、为User类增加对login_manage的支持

modele/user.py
  1. from datetime import datetimefrom flask_login import UserMixin
  2. from routes import db, login_manager
  3. from sqlalchemy import Integer, String, BLOB, TIMESTAMP
  4. from sqlalchemy.orm import Mapped, mapped_column
  5. @login_manager.user_loader
  6. def load_user(user_id):
  7.     return db.session.get(User, user_id)
  8. class User(db.Model, UserMixin):
  9.     __tablename__ = 'user'
  10.     id: Mapped[int] = mapped_column(Integer, primary_key=True)
  11.     username: Mapped[str] = mapped_column(String(128), unique=True, nullable=False)
  12.     password: Mapped[str] = mapped_column(String(255), nullable=False)
  13.     fullname: Mapped[str] = mapped_column(String(128), nullable=False)
  14.     description: Mapped[str] = mapped_column(String(255), nullable=True)
  15.     def check_password_correction(self, attempted_password):
  16.         return self.password == attempted_password
复制代码
5、编写表单类

forms/login_form.py
  1. from flask_wtf import FlaskForm
  2. from wtforms import StringField, PasswordField, SubmitField
  3. from wtforms.validators import DataRequired
  4. class LoginForm(FlaskForm):
  5.     username = StringField(label="用户名:", validators=[DataRequired()])
  6.     password = PasswordField(label="密码:", validators=[DataRequired()])
  7.     submit = SubmitField(label="登陆")
复制代码
6、编写表单页面

templates/login.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3. 博客主页
  4. {% endblock %}
  5. {% block content %}
  6.     <form method="POST" >
  7.         {{ form.hidden_tag() }}
  8.         <h1 >博客管理员登录</h1>
  9.         {{ form.username.label }}
  10.         {{ form.username(, placeholder="输入用户名") }}
  11.         {{ form.password.label }}
  12.         {{ form.password(, placeholder="输入密码") }}
  13.         
  14.         {{ form.submit() }}
  15.     </form>
  16. {% endblock %}
复制代码
7、添加路由追踪

routes/user_route.py
  1. ······
  2. @app.route('/login.html', methods=['GET', 'POST'])
  3. def login_page():
  4.     form = LoginForm()
  5.     if form.validate_on_submit():
  6.         result = UserService().do_login(username=form.username.data, password=form.password.data)
  7.         if result:
  8.             flash(f'欢迎{form.username.data}回来',category='success')
  9.             return redirect(url_for('home_page'))
  10.         else:
  11.             flash(f'用户名或密码错误!',category='error')
  12.     return render_template('login.html', form=form)
复制代码
8、完成UserService的登岸支持

service/user_service
  1. from sqlalchemy import Select
  2. from models.user import User
  3. from routes import db
  4. from flask_login import login_user
  5. class UserService:
  6.     def do_login(self, username: str, password: str)-> bool:
  7.         query = Select(User).where(User.username == username)
  8.         attempted_user = db.session.scalar(query)
  9.         if attempted_user and attempted_user.check_password_correction(
  10.             attempted_password=password
  11.         ):
  12.             login_user(attempted_user)
  13.             return True
  14.         return False
复制代码

五、登岸错误处理和退出

1、增加显示提示的页面组件

templates/base.html
  1.     {% with messages = get_flashed_messages(with_categories=true) %}
  2.      {% if messages %}
  3.         {% for category, message in messages %}
  4.             
  5.                 {{ message }}
  6.                 <button type="button"  data-bs-dismiss="alert" aria-label="Close"></button>
  7.             
  8.         {% endfor %}
  9.     {% endif %}
  10.     {% endwith %}
复制代码
2、添加路由

routes/user_routes.py
  1. @app.route('/logout.html')
  2. def logout_page():
  3.     logout_user()
  4.     return redirect(url_for('home_page'))
复制代码
3、显示按钮

templates/base.html
  1.         ·····
  2.                 {% if current_user.is_authenticated %}
  3.         <ul >
  4.             <li >
  5.                <a  target="_blank" href="https://www.cnblogs.com/#">发布新文章</a>
  6.             </li>
  7.             <li >
  8.                <a  target="_blank" href="https://www.cnblogs.com/{{ url_for('logout_page') }}">退出</a>
  9.             </li>
  10.         </ul>
  11.         {% else %}
  12.                 ·····
复制代码




六、发布文章

1、定义表单类

forms/article_form.py
  1. from flask_wtf import FlaskForm
  2. from wtforms import StringField, SubmitField, HiddenField, TextAreaField
  3. from wtforms.validators import DataRequired
  4. class ArticleForm(FlaskForm):
  5.     title = StringField(label="标题:", validators=[DataRequired()])
  6.     content = TextAreaField(label="内容:", validators=[DataRequired()])
  7.     submit = SubmitField(label="保持")
复制代码
2、定义添加文章表单页面

templates/editarticle.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3. 博客主页
  4. {% endblock %}
  5. {% block content %}
  6.     <form method="POST" >
  7.         {{ form.hidden_tag() }}
  8.         <h1 >添加新文章</h1>
  9.         
  10.             {{ form.title.label() }}
  11.             {{ form.title(, placeholder="请输入文章标题") }}
  12.             {{ form.content.label() }}
  13.             {{ form.content(, placeholder="请输入文章内容") }}
  14.         </br>
  15.         {{ form.submit()}}
  16.     </form>
  17. {% endblock %}
复制代码
3、实现添加文章的service方法

service/article_service.py
  1. class ArticleService:
  2. ·····
  3.     def create_article(self, article: Article):
  4.         db.session.add(article)
  5.         db.session.commit()
  6.         return article
复制代码
4、添加文章的路由处理

routes/admin_routes.py
  1. from flask import render_template, url_for, redirect,flash
  2. from flask_login import login_required
  3. from forms.article_form import ArticleForm
  4. from models.article import Article
  5. from routes import app
  6. from services.article_service import ArticleService
  7. @app.route('/createarticle.html', methods=['GET','POST'])
  8. @login_required
  9. def create_article_page():
  10.     form = ArticleForm()
  11.     if form.validate_on_submit():
  12.         new_article = Article()
  13.         new_article.title = form.title.data
  14.         new_article.content = form.content.data
  15.         try:
  16.             ArticleService().create_article(new_article)
  17.             flash(message=f'发布文章完成', category='success')
  18.             return redirect(url_for('home_page'))
  19.         except Exception as error:
  20.             flash(message=f'发布文章失败: {error}', category='danger')
  21.     return render_template(template_name_or_list='editarticle.html', form=form)
复制代码
七、美化主页与修改文章

1、美化主页

templates/index.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3. 博客主页
  4. {% endblock %}
  5. {% block content %}
  6.     <--! 拼写错误:在 index.html 文件中,你在循环部分写成了 acticles,应该是 articles。这个拼写错误会导致循环内容无法正确显示。 -->
  7.     {% for article in articles %}
  8.    
  9.         
  10.             <ul >
  11.                 <li >
  12.                     <a  target="_blank" href="https://www.cnblogs.com/article/{{ article.id }}.html" >{{ article.title }}</a>
  13.                 </li>
  14.                 {% if current_user.is_authenticated %}
  15.                 <li >
  16.                     <small >
  17.                         <a  target="_blank" href="https://www.cnblogs.com/editartical/{{ article.id }}.html">编辑</a>
  18.                     </small>
  19.                 </li>
  20.                 {% endif %}
  21.             </ul>
  22.         
  23.         
  24.             <p >
  25.                 <a  target="_blank" href="https://www.cnblogs.com/article/{{ article.id }}.html">{{ article.content }}</a>
  26.             </p>
  27.             <ul >
  28.                 <small >发布时间:{{ article.create_time }}</small>
  29.             </ul>
  30.         
  31.    
  32.     {% endfor %}
  33. {% endblock %}
复制代码

2、编辑文章功能

文章发布错误美化

route/admin_route.py
  1. @app.route('/createarticle.html', methods=['GET','POST'])
  2. @login_required
  3. def create_article_page():
  4.     form = ArticleForm()
  5.     if form.validate_on_submit():
  6.         new_article = Article()
  7.         new_article.title = form.title.data
  8.         new_article.content = form.content.data
  9.         try:
  10.             article, error_msg = ArticleService().create_article(new_article)
  11.             if error_msg:
  12.                 flash(message=f'发布文章错误', category='danger')
  13.             else:
  14.                 flash(message=f'发布文章完成', category='success')
  15.                 return redirect(url_for('home_page'))
  16.         except Exception as error:
  17.             flash(message=f'发布文章失败: {error}', category='danger')
  18.     return render_template(template_name_or_list='editarticle.html', form=form)
复制代码

文章编辑

route/admin_route.py
  1. ····
  2. # 发布文章
  3. @app.route('/createarticle.html', methods=['GET','POST'])
  4. @login_required
  5. def create_article_page():
  6.     form = ArticleForm()
  7.     if form.validate_on_submit():
  8.         new_article = Article()
  9.         new_article.title = form.title.data
  10.         new_article.content = form.content.data
  11.         try:
  12.             article, error_msg = ArticleService().create_article(new_article)
  13.             if error_msg:
  14.                 flash(message=f'发布文章错误:{error_msg}', category='danger')
  15.             else:
  16.                 flash(message=f'发布文章完成', category='success')
  17.                 return redirect(url_for('home_page'))
  18.         except Exception as error:
  19.             flash(message=f'发布文章失败: {error}', category='danger')
  20.     return render_template(template_name_or_list='editarticle.html', form=form)
  21. # 更新文章
  22. @app.route('/editarticle/<article_id>.html', methods=['GET','POST'])
  23. @login_required
  24. def edit_article_page(article_id: str):
  25.     form = ArticleForm()
  26.     if request.method == 'GET':
  27.         try:
  28.             article = ArticleService().get_article(int(article_id))
  29.             if not article:
  30.                 flash(message=f'修改的文章不存在', category='danger')
  31.                 return redirect(url_for('home_page'))
  32.             else:
  33.                 form.title.data = article.title
  34.                 form.content.data = article.content
  35.         except Exception as ex:
  36.             flash(massage=f'提取文件失败: {ex}', category='danger')
  37.             return redirect(url_for('home_page'))
  38.     if form.validate_on_submit():
  39.         try:
  40.             updated_article = Article()
  41.             updated_article.id = int(article_id)
  42.             updated_article.title = form.title.data
  43.             updated_article.content = form.content.data
  44.             article, error_msg = ArticleService().update_article(updated_article)
  45.             if error_msg:
  46.                 flash(message=f'更新文章失败', category='danger')
  47.             else:
  48.                 flash(message=f'更新文章成功', category='success')
  49.                 return redirect(url_for('home_page'))
  50.             return redirect(url_for('home_page'))
  51.         except Exception as error:
  52.             flash(message=f'发布文章失败: {error}', category='danger')
  53.     return render_template(template_name_or_list='editarticle.html', form=form)
复制代码
route/admin_service.py
  1. ····
  2.         # 发布文章对数据库进行比对
  3.     def create_article(self, article: Article):
  4.         query = Select(Article).where(Article.title == article.title)
  5.         # db.session.scalar和 db.session.execute。这里使用execute 有问题,无法判断是否查询到数据 所以使用scalar
  6.         exit_article = db.session.scalar(query)
  7.         if exit_article:
  8.             return article, '同标题的文章已存在'
  9.         db.session.add(article)
  10.         db.session.commit()
  11.         return article, None
  12.     # 更新文章
  13.     def update_article(self, article: Article):
  14.         exit_article = db.session.get(Article, article.id)
  15.         if not exit_article:
  16.             return article, '文章不存在'
  17.         # TODO: 检查同标题文章是否存在
  18.         qury = Select(Article).where(and_(Article.title == article.title, Article.id != article.id))
  19.         same_title_article = db.session.scalar(qury)
  20.         if same_title_article :
  21.             return article, '更新同标题的文章已存在'
  22.         exit_article.title = article.title
  23.         exit_article.content = article.content
  24.         exit_article.update_time = func.now()
复制代码

动态修改编译页面的文章

route/admin_route.py
  1. ····
  2. # 发布文章
  3. @app.route('/createarticle.html', methods=['GET','POST'])
  4. @login_required
  5. def create_article_page():
  6. ·····
  7.         # 通过传递 is_edit参数判断编辑/更新
  8.     return render_template(template_name_or_list='editarticle.html', form=form, is_edit=False)
  9. # 更新文章
  10. @app.route('/editarticle/<article_id>.html', methods=['GET','POST'])
  11. @login_required
  12. def edit_article_page(article_id: str):
  13. ····
  14.         # 通过传递 is_edit参数判断编辑/更新
  15.     return render_template(template_name_or_list='editarticle.html', form=form, is_edit=True)
复制代码
templates/editarticle.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3.     博客主页-
  4.     {% if is_edit %}
  5.         编辑文章
  6.     {% else %}
  7.         添加新文章
  8.     {% endif %}
  9. {% endblock %}
  10. {% block content %}
  11.     <form method="POST" >
  12.         {{ form.hidden_tag() }}
  13.         <h1 >
  14.             {% if is_edit %}
  15.                 编辑文章
  16.             {% else %}
  17.                 添加新文章
  18.             {% endif %}
  19.             </h1>
  20.             
  21.             {{ form.title.label() }}
  22.             {{ form.title(, placeholder="请输入文章标题") }}
  23.             {{ form.content.label() }}
  24.             {{ form.content(, placeholder="请输入文章内容") }}
  25.         </br>
  26.         {{ form.submit()}}
  27.     </form>
  28. {% endblock %}
复制代码


八、删除文章

1、增加删除功能按钮

templates/index.html
  1.             ·····
  2.                                 {% if current_user.is_authenticated %}
  3.             ·····
  4.                 <li  >
  5.                     <small >
  6.                         <a  data-bs-toggle="modal" data-bs-target="#Mdal-DeleteConfirm-{{ article.id }}">删除</a>
  7.                     </small>
  8.                 </li>
  9.                 {% endif %}
复制代码
2、定义删除文章表单类

新增forms/delete_article_form.py
  1. from flask_wtf import FlaskForm
  2. from wtforms import HiddenField, SubmitField
  3. from wtforms.validators import DataRequired
  4. class DeleteArticleForm(FlaskForm):
  5.     article_id = HiddenField(validators=[DataRequired()])
  6.     submit = SubmitField(label='删除')
复制代码
3、增加确认删除对话框

新增templates/includes/article_modals.html
  1.     <dev >
  2.         
  3.             
  4.                 <h5  id="deleteModalLabel">{{ article.title }}</h5>
  5.                 <button type="button"  data-bs-dismiss="modal" aria-label="Close"></button>
  6.             
  7.             <form method="POST">
  8.                 {{ delete_article_form.csrf_token }}
  9.                 {{ delete_article_form.article_id(value=article.id) }}
  10.                
  11.                     <h4 >确定要删除"{{ article.title }}"吗?</h4>
  12.                
  13.                
  14.                     <button type="button"  data-bs-dismiss="modal">取消</button>
  15.                     <button type="button" >确定</button>
  16.                
  17.             </form>
  18.         
  19.     </dev>
复制代码
4、引入确认删除对话框

templates/index.html
  1.         
  2.     {% for article in articles %}
  3.       
  4.     {% if current_user.is_authenticated %}
  5.         {% include 'includes/article_modals.html' %}
  6.     {% endif %}
复制代码
5、在service类中添加删除文章的业务逻辑

service/article_service.py
  1. ·······
  2.         def delete_article(self, article_id: int):
  3.         article = db.session.get(Article, article_id)
  4.         if article:
  5.             db.session.delete(article)
  6.             db.session.commit()
  7.             return article, None
  8.         else:
  9.             return False, '文章不存在'
复制代码
6、路由处理中添加删除逻辑

routes/user_routes.py
  1. @app.route('/', methods=['GET', 'POST'])
  2. @app.route('/index.html', methods=['GET', 'POST'])
  3. def home_page():
  4.     if current_user.is_authenticated:
  5.         delete_article_form = DeleteArticleForm()
  6.         if delete_article_form.validate_on_submit():
  7.             if delete_article_form.validate_on_submit():
  8.                 result, error = ArticleService().delete_article(int(delete_article_form.article_id.data))
  9.                 if result:
  10.                     flash(message=f'删除文章成功', category='success')
  11.                     return redirect(url_for('home_page'))
  12.                 else:
  13.                     flash(message=f'删除文章成功', category='danger')
  14.         articles = ArticleService().get_articles()
  15.         if current_user.is_authenticated:
  16.             return render_template(template_name_or_list='index.html', articles=articles, delete_article_form=delete_article_form)
  17.         return render_template(template_name_or_list='index.html', articles=articles)
复制代码


九、引入Markdown来显示文章

1、下载showdownjs

https://github.com/showdownjs/showdown/tree/master/dist
下载 showdown.min.js 和 showdown.min.js.map 文件夹;放置 assets/plugins/showdownjs-2.0.0
2、引入showdownjs与自定义markdown的一些显示样式

templates/base.html
  1.         ·····
  2.        
  3.    
  4.         ·····
复制代码
3、调试文章显示页面的内容支持markdown

templates/article.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3. 博客 -{{ article.title }}
  4. {% endblock %}
  5. {% block content %}
  6. <textarea id="article_content" >{{ article.content }}</textarea>
  7.     <h4><p  >{{ article.title }}</p></h4>
  8.     <p  >最后更新: {{ article.update_time }}</p>
  9.     <p id="article_viewer"></p>
  10. "
  11. {% endblock %}
复制代码
4、编写本身的js来使用markdown

/assets/js/article.js
  1. $(function (){
  2.     var converter = new showdown.Converter();
  3.     var article_html = converter.makeHtml($('#article_content').val())
  4.     $('#article_content').html(article_html)
  5. })
复制代码


十、编辑时预览文章

1、修改编译页面

templates/editartcile.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3.     博客主页-
  4.    
  5.     {% if is_edit %}
  6.         编辑文章
  7.     {% else %}
  8.         添加新文章
  9.     {% endif %}
  10. {% endblock %}
  11. {% block content %}
  12.     <form method="POST" >
  13.         {{ form.hidden_tag() }}
  14.         <h1 >
  15.             
  16.             {% if is_edit %}
  17.                 编辑文章
  18.             {% else %}
  19.                 添加新文章
  20.             {% endif %}
  21.             </h1>
  22.             
  23.             {{ form.title.label() }}
  24.             {{ form.title(, placeholder="请输入文章标题") }}
  25.             
  26.                
  27.                     {{ form.content.label() }}
  28.                     {{ form.content(, placeholder="请输入文章内容") }}
  29.                      </br>
  30.                     {{ form.submit()}}
  31.                     <a target="_blank" href="https://www.cnblogs.com/#" id="article_preview_btn" >预览</a>
  32.                
  33.                
  34.                     文章内容:
  35.                     
  36.                         
  37.                     
  38.                
  39.             
  40.     </form>
  41. {% endblock %}
复制代码
2、编写js来渲染markdown

/assets/js/editarticle.js
  1. $(function (){
  2.     $('#article_preview_btn').click(function (){
  3.         var converter = new showdown.Converter();
  4.         var content_html = converter.makeHtml($('#content').val());
  5.         $('#article_preview').html(content_html);
  6.     });
  7. });
复制代码

十一、消除明文暗码

使用 bcrypt  做加密: https://pypi.org/project/bcrypt/
1、安装 brcypt 模块
  1. $ pip install bcrypt
复制代码
requirements.txt
  1. Flask==3.0.0mysqlclient==2.2.0
  2. SQLAlchemy==2.0.23
  3. Flask-SQLAlchemy==3.1.1flask-WTF==1.2.1
  4. flask-login==0.6.3bcrypt==4.1.1
复制代码
2、修改数据库明文暗码
  1. >>> import bcrypt
  2. >>> pd='admin'
  3. >>> hashed = bcrypt.hashpw(pd.encode(), bcrypt.gensalt())
  4. >>> print(hashed.decode('utf-8'))
  5. $2b$12$U3PhlQenadR1WCb63.1Rxu83TrnFxv884YpPOPjYZI0wzbl.oG4Iq
复制代码

3、修改认证方式
  1.         ·····
  2.     def check_password_correction(self, attempted_password):
  3.         password_hashed = self.password.encode()
  4.         return bcrypt.checkpw(attempted_password.encode(), password_hashed)   
复制代码
登录免密已然是admin/admin , 但数据存储的暗码以及h加密了成字符串了

十二、实现图片上传

1、上传页

forms/image_upload_form.py
  1. from flask_wtf import FlaskForm
  2. from flask_wtf.file import FileField, FileRequired
  3. from wtforms import SubmitField
  4. class ImageUploadForm(FlaskForm):
  5.     image_file = FileField(label="选择图片", validators=[FileRequired()])
  6.     submit = SubmitField(label="上传")
复制代码
templates/images.html
  1. {% extends 'base.html' %}
  2. {% block title %}
  3. 博客主页
  4. {% endblock %}
  5. {% block content %}
  6.     <form method="POST"  enctype="multipart/form-data">
  7.         {{ form.hidden_tag() }}
  8.         <h1 >
  9.             上传页面
  10.         </h1>
  11.         
  12.         {{ form.image_file.label }}
  13.         {{ form.image_file() }}
  14.          
  15.         {{ form.submit() }}
  16.     </form>
  17. {% endblock %}
复制代码
2、工具类

commom/profile.py
定义了一个Profile类,用于获取图像文件的路径。
  1. from pathlib import Path
  2. class Profile:
  3.     __images_path = None
  4.     @staticmethod
  5.     def get_images_path():
  6.         home_path = Path(__file__).parent.parent
  7.         images_path = home_path.joinpath("data/images")
  8.         if not images_path.exists():
  9.             images_path.mkdir(parents=True)
  10.         return images_path
复制代码
common/utils.py
获取文件名和扩展名,并天生唯一的生存文件路径。
  1. from pathlib import Path
  2. def get_file_name_parts( filename: str):
  3.     pos = filename.rfind('.')
  4.     if pos == -1:
  5.         return filename, ''
  6.     return filename[:pos], filename[pos + 1:]
  7. def get_save_filepaths(file_path: Path, filename: str):
  8.     save_file = file_path.joinpath(filename)
  9.     if not save_file.exists():
  10.         return save_file
  11.     name, ext = get_file_name_parts(filename)
  12.     for index in range(1, 100):
  13.         save_file = file_path.joinpath(f'{name}_{index}.{ext}')
  14.         if not save_file.exists():
  15.             return save_file
  16.     return file_path.joinpath(f'{name}_override.{ext}')
复制代码
3、路由上传页面

routes/admin_routes.py
  1.         ·····
  2. @app.route('/images.html', methods=['GET', 'POST'])
  3. @login_required
  4. def images_page():
  5.     form = ImageUploadForm()
  6.     if form.validate_on_submit():
  7.         image_file = form.image_file.data
  8.         images_path = Profile.get_images_path()
  9.         image_filename = secure_filename(image_file.filename)
  10.         image_fullpath = utils.get_save_filepaths(images_path, image_filename)
  11.         image_file.save(image_fullpath)
  12.         flash(message=f'上传图片成功: {image_fullpath}', category='success')
  13.     return render_template(template_name_or_list='images.html', form=form)
复制代码


十三、实现图片下载

1、导航栏添加“图片管理”

templates/base.html
  1.     ·····
  2.            {% if current_user.is_authenticated %}
  3.         <ul >
  4.            
  5.            <li >
  6.                <a  target="_blank" href="https://www.cnblogs.com/{{ url_for('images_page') }}">图片管理</a>
  7.             </li>
  8.             <li >
  9.                <a  target="_blank" href="https://www.cnblogs.com/{{ url_for('create_article_page') }}">发布新文章</a>
  10.             </li>
  11.             <li >
  12.                <a  target="_blank" href="https://www.cnblogs.com/{{ url_for('logout_page') }}">退出</a>
  13.             </li>
  14.         </ul>
  15.         {% else %}
  16.         ······
复制代码
2、访问服务端图片

routes/user_routes.py
  1. ·····
  2. @app.route('/image/<image_filename>')
  3. def download_image(image_filename: str):
  4.     image_path = Profile.get_images_path()
  5.     image_filepath = image_path.joinpath(image_filename)
  6.     if not image_filepath:
  7.         return abort(404)
  8.     return send_from_directory(directory=image_filepath, path=image_filename)
复制代码
新增 service/image_service.py
  1. from common.profile import Profile
  2. class ImageService:
  3.     def get_image_filename_list(self):
  4.         image_paht = Profile.get_images_path()
  5.         filename_list = []
  6.         if image_paht.exists():
  7.             for item in image_paht.iterdir():
  8.                 if item.is_file():
  9.                     filename_list.append(item.name)
  10.         return filename_list
复制代码
3、图片展示

routes/admin_routes.py
  1. @app.route('/images.html', methods=['GET', 'POST'])
  2. @login_required
  3. def images_page():
  4.     form = ImageUploadForm()
  5.     if form.validate_on_submit():
  6.         image_file = form.image_file.data
  7.         images_path = Profile.get_images_path()
  8.         image_filename = secure_filename(image_file.filename)
  9.         image_fullpath = utils.get_save_filepaths(images_path, image_filename)
  10.         image_file.save(image_fullpath)
  11.         flash(message=f'上传图片成功: {image_fullpath}', category='success')
  12.     image_filenames = ImageService().get_image_filename_list()
  13.     return render_template(template_name_or_list='images.html', form=form, image_filenames=image_filenames)
复制代码
templates/images.html
  1.     <hr/>
  2.    
  3.         {% if image_filenames %}
  4.             {% for image_file in image_filenames %}
  5.                
  6.                     <b>https://www.cnblogs.com/image/{{ image_file }}</b>
  7.                     <img src="https://www.cnblogs.com/image/{{ image_file }}"   height="200px;"/>
  8.                         <a target="_blank" href="https://www.cnblogs.com/image/{{ image_file }}" >查看</a>
  9.                         <a  data-bs-toggle="modal" data-bs-target="#Modal-DeleteConfirm-{{ image_file }}">删除</a>
  10.                
  11.             {% endfor %}
  12.         {% endif %}
  13.    
复制代码


十四、实现Docker部署

1、代码改造

main.py 主文件
  1. import bcrypt
  2. from sqlalchemy import inspect
  3. from routes import app, db
  4. def init_db():
  5.     with app.app_context():
  6.         inspector = inspect(db.engine)
  7.         if not inspector.has_table('users'):
  8.             from models.user import User
  9.             from models.article import Article
  10.             db.create_all()
  11.             password_hashed = bcrypt.hashpw('admin'.encode(), bcrypt.gensalt())
  12.             user = User(username="root", password=password_hashed.decode('utf-8'), fullname='root', description='')
  13.             db.session.add(user)
  14.             db.session.commit()
  15. if __name__ == '__main__':
  16.     init_db()
  17.     app.run(host='0.0.0.0', debug=True, port=8080)
复制代码
数据库连接方式
route/__init__.py
  1. import os
  2. from flask import Flask
  3. from flask_login import LoginManager
  4. from flask_sqlalchemy import SQLAlchemy
  5. MYSQL_HOST = os.getenv("MYSQL_HOST", "localhost")
  6. MYSQL_PORT = os.getenv("MYSQL_PORT", "3306")
  7. MYSQL_USER = os.getenv("MYSQL_USER", "root")
  8. MYSQL_PWD = os.getenv("MYSQL_PWD", "test")
  9. MYSQL_DB = os.getenv("MYSQL_DB", "testdb")
  10. app = Flask(__name__,
  11.             template_folder='../templates',
  12.             static_folder='../assets',
  13.             static_url_path='/assets')
  14. app.config['SQLALCHEMY_DATABASE_URI'] = f'mysql+mysqldb://{MYSQL_USER}:{MYSQL_PWD}@{MYSQL_HOST}:{MYSQL_PORT}/{MYSQL_DB}'
  15. app.config['SECRET_KEY'] = 'ec9439cfc6c796ae2029594d'
  16. db = SQLAlchemy(app)
  17. login_manager = LoginManager(app)
  18. from routes import user_routes
  19. from routes import admin_routes
复制代码
2、Dockerfile编写

/Dockerfile
  1. FROM ubuntu
  2. COPY . /opt/myblog/
  3. WORKDIR /opt/myblog/
  4. RUN apt-get update
  5. RUN apt-get install -y python3.9 python3-pip
  6. RUN apt-get install -y pkg-config
  7. RUN apt-get install -y libmysqlclient-dev
  8. RUN pip3 install --upgrade pip
  9. RUN pip3 install -r requirements.txt
  10. ENV PYTHONPATH=/opt/myblog/
  11. ENTRYPOINT ["python3", "main.py"]
复制代码
3、docker-compose.yaml编写
  1. version: '3.8'
  2. services:
  3.   myblog_server:
  4.     build: .
  5.     image: myblog
  6.     container_name: myblog_server
  7.     ports:
  8.       - "80:8080"
  9.     links:
  10.       - mysql_server
  11.     environment:
  12.       MYSQL_HOST: mysql_server
  13.       MYSQL_DB: myblog_db
  14.       MYSQL_USER: root
  15.       MYSQL_PWD: nevertellyou
  16.     volumes:
  17.       - /opt/myblog_data:/opt/myblog/data
  18.     depends_on:
  19.       mysql_server:
  20.           condition: service_healthy
  21.   mysql_server:
  22.     image: mysql:8.0
  23.     container_name: mysql_server
  24.     volumes:
  25.       - /opt/mysql:/var/lib/mysql
  26.     environment:
  27.       MYSQL_ROOT_PASSWORD: nevertellyou
  28.       MYSQL_DATABASE: myblog_db
  29.     healthcheck:
  30.       test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
  31.       timeout: 20s
  32.       retries: 10
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

去皮卡多

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表