中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁(yè) > news >正文

kol合作推廣seo外鏈?zhǔn)鞘裁?/h1>

kol合作推廣,seo外鏈?zhǔn)鞘裁?網(wǎng)站中英切換實(shí)例,百度seo排名主要看啥Flask學(xué)習(xí)筆記_異步論壇(四) 1.配置和數(shù)據(jù)庫(kù)鏈接1.exts.py里面實(shí)例化sqlalchemy數(shù)據(jù)庫(kù)2.config.py配置app和數(shù)據(jù)庫(kù)信息3.app.py導(dǎo)入exts和config并初始化到app上 2.創(chuàng)建用戶模型并映射到數(shù)據(jù)庫(kù)1.models/auth.py創(chuàng)建用戶模型2.app.py導(dǎo)入模型并用flask-mi…

Flask學(xué)習(xí)筆記_異步論壇(四)

  • 1.配置和數(shù)據(jù)庫(kù)鏈接
    • 1.exts.py里面實(shí)例化sqlalchemy數(shù)據(jù)庫(kù)
    • 2.config.py配置app和數(shù)據(jù)庫(kù)信息
    • 3.app.py導(dǎo)入exts和config并初始化到app上
  • 2.創(chuàng)建用戶模型并映射到數(shù)據(jù)庫(kù)
    • 1.models/auth.py創(chuàng)建用戶模型
    • 2.app.py導(dǎo)入模型并用flask-migrate管理數(shù)據(jù)庫(kù)
    • 3.命令行migrate三部曲將模型映射到數(shù)據(jù)庫(kù)
  • 3.登錄與注冊(cè)頁(yè)面的get請(qǐng)求
    • 1.首先寫登錄和注冊(cè)的前端頁(yè)面
    • 2.寫它們的view藍(lán)圖并導(dǎo)入到__init__中
    • 3.藍(lán)圖注冊(cè)到app
  • 4.郵箱驗(yàn)證功能
    • 1.郵箱驗(yàn)證
    • 2.使用celery異步發(fā)送郵箱驗(yàn)證網(wǎng)絡(luò)請(qǐng)求
    • 3.使用flask-caching緩存驗(yàn)證碼并驗(yàn)證
    • 4.重構(gòu)restful API
  • 5.注冊(cè)頁(yè)面的post請(qǐng)求
    • 5.1注冊(cè)頁(yè)面郵箱驗(yàn)證碼的ajax請(qǐng)求
    • 5.2注冊(cè)頁(yè)面的圖形驗(yàn)證碼功能
    • 5.3注冊(cè)頁(yè)面的post提交
  • 6.登錄頁(yè)面的post請(qǐng)求
  • 7.首頁(yè)
    • 7.1首頁(yè)狀態(tài)切換功能
    • 7.2首頁(yè)設(shè)置功能
    • 7.3首頁(yè)頭像功能
    • 7.4個(gè)性簽名功能
  • 8.帖子相關(guān)設(shè)置
    • 8.1帖子板塊
    • 8.2發(fā)布帖子
    • 8.3帖子詳情頁(yè)
    • 8.4首頁(yè)帖子列表

flask 系列的代碼筆記都放在了 倉(cāng)庫(kù)。

1.配置和數(shù)據(jù)庫(kù)鏈接

1.exts.py里面實(shí)例化sqlalchemy數(shù)據(jù)庫(kù)

from flask_sqlalchemy import SQLAlchemy
db=SQLAlchemy()

2.config.py配置app和數(shù)據(jù)庫(kù)信息

#1.app配置
DEBUG=True
#2.數(shù)據(jù)庫(kù)配置
DB_USERNAME="root"
DB_PASSWORD="1xxxx"
DB_HOST="127.0.0.1"
DB_PORT="3306"
DB_NAME="aforum"
DB_URI="mysql+pymysql://%s:%s@%s:%s/%s?charset=utf8mb4" % (DB_USERNAME,DB_PASSWORD,DB_HOST,DB_PORT,DB_NAME)
SQLALCHEMY_DATABASE_URI=DB_URI
SQLALCHEMY_TRACK_MODIFIER=False

3.app.py導(dǎo)入exts和config并初始化到app上

from flask import Flask
import config
from exts import db
app=Flask(__name__)#1.實(shí)例化app
app.config.from_object(config)#2.config配置文件綁定到app
db.init_app(app)#3.數(shù)據(jù)庫(kù)綁定到app
@app.route('/')
def index():return "hello"
if __name__=="__main__":app.run()

2.創(chuàng)建用戶模型并映射到數(shù)據(jù)庫(kù)

1.models/auth.py創(chuàng)建用戶模型

from exts import db
import shortuuid
from datetime import datetime
from werkzeug.security import generate_password_hash,check_password_hashclass UserModel(db.Model):__tablename__ = "user"id = db.Column(db.String(100), primary_key=True, default=shortuuid.uuid)email = db.Column(db.String(50), unique=True, nullable=False)username = db.Column(db.String(50), nullable=False)_password = db.Column(db.String(200), nullable=False)avatar = db.Column(db.String(100))signature = db.Column(db.String(100))join_time = db.Column(db.DateTime, default=datetime.now)is_staff = db.Column(db.Boolean, default=False)is_active = db.Column(db.Boolean, default=True)def __init__(self, *args, **kwargs):if "password" in kwargs:self.password = kwargs.get('password')kwargs.pop("password")super(UserModel, self).__init__(*args, **kwargs)@propertydef password(self):return self._password@password.setterdef password(self, newpwd):self._password = generate_password_hash(newpwd)def check_password(self,rawpwd):return check_password_hash(self.password, rawpwd)

2.app.py導(dǎo)入模型并用flask-migrate管理數(shù)據(jù)庫(kù)

from flask_migrate import Migrate
from models import auth
migrate=Migrate(app,db)

3.命令行migrate三部曲將模型映射到數(shù)據(jù)庫(kù)

在app.py文件的目錄下

flask db init
flask db migrate
flask db upgrade

3.登錄與注冊(cè)頁(yè)面的get請(qǐng)求

1.首先寫登錄和注冊(cè)的前端頁(yè)面

#1。首先抽出base.html文件
<html>
<head><meta charset="utf-8"><script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script><link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"><script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script><script src="{{ url_for('static', filename='front/js/zlajax.js') }}"></script><script src="{{ url_for('static', filename='front/js/zlparam.js') }}"></script><link rel="stylesheet" href="{{ url_for('static', filename='front/css/front_base.css') }}"><meta name="viewport" content="width=device-width, initial-scale=1"><title>{% block title %}{% endblock %}</title>{% block head %}{% endblock %}
</head><body><nav class="navbar navbar-default"><div class="container"><div class="navbar-header"><button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"><span class="sr-only">Toggle navigation</span><span class="icon-bar"></span><span class="icon-bar"></span><span class="icon-bar"></span></button><a class="navbar-brand" href="/">論壇</a></div><!-- Collect the nav links, forms, and other content for toggling --><div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"><ul class="nav navbar-nav"><li class="active"><a href="/">首頁(yè)<span class="sr-only">(current)</span></a></li></ul><form class="navbar-form navbar-left"><div class="form-group"><input type="text" class="form-control" placeholder="請(qǐng)輸入關(guān)鍵字"></div><button type="submit" class="btn btn-default">搜索</button></form><ul class="nav navbar-nav navbar-right">{% if user %}<li class="dropdown"><a href="#" class="dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">{{ user.username }}<span class="caret"></span></a><ul class="dropdown-menu" aria-labelledby="dropdownMenu1"><li><a href="{{ url_for('front.cms') }}">后臺(tái)管理</a></li><li><a href="{{ url_for('front.setting') }}">設(shè)置</a></li><li><a href="{{ url_for('front.logout') }}">注銷</a></li></ul></li>{% else %}<li><a href="{{ url_for('front.login') }}">登錄</a></li><li><a href="{{ url_for('front.register') }}">注冊(cè)</a></li>{% endif %}</ul></div><!-- /.navbar-collapse --></div><!-- /.container-fluid --></nav><div class="main-container">{% block body %}{% endblock %}</div>
</body></html>
#2.login.html文件
{% extends "front/base.html" %}{% block title %}登錄
{% endblock %}{% block head %}<link rel="stylesheet" href="{{ url_for('static', filename='front/css/signbase.css') }}"><script src="{{ url_for('static', filename='front/js/login.js') }}"></script>
{% endblock %}
{% block body %}<div class="outer-box"><div class="logo-box"><a href="/"><img src="{{ url_for('static', filename='front/images/logo.png') }}" alt=""></a></div><h2 class="page-title">登錄</h2><div class="sign-box"><div class="form-group"><input type="text" class="form-control" name="email" placeholder="郵箱"></div><div class="form-group"><input type="password" class="form-control" name="password" placeholder="密碼"></div><div class="checkbox"><label><input type="checkbox" name="remember" value="1">記住我</label></div><div class="form-group"><button class="btn btn-warning btn-block" id="submit-btn">立即登錄</button></div><div class="form-group"><a href="#" class="signup-link">沒有賬號(hào)?立即注冊(cè)</a><a href="#" class="resetpwd-link" style="float:right;">找回密碼</a></div></div></div>
{% endblock %}
#3.register.html文件
{% extends "front/base.html" %}{% block title %}注冊(cè)
{% endblock %}{% block head %}<link rel="stylesheet" href="{{ url_for('static', filename='front/css/signbase.css') }}"><script src="{{ url_for('static', filename='front/js/register.js') }}"></script>
{% endblock %}{% block body %}<div class="outer-box"><div class="logo-box"><a href="/"><img src="{{ url_for('static', filename='front/images/logo.png') }}" alt=""></a></div><h2 class="page-title">注冊(cè)</h2><div class="sign-box"><div class="form-group"><div class="input-group"><input type="email" class="form-control" name="email" placeholder="郵箱"><span class="input-group-btn"><button id="email-captcha-btn" class="btn btn-default">發(fā)送驗(yàn)證碼</button></span></div></div><div class="form-group"><input type="text" class="form-control" name="email-captcha" placeholder="郵箱驗(yàn)證碼"></div><div class="form-group"><input type="text" class="form-control" name="username" placeholder="用戶名"></div><div class="form-group"><input type="password" class="form-control" name="password" placeholder="密碼"></div><div class="form-group"><input type="password" class="form-control" name="repeat-password" placeholder="確認(rèn)密碼"></div><div class="form-group"><div class="input-group"><input type="text" class="form-control" name="graph-captcha" placeholder="圖形驗(yàn)證碼"><span class="input-group-addon captcha-addon"><img id="captcha-img" class="captcha-img" src="#" alt=""></span></div></div><div class="form-group"><button class="btn btn-warning btn-block" id="submit-btn">立即注冊(cè)</button></div></div></div>
{% endblock %}

2.寫它們的view藍(lán)圖并導(dǎo)入到__init__中

#1.在apps/front/views.py里面寫藍(lán)圖的視圖函數(shù)
from flask import Blueprint,request,render_template
bp=Blueprint("front",__name__,url_prefix="/")@bp.route('/login/', methods=['GET', 'POST'])
def login():if request.method == 'GET':return render_template('front/login.html')@bp.route('/register/', methods=['GET', 'POST'])
def register():if request.method == 'GET':return render_template('front/register.html')
#2.在apps/front/__init__.py里面導(dǎo)入藍(lán)圖
from .views import bp as front_bp                             

3.藍(lán)圖注冊(cè)到app

#在app.py里面導(dǎo)入藍(lán)圖并注冊(cè)到app上
from apps.front import front_bp
app.register_blueprint(front_bp)

4.郵箱驗(yàn)證功能

1.郵箱驗(yàn)證

#1.在config里面配置郵箱第三方服務(wù)商來發(fā)送郵件
MAIL_SERVER="smtp.qq.com"#發(fā)送驗(yàn)證碼的郵箱服務(wù)器,這里是自己公司的郵箱服務(wù)器
MAIL_PORT='587'#587是tls協(xié)議,465是ssl協(xié)議
MAIL_USE_TLS=True
#MAIL_USE_SSL
MAIL_USERNAME="1xxxc9@qq.com"
MAIL_PASSWORD="wxxbe"
MAIL_DEFAULT_SENDER="11xx@qq.com"
#2.exts里面導(dǎo)入mail
from flask_mail import Mail
mail=Mail()
#3.在app里面把exts里面的mail導(dǎo)入進(jìn)來并綁定到app
from exts import db,mail
mail.init_app(app)
#4.開始在views里面寫發(fā)送郵箱驗(yàn)證碼的視圖函數(shù)
from exts import mail
from flask_mail import Message
from flask importjsonify
import string,random
@bp.get("/email/captcha/")
def email_captcha():email=request.args.get('email')if not email:return jsonify({"code":400,"message":"請(qǐng)先傳入郵箱"})source=list(string.digits)captcha="".join(random.sample(source,6))message=Message(subject="注冊(cè)驗(yàn)證碼",recipients=[email],body="您的注冊(cè)驗(yàn)證碼是:%s" % captcha)try:mail.send(message)except Exception as e:print("郵件發(fā)送失敗")print(e)return jsonify({"code":500,"message":"郵件發(fā)送失敗"})return jsonify({"code":200,"message":"郵件發(fā)送成功"})

2.使用celery異步發(fā)送郵箱驗(yàn)證網(wǎng)絡(luò)請(qǐng)求

celery(分布式任務(wù)隊(duì)列/任務(wù)調(diào)度器)和redis(內(nèi)存數(shù)據(jù)庫(kù))的教程和安裝步驟可以參考學(xué)習(xí)。Broker和Backend都用redis存儲(chǔ)。

pip install gevent
pip install redis
pip install hiredis
啟動(dòng)celery

redis-cli

在這里插入圖片描述

#1.在config中設(shè)置reids的相關(guān)信息
CELERY_BROKER_URL="redis://127.0.0.1:6379/0"#broker
CELERY_RESULT_BACKEND="redis://127.0.0.1:6379/0"#backend
#2.mycelery.py里面定義并添加任務(wù)
from flask_mail import Message
from exts import mail
from celery import Celery# 定義任務(wù)函數(shù)
def send_mail(recipient,subject,body):message = Message(subject=subject,recipients=[recipient],body=body)try:mail.send(message)return {"status": "SUCCESS"}except Exception :return {"status": "FAILURE"}# 創(chuàng)建celery對(duì)象
def make_celery(app):celery = Celery(app.import_name, backend=app.config['CELERY_RESULT_BACKEND'],broker=app.config['CELERY_BROKER_URL'])TaskBase = celery.Taskclass ContextTask(TaskBase):abstract = Truedef __call__(self, *args, **kwargs):with app.app_context():return TaskBase.__call__(self, *args, **kwargs)celery.Task = ContextTaskapp.celery = celery# 添加任務(wù)celery.task(name="send_mail")(send_mail)return celery
#3.在app.py里面將celery綁定到app
from mycelery import make_celery
mycelery=make_celery(app)
#4.在views.py里面利用current_app調(diào)用celery里面的task任務(wù)
from flask import current_app
@bp.get("/email/captcha/")
def email_captcha():email=request.args.get('email')if not email:return jsonify({"code":400,"message":"請(qǐng)先傳入郵箱"})source=list(string.digits)captcha="".join(random.sample(source,6))subject="注冊(cè)驗(yàn)證碼"body="您的注冊(cè)驗(yàn)證碼是:%s"%captchacurrent_app.celery.send_task("send_mail",(email,subject,body))return jsonify({"code":200,"message":"郵件發(fā)送成功"})
#5.在工程目錄下運(yùn)行這個(gè)celery
celery -A app.mycelery worker --loglevel=info -P gevent
#6.訪問這個(gè)視圖函數(shù)就可以成功利用celery進(jìn)行異步任務(wù)調(diào)取

3.使用flask-caching緩存驗(yàn)證碼并驗(yàn)證

flask-caching的相關(guān)教程可以查看博文。

#1.安裝:pip install flask-caching
#2.在config里面寫flask-caching相關(guān)的配置
CACHE_TYPE="RedisCache"
CACHE_DEFAULT_TIMEOUT=300
CACHE_REDIS_HOST="127.0.0.1"
CACHE_REDIS_PORT=6379
#3.在exts里面引入caching
from flask_caching import Cache
cache=Cache()
#4.在app.py里面init
from exts import cache
cache.init_app(app)
#5.在view的視圖函數(shù)里面緩存驗(yàn)證碼
from exts import cache
cache.set(email,captcha)#cache緩存是鍵值對(duì)的形式

4.重構(gòu)restful API

#1.在utils/restful.py里面
# Restful API
from flask import jsonifyclass HttpCode(object):# 響應(yīng)正常ok = 200# 沒有登陸錯(cuò)誤unloginerror = 401# 沒有權(quán)限錯(cuò)誤permissionerror = 403# 客戶端參數(shù)錯(cuò)誤paramserror = 400# 服務(wù)器錯(cuò)誤servererror = 500def _restful_result(code, message, data):return jsonify({ "code": code,"message": message or "", "data": data or {}})def ok(message=None, data=None):return _restful_result(code=HttpCode.ok, message=message, data=data)def unlogin_error(message="沒有登錄!"):return _restful_result(code=HttpCode.unloginerror, message=message, data=None)def permission_error(message="沒有權(quán)限訪問!"):return _restful_result(code=HttpCode.paramserror, message=message, data=None)def params_error(message="參數(shù)錯(cuò)誤!"):return _restful_result(code=HttpCode.paramserror, message=message, data=None)def server_error(message="服務(wù)器開小差啦!"):return _restful_result(code=HttpCode.servererror, message=message or '服務(wù)器內(nèi)部錯(cuò)誤', data=None)
#2.在view視圖函數(shù)里
from utils import restful
return restful.params_error(message="請(qǐng)先傳入郵箱")
return restful.ok(message="郵件發(fā)送成功")

5.注冊(cè)頁(yè)面的post請(qǐng)求

5.1注冊(cè)頁(yè)面郵箱驗(yàn)證碼的ajax請(qǐng)求

#1.在register.html里面引入js文件
<script src="{{ url_for('static', filename='front/js/register.js') }}"></script>
#2.在register.js里面監(jiān)聽(4步),這里引用zlajax是因?yàn)樗詣?dòng)給了csrf-token
var RegisterHandler = function (){\\1.定義了一個(gè)JavaScript對(duì)象}RegisterHandler.prototype.listenSendCaptchaEvent = function (){\\2.包含一個(gè)方法var callback = function (event){// 原生的JS對(duì)象:this => jQuery對(duì)象var $this = $(this);// 阻止默認(rèn)的點(diǎn)擊事件event.preventDefault();var email = $("input[name='email']").val();var reg = /^\w+((.\w+)|(-\w+))@[A-Za-z0-9]+((.|-)[A-Za-z0-9]+).[A-Za-z0-9]+$/;if(!email || !reg.test(email)){alert("請(qǐng)輸入正確格式的郵箱!");return;}zlajax.get({url: "/email/captcha?email=" + email,success: function (result){if(result['code'] == 200){console.log("郵件發(fā)送成功!");// 取消按鈕的點(diǎn)擊事件$this.off("click");// 添加禁用狀態(tài)$this.attr("disabled", "disabled");// 開始倒計(jì)時(shí)var countdown = 60;var interval = setInterval(function (){if(countdown > 0){$this.text(countdown);}else{$this.text("發(fā)送驗(yàn)證碼");$this.attr("disabled", false);$this.on("click", callback);// 清理定時(shí)器clearInterval(interval);}countdown--;}, 1000);}else{var message = result['message'];alert(message);}}})}$("#email-captcha-btn").on("click", callback);
}
RegisterHandler.prototype.run = function (){\\3.方法在run函數(shù)里調(diào)用this.listenSendCaptchaEvent();
}// $(function(){})
$(function (){\\4.實(shí)例化并運(yùn)行var handler = new RegisterHandler();handler.run();
})#3.post請(qǐng)求要用csrf-token,所以在base.html里面引入
<meta name="csrf-token" content="{{csrf_token()}}">
#4.csrf-token需要先安裝:
pip install flask-wtf
#5.在config里面設(shè)置secretkey
SECRET_KEY="FASDFNMLKSDF"
#6.在exts里面引入
from flask_wtf import CSRFProtect
csrf=CSRFProtect()
#6.在app上綁定init
from exts import csrf
csrf.init_app(app)

5.2注冊(cè)頁(yè)面的圖形驗(yàn)證碼功能

#1.在config里面獲取工程的base目錄
import os
BASE_DIR=os.path.dirname(__file__)
#2.在utils目錄下的captcha的init文件里生成圖形驗(yàn)證碼
import random
import string
# Image:一個(gè)畫布
# ImageDraw:一個(gè)畫筆
# ImageFont:畫筆的字體
from PIL import Image,ImageDraw,ImageFontfrom flask import current_app
import os# pip install pillow# Captcha驗(yàn)證碼class Captcha(object):# 生成幾位數(shù)的驗(yàn)證碼number = 4# 驗(yàn)證碼圖片的寬度和高度size = (100,30)# 驗(yàn)證碼字體大小fontsize = 25# 加入干擾線的條數(shù)line_number = 2# 構(gòu)建一個(gè)驗(yàn)證碼源文本SOURCE = list(string.ascii_letters)for index in range(0, 10):SOURCE.append(str(index))#用來繪制干擾線@classmethoddef __gene_line(cls,draw,width,height):begin = (random.randint(0, width), random.randint(0, height))end = (random.randint(0, width), random.randint(0, height))draw.line([begin, end], fill = cls.__gene_random_color(),width=2)# 用來繪制干擾點(diǎn)@classmethoddef __gene_points(cls,draw,point_chance,width,height):chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]for w in range(width):for h in range(height):tmp = random.randint(0, 100)if tmp > 100 - chance:draw.point((w, h), fill=cls.__gene_random_color())# 生成隨機(jī)的顏色@classmethoddef __gene_random_color(cls,start=0,end=255):random.seed()return (random.randint(start,end),random.randint(start,end),random.randint(start,end))# 隨機(jī)選擇一個(gè)字體@classmethoddef __gene_random_font(cls):fonts = ['Courgette-Regular.ttf','LHANDW.TTF','Lobster-Regular.ttf','verdana.ttf']font = random.choice(fonts)fontpath = os.path.join(current_app.config['BASE_DIR'],'utils','captcha',font)# return 'utils/captcha/'+fontreturn fontpath# 用來隨機(jī)生成一個(gè)字符串(包括英文和數(shù)字)@classmethoddef gene_text(cls, number):# number是生成驗(yàn)證碼的位數(shù)return ''.join(random.sample(cls.SOURCE, number))#生成驗(yàn)證碼@classmethoddef gene_graph_captcha(cls):# 驗(yàn)證碼圖片的寬和高width,height = cls.size# 創(chuàng)建圖片# R:Red(紅色)0-255# G:G(綠色)0-255# B:B(藍(lán)色)0-255# A:Alpha(透明度)image = Image.new('RGBA',(width,height),cls.__gene_random_color(0,100))# 驗(yàn)證碼的字體font = ImageFont.truetype(cls.__gene_random_font(),cls.fontsize)# 創(chuàng)建畫筆draw = ImageDraw.Draw(image)# 生成字符串text = cls.gene_text(cls.number)# 獲取字體的尺寸font_width, font_height = font.getsize(text)# 填充字符串draw.text(((width - font_width) / 2, (height - font_height) / 2),text,font= font,fill=cls.__gene_random_color(150,255))# 繪制干擾線for x in range(0, cls.line_number):cls.__gene_line(draw, width, height)# 繪制噪點(diǎn)cls.__gene_points(draw, 10, width, height)return (text,image)
#3.在views里面寫視圖函數(shù)
from utils.captcha import Captcha
import time
from hashlib import md5
from io import BytesIO
from flask import make_response
@bp.route("/graph/captcha/")
def graph_captcha():captcha,image=Captcha.gene_graph_captcha()key=md5((captcha+str(time.time())).encode('utf-8')).hexdigest()cache.set(key,captcha)#cache里面緩存這個(gè)captchabuffer=BytesIO()image.save(buffer,"png")buffer.seek(0)#buffer文件指針指向最開始的位置resp=make_response(buffer.read())resp.content_type="image/png"resp.set_cookie("_graph_captcha_key",key,max_age=3600)#將key值保存到cookie1個(gè)小時(shí)return resp
#4.在register.html里面寫圖片驗(yàn)證碼的src
<img id="captcha-img" class="captcha-img" src="{{url_for('front.graph_captcha')}}" alt="">
#5.實(shí)現(xiàn)點(diǎn)擊圖片重新生成,所以在regist.js里面監(jiān)聽
RegisterHandler.prototype.listenGraphCaptchaEvent = function (){$("#captcha-img").on("click", function (){console.log("點(diǎn)擊了圖形驗(yàn)證碼");var $this = $(this);var src = $this.attr("src");// /graph/captcha// /graph/captcha?sign=Math.random()// 防止一些老的瀏覽器,在兩次url相同的情況下,不會(huì)重新發(fā)送請(qǐng)求,導(dǎo)致圖形驗(yàn)證碼不會(huì)更新let new_src = zlparam.setParam(src, "sign", Math.random())$this.attr("src",new_src);});
}
RegisterHandler.prototype.run = function (){this.listenSendCaptchaEvent();this.listenGraphCaptchaEvent();
}

5.3注冊(cè)頁(yè)面的post提交

#1.在front/forms.py里面進(jìn)行表單驗(yàn)證
from wtforms import Form,ValidationError
from wtforms.fields import StringField
from wtforms.validators import Email,Length,EqualTo
from models.auth import UserModel#對(duì)表單進(jìn)行二次驗(yàn)證
from exts import cache
from flask import request
class BaseForm(Form):@propertydef messages(self):message_list = []if self.errors:for error in self.errors.values():message_list.extend(error)return message_list
class RegisterForm(BaseForm):email=StringField(validators=[Email(message="請(qǐng)輸入正確的郵箱")])email_captcha=StringField(validators=[Length(6,6,message="請(qǐng)輸入6位驗(yàn)證碼")])username=StringField(validators=[Length(3,20,message="請(qǐng)輸入3-20位的用戶名")])password=StringField(validators=[Length(6,20,message="請(qǐng)輸入6-20位的密碼")])    repeat_password=StringField(validators=[EqualTo("password",message="兩次密碼不一致")])graph_captcha=StringField(validators=[Length(4,4,message="請(qǐng)輸入4位圖形驗(yàn)證碼")])def validate_email(self,field):email=field.datauser=UserModel.query.filter_by(email=email).first()if user:raise ValidationError(message="郵箱已經(jīng)被注冊(cè)")def validate_email_captcha(self,field):email_captcha=field.dataemail=self.email.datacache_captcha=cache.get(email)if not cache_captcha or cache_captcha!=email_captcha:raise ValidationError(message="郵箱驗(yàn)證碼錯(cuò)誤")def validate_graph_captcha(self,field):graph_captcha=field.datakey=request.cookies.get("_graph_captcha_key")cache_captcha=cache.get(key)if not cache_captcha or cache_captcha.lower()!=graph_captcha.lower():raise ValidationError(message="圖形驗(yàn)證碼錯(cuò)誤")
#2.在front/views.py里面寫post視圖函數(shù)
from .forms import RegisterForm
from models.auth import UserModel
from exts import db
@bp.route('/register/', methods=['GET', 'POST'])
def register():if request.method == 'GET':return render_template('front/register.html')else:form=RegisterForm(request.form)if form.validate():email=form.email.datausername=form.username.datapassword=form.password.datauser=UserModel(email=email,username=username,password=password)db.session.add(user)db.session.commit()return restful.ok()else:message=form.messages[0]return restful.params_error(message=message)
#3.在js里面綁定點(diǎn)擊事件,跳到上面的視圖函數(shù)
RegisterHandler.prototype.listenSubmitEvent = function (){$("#submit-btn").on("click", function (event){event.preventDefault();var email = $("input[name='email']").val();var email_captcha = $("input[name='email-captcha']").val();var username = $("input[name='username']").val();var password = $("input[name='password']").val();var repeat_password = $("input[name='repeat-password']").val();var graph_captcha = $("input[name='graph-captcha']").val();// 如果是商業(yè)項(xiàng)目,一定要先驗(yàn)證這些數(shù)據(jù)是否正確zlajax.post({url: "/register",data: {"email": email,"email_captcha": email_captcha,"username": username,password, // "password": passwordrepeat_password,graph_captcha},success: function (result){if(result['code'] == 200){window.location = "/login";}else{alert(result['message']);}}})});
}RegisterHandler.prototype.run = function (){this.listenSendCaptchaEvent();this.listenGraphCaptchaEvent();this.listenSubmitEvent();
}

6.登錄頁(yè)面的post請(qǐng)求

#1.首先表單驗(yàn)證
from wtforms.fields import IntegerField
class LoginForm(BaseForm):email=StringField(validators=[Email(message="請(qǐng)輸入正確的郵箱")])password=StringField(validators=[Length(6,20,message="請(qǐng)輸入6-20位的密碼")]) remember=IntegerField()
#2.視圖函數(shù)
from flask import session
from .forms import LoginForm
@bp.route('/login/', methods=['GET', 'POST'])
def login():if request.method == 'GET':return render_template('front/login.html')else:form=LoginForm(request.form)if form.validate():email=form.email.datapassword=form.password.dataremember=form.remember.datauser=UserModel.query.filter_by(email=email).first()if not user:return restful.params_error("此郵箱沒有注冊(cè)")if not user.check_password(password):return restful.params_error("郵箱或密碼錯(cuò)誤")session['user_id']=user.idif remember ==1:session.permanent=Truereturn restful.ok()else:return restful.params_error(message=form.messages[0])
#3.在config里面設(shè)置permanent時(shí)間
from datetime import timedelta
PERMANENT_SESSION_LIFETIME=timedelta(days=7)
#4.登錄的post提交的前端監(jiān)聽,在login.js中,并加載到html中
var LoginHandler = function (){}LoginHandler.prototype.listenSubmitEvent = function (){$("#submit-btn").on("click", function (event){event.preventDefault();var email = $("input[name='email']").val();var password = $("input[name='password']").val();var remember = $("input[name='remember']").prop("checked");zlajax.post({url: "/login",data: {email,password,remember: remember?1:0},success: function (result){if(result['code'] == 200){var token = result['data']['token'];var user = result['data']['user'];localStorage.setItem("JWT_TOKEN_KEY", token);localStorage.setItem("USER_KEY", JSON.stringify(user));window.location = "/"}else{alert(result['message']);}}})});
}LoginHandler.prototype.run = function (){this.listenSubmitEvent();
}$(function (){var handler = new LoginHandler();handler.run();
});

7.首頁(yè)

7.1首頁(yè)狀態(tài)切換功能

1.get請(qǐng)求:寫index視圖函數(shù)和html
2.狀態(tài)切換功能

#1.寫退出登錄的視圖函數(shù)
@bp.route("/logout/")
def logout():session.clear()return redirect("/")
#2.利用鉤子函數(shù)(用戶發(fā)送請(qǐng)求前的操作)和上下文處理器函數(shù)(視圖函數(shù)返回給用戶數(shù)據(jù)前的操作)將user綁定到g上
bp=Blueprint("front",__name__,url_prefix="/")
@bp.before_request#鉤子函數(shù),在用戶訪問視圖函數(shù)前在session里拿到用戶綁到g上
def front_before_request():if 'user_id' in session:user_id=session.get("user_id")user=UserModel.query.get(user_id)setattr(g,"user",user)
@bp.context_processor#上下文處理器函數(shù),在視圖函數(shù)里,服務(wù)器返回給用戶數(shù)據(jù)前將這里的參數(shù)返回給模板進(jìn)行渲染
def front_after_request():if hasattr(g,"user"):return {"user":g.user}else:return {}
#3.修改html里面的變量
{% if user %}<li class="dropdown"><a href="#" class="dropdown-toggle" type="button" id="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">{{ user.username }}<span class="caret"></span></a><ul class="dropdown-menu" aria-labelledby="dropdownMenu1"><li><a href="#">后臺(tái)管理</a></li><li><a href="{{url_for('front.setting')}}">設(shè)置</a></li><li><a href="{{ url_for('front.logout') }}">注銷</a></li></ul></li>{% else %}<li><a href="{{ url_for('front.login') }}">登錄</a></li><li><a href="{{ url_for('front.register') }}">注冊(cè)</a></li>{% endif %}

7.2首頁(yè)設(shè)置功能

#1.在front/decorates.py里面寫登錄裝飾器
from flask import g,redirect,url_for
from functools import wraps
def login_required(func):@wraps(func)def inner(*args, **kwargs):if hasattr(g,"user"):return func(*args, **kwargs)else:return redirect(url_for('front.login'))return inner
#2.寫視圖函數(shù)和html,以及設(shè)置按鈕的跳轉(zhuǎn)鏈接,并要有登錄裝飾器
@bp.route("/setting/")
@login_required
def setting():email_hash=md5(g.user.email.encode("utf-8")).hexdigest()return render_template("front/setting.html",email_hash=email_hash)

7.3首頁(yè)頭像功能

#1.pip install flask-avatars
#2.在exts里面導(dǎo)入,在app里面初始化
from flask_avatars import Avatars
avatars=Avatars()
from exts import avatars
avatars.init_app(app)
#3.使用Gravatar頭像
<img src="{{ avatars.gravatar(email_hash) }}" alt="..." class="img-circle" id="avatar-img">
#4.使用標(biāo)識(shí)生成頭像,并把L尺寸的頭像地址保存到數(shù)據(jù)庫(kù)
AVATARS_SAVE_PATH=os.path.join(BASE_DIR,"media","avatars")#在config里面設(shè)置圖像的保存地址
#在register視圖函數(shù)里面添加avatar的保存數(shù)據(jù)
from flask_avatars import Identicon
import osidenticon=Identicon()filenames=identicon.generate(text=md5(email.encode("utf-8")).hexdigest())avatar=filenames[2]user=UserModel(email=email,username=username,password=password,avatar=avatar)#在media/view里面寫訪問頭像的視圖函數(shù),并綁定到app上,然后修改html的鏈接from flask import Blueprint,send_from_directory,current_app
bp=Blueprint("media",__name__,url_prefix="/media")
@bp.route('/avatar/<filename>')
def get_avatar(filename):return send_from_directory(current_app.config['AVATARS_SAVE_PATH'],filename)
from apps.media import media_bp 
app.register_blueprint(media_bp)
<img src="{{ url_for('media.get_avatar',filename=user.avatar) }}" alt="..." class="img-circle" id="avatar-img">
#4.用戶自定義圖像:用戶頭像上傳就是表單提交的方式,所以要進(jìn)行表單驗(yàn)證,然后寫視圖函數(shù),
class UploadAvatarForm(BaseForm):image=FileField(validators=[FileAllowed(['png', 'jpg', 'jpeg',],message='圖片格式不符合要求'),FileSize(max_size=1024*1024*5,message='圖片大小不超過5MB')])
@bp.post("/avatar/upload/")
@login_required
def upload_avatar():form=UploadAvatarForm(request.files)if form.validate():image=form.image.datafilename=image.filename_,ext=os.path.splitext(filename)filename=md5((g.user.email+str(time.time())).encode('utf-8')).hexdigest()+extimage_path=os.path.join(current_app.config['AVATARS_SAVE_PATH'],filename)image.save(image_path)g.user.avatar=filenamedb.session.commit()return restful.ok(data={'avatar':filename})else:message=form.messages[0]return restful.params_error(message=message)
#寫圖像上傳的js,并將js導(dǎo)入到html,

7.4個(gè)性簽名功能

#1.寫form表單驗(yàn)證,view視圖進(jìn)行post請(qǐng)求,寫js,導(dǎo)入到html

8.帖子相關(guān)設(shè)置

8.1帖子板塊

1.命令行實(shí)現(xiàn)板塊初始化

#1.在models/post.py里面創(chuàng)建帖子板塊模型,并導(dǎo)入到app文件,然后migrate到數(shù)據(jù)庫(kù)
from exts import db
from datetime import datetime
class BoardModel(db.Model):__tablename__ = 'board'id=db.Column(db.Integer, primary_key=True,autoincrement=True)name=db.Column(db.String(20),unique=True)priority=db.Column(db.Integer, default=1)create_time=db.Column(db.DateTime,default=datetime.now)
from models import post#app.py里面導(dǎo)入一下
flask db migrate
flask db upgrade
#2.在commands.py里面寫初始化板塊的命令函數(shù)(給板塊數(shù)據(jù)到db數(shù)據(jù)庫(kù))
from models.post import BoardModel
from exts import db
def init_boards():board_names=['flask','fast','ai','爬蟲']for index,board_name in enumerate(board_names):board=BoardModel(name=board_name,priority=len(board_names)-index)db.session.add(board)db.session.commit()print("板塊初始化成功")
#3.在app.py里面導(dǎo)入并注冊(cè)命令
import commands
app.cli.command("inbo")(commands.init_boards)
#4.在cmd里面調(diào)用命令
flask inbo
#5.板塊的后端已經(jīng)實(shí)現(xiàn),現(xiàn)在要將這個(gè)信息傳給前端并顯示,所以在首頁(yè)(/)視圖函數(shù)下傳參并在html中循環(huán)顯示
from models.post import BoardModel
boards=BoardModel.query.order_by(BoardModel.priority.desc()).all()
return render_template("front/index.html",boards=boards)
{% for board in boards %}<a href="#" class="list-group-item">{{board.name}}</a>
{% endfor %}

2.創(chuàng)建帖子相關(guān)的模型,包括PostModel,BannerModel,CommentModel,并migrate到數(shù)據(jù)庫(kù)

8.2發(fā)布帖子

1.get

#1.首先修改首頁(yè)的發(fā)布帖子的跳轉(zhuǎn)鏈接,然后寫視圖函數(shù)和html文件
<a href="{{url_for('front.public_post')}}" class="btn btn-warning btn-block">發(fā)布帖子</a>
@bp.route("post/public/",methods=["POST", "GET"])
def public_post():if request.method == "GET":boards=BoardModel.query.all()return render_template("front/public_post.html",boards=boards)

2.富文本編輯器wangEditor,這里首先需要導(dǎo)入它的js文件,可以按官網(wǎng)的示例線上引用也可以下載到本地再導(dǎo)入。

#1.在public_post.html里面導(dǎo)入富文本編輯器的js
<script type="text/javascript" src="{{url_for('static',filename='lib/wangEditor/wangEditor.min.js')}}"></script>
#2.寫自己的初始化編輯器和文本內(nèi)容提交的js并引入到html中
<script type="text/javascript" src="{{url_for('static',filename='front/js/public_post.js')}}"></script>
#3.上傳圖片到本地,首先在wangeditor里面初始化圖片上傳的路徑等信息,然后寫圖片上傳的視圖函數(shù)@bp.post("/post/image/upload/")
@login_required
def upload_post_image():form=UploadAvatarForm(request.files)if form.validate():image=form.image.datafilename=image.filename_,ext=os.path.splitext(filename)filename=md5((g.user.email+str(time.time())).encode('utf-8')).hexdigest()+extimage_path=os.path.join(current_app.config['POST_IMAGE_SAVE_PATH'],filename)image.save(image_path)return jsonify({"errno": 0, # 注意:值是數(shù)字,不能是字符串"data": [{"url": url_for('media.get_post_image',filename=filename), # 圖片 src ,必須"alt": "filename", # 圖片描述文字,非必須"href": "" # 圖片的鏈接,非必須}]
})else:message=form.messages[0]return jsonify({"errno": 1, #只要不等于 0 就行"message": message
})

3.發(fā)布帖子的提交

#1.首先表單驗(yàn)證,然后寫帖子內(nèi)容提交的視圖函數(shù),js提交前端
@bp.route("post/public/",methods=["POST", "GET"])
@login_required
def public_post():if request.method == "GET":boards=BoardModel.query.all()return render_template("front/public_post.html",boards=boards)else:form=PublicPostForm(request.form)if form.validate():title=form.title.datacontent=form.content.databoard_id=form.board_id.datatry:board=BoardModel.query.get(board_id)except Exception as e:return restful.error(message='板塊不存在')post_model=PostModel(title=title,content=content,board=board,author=g.user)db.session.add(post_model)db.session.commit()return restful.ok(data={"id":post_model.id})else:return restful.params_error(message=form.messages[0])
var PublicPostHandler = function (){var csrf_token = $("meta[name='csrf-token']").attr("content");var editor = new window.wangEditor("#editor");editor.config.uploadImgServer = "/post/image/upload";editor.config.uploadFileName = "image";// 1. 放到請(qǐng)求體中// 2. 放到請(qǐng)求頭中X-CSRFToken// 再和cookie中的csrf_token進(jìn)行對(duì)比editor.config.uploadImgHeaders = {"X-CSRFToken": csrf_token}editor.config.uploadImgMaxSize = 1024*1024*5;editor.create();this.editor = editor;}PublicPostHandler.prototype.listenSubmitEvent = function (){var that = this;$("#submit-btn").on("click", function (event){event.preventDefault();var title = $("input[name='title']").val();var board_id = $("select[name='board_id']").val();var content = that.editor.txt.html();zlajax.post({url: "/post/public/",data: {title,board_id,content},success: function (result){if(result['code'] == 200){let data = result['data'];let post_id = data['id'];window.location = "/post/detail/" + post_id;}else{alert(result['message']);}}});});}PublicPostHandler.prototype.run = function(){this.listenSubmitEvent();}$(function(){var handler = new PublicPostHandler();handler.run();});

8.3帖子詳情頁(yè)

#1.帖子詳情頁(yè)的get請(qǐng)求視圖函數(shù),html
#2.帖子詳情的代碼高亮功能,使用highlight.js<link rel="stylesheet" href="{{ url_for('static', filename='lib/highlight/styles/github-dark.min.css') }}"><script src="{{ url_for('static', filename='lib/highlight/highlight.min.js') }}"></script><script src="{{ url_for('static', filename='front/js/post_detail.js') }}"></script>hljs.highlightAll();#post_detail.js里面初始化
#3.帖子詳情的評(píng)論功能:表單驗(yàn)證,post視圖函數(shù),在html中獲取填寫的信息通過js提交,html中導(dǎo)入js

8.4首頁(yè)帖子列表

1.在首頁(yè)的視圖函數(shù)中拿到數(shù)據(jù)庫(kù)的帖子數(shù)據(jù),通過參數(shù)傳遞給前端html,前端通過for循環(huán)展示帖子相關(guān)信息
2.使用flask-paginate實(shí)現(xiàn)帖子分頁(yè)

pip install flask-paginate
#1.首先在config中配置每頁(yè)展示帖子的數(shù)量
PER_PAGE_COUNT=7
#2.在首頁(yè)的視圖函數(shù)中查詢并進(jìn)行分頁(yè),然后返回參數(shù)給html渲染
from flask_paginate import get_page_parameter,Pagination
@bp.route("/")
def index():boards=BoardModel.query.order_by(BoardModel.priority.desc()).all()post_query =PostModel.query.order_by(PostModel.create_time.desc())total=post_query.count()page= request.args.get(get_page_parameter(), type=int, default=1)per_page_count = current_app.config['PER_PAGE_COUNT']start = (page - 1) * per_page_countposts = post_query.offset(start).limit(per_page_count).all()pagination = Pagination(bs_version=3,page=page,total=total,per_page=per_page_count,  force_parameter=True)context={"boards":boards, "posts":posts,"pagination":pagination}return render_template("front/index.html",**context)
#3.在index的html的帖子列表末尾添加翻頁(yè)按鈕
<div style="text-align:center;">
{{pagination.links}}  
</div> 

3.帖子按評(píng)論順序和時(shí)間排列

#1.首先通過html傳遞st參數(shù),即按什么方式排序
{% if st==1 %}<li class="active">
{% else %} 
<li>
{% endif %}<a href="{{url_for('front.index',st=1)}}">最新</a></li>
{% if st==2 %}<li class="active">
{% else %} <li>
{% endif %}<a href="{{url_for('front.index',st=2)}}">評(píng)論最多</a></li>
#2.在view里面按照st參數(shù)進(jìn)行排序,
from sqlalchemy.sql import func
@bp.route("/")
def index():sort=request.args.get('st',type=int,default=1)post_query=Noneif sort==1:post_query=PostModel.query.order_by(PostModel.create_time.desc())else:post_query=db.session.query(PostModel).outerjoin(CommentModel).group_by(PostModel.id).order_by(func.count(CommentModel.id).desc(),PostModel.create_time.desc())...context={"boards":boards, "posts":posts,"pagination":pagination,"st":sort}return render_template("front/index.html",**context)

4.帖子按板塊過濾

#1.html傳遞板塊bd參數(shù)
{% if not bd %}<a href="/" class="list-group-item active">所有板塊</a>{% else %}<a href="/" class="list-group-item">所有板塊</a>{% endif %}{% for board in boards %}{% if board.id==bd %}<a href="{{url_for('front.index',bd=board.id,page=1)}}" class="list-group-item active">{{board.name}}</a>{% else %}<a href="{{url_for('front.index',bd=board.id,page=1)}}" class="list-group-item">{{board.name}}</a>{% endif %}{% endfor %}
#2.view里面拿到bd參數(shù),對(duì)post進(jìn)行過濾
@bp.route("/")
def index():sort=request.args.get('st',type=int,default=1)board_id=request.args.get('bd',type=int,default=None)boards=BoardModel.query.order_by(BoardModel.priority.desc()).all()post_query=Noneif sort==1:post_query=PostModel.query.order_by(PostModel.create_time.desc())else:post_query = db.session.query(PostModel).outerjoin(CommentModel).group_by(PostModel.id).order_by(func.count(CommentModel.id).desc(), PostModel.create_time.desc())page= request.args.get(get_page_parameter(), type=int, default=1)per_page_count = current_app.config['PER_PAGE_COUNT']start = (page - 1) * per_page_countend=start+current_app.config['PER_PAGE_COUNT']if board_id:post_query=post_query.filter(PostModel.board_id==board_id)total = post_query.count()posts=post_query.slice(start,end)pagination = Pagination(bs_version=3,page=page,total=total,per_page=per_page_count,  force_parameter=True)context={"boards":boards, "posts":posts,"pagination":pagination,"st":sort,'bd':board_id}return render_template("front/index.html",**context)
http://www.risenshineclean.com/news/37622.html

相關(guān)文章:

  • 自己創(chuàng)業(yè)做原公司一樣的網(wǎng)站網(wǎng)站seo設(shè)計(jì)
  • 公司做網(wǎng)站的步驟廣州seo關(guān)鍵字推廣
  • 做韋恩圖的網(wǎng)站怎么樣推廣自己的公司
  • wordpress 添加導(dǎo)航菜單成都seo招聘
  • 網(wǎng)站域名有什么用計(jì)算機(jī)培訓(xùn)
  • 大學(xué)新校區(qū)建設(shè)網(wǎng)站網(wǎng)站seo重慶
  • 網(wǎng)站推廣資訊上海百度競(jìng)價(jià)托管
  • 中國(guó)大型建筑公司有哪些seo西安
  • 全國(guó)公安網(wǎng)站備案應(yīng)用寶aso優(yōu)化
  • 班級(jí)建設(shè)網(wǎng)站設(shè)計(jì)方案搜索引擎優(yōu)化到底是優(yōu)化什么
  • 陜西省建設(shè)廳小紅書關(guān)鍵詞排名優(yōu)化
  • java 網(wǎng)站設(shè)計(jì)都有什么推廣平臺(tái)
  • 香港網(wǎng)站代理seo優(yōu)化方案
  • 南昌做網(wǎng)站市場(chǎng)報(bào)價(jià)刷seo關(guān)鍵詞排名軟件
  • 做網(wǎng)站設(shè)計(jì)累嗎網(wǎng)絡(luò)營(yíng)銷策劃步驟
  • css優(yōu)秀網(wǎng)站百度平臺(tái)客服
  • 網(wǎng)站制作公司官網(wǎng)南京長(zhǎng)沙百度
  • 淘客做網(wǎng)站百度關(guān)鍵詞優(yōu)化專家
  • 找哪個(gè)網(wǎng)站做摩配百度投訴電話人工服務(wù)總部
  • 羅湖建設(shè)網(wǎng)站志鴻優(yōu)化設(shè)計(jì)答案網(wǎng)
  • wordpress圖片展示主題yousucai寧波網(wǎng)站推廣優(yōu)化外包
  • 做網(wǎng)站工商局要不要備案呢色盲測(cè)試圖 考駕照
  • cname解析對(duì)網(wǎng)站影響seo課程心得體會(huì)
  • 商務(wù)網(wǎng)站制作語(yǔ)言基礎(chǔ)seo平臺(tái)怎么樣
  • 烏蘭察布做網(wǎng)站的公司百度推廣是怎么做的
  • 求幾個(gè)夸克沒封的a站2023惠州seo排名外包
  • 設(shè)計(jì)網(wǎng)站頁(yè)面好處百度瀏覽器下載
  • 自己有服務(wù)器和域名怎么做網(wǎng)站谷歌seo培訓(xùn)
  • 網(wǎng)站建設(shè)建設(shè)多少錢湖南網(wǎng)站營(yíng)銷seo多少費(fèi)用
  • tq網(wǎng)站漂浮代碼小紅書seo是什么