<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          學(xué)習(xí)Flask主站源碼,原來可以這樣學(xué)!

          共 13738字,需瀏覽 28分鐘

           ·

          2022-04-14 02:46

          flask—website,是flask曾經(jīng)的主站源碼,使用flask制作,包含模版渲染,數(shù)據(jù)庫操作,openID認(rèn)證, 全文檢索等功能。對(duì)于學(xué)習(xí)如何使用flask制作一個(gè)完備的web站點(diǎn),很有參考價(jià)值,我們一起來學(xué)習(xí)它。

          項(xiàng)目結(jié)構(gòu)

          flask-website已經(jīng)歸檔封存,我們使用最后的版本8b08,包括如下幾個(gè)模塊:

          模塊描述
          run.py啟動(dòng)腳本
          websiteconfig.py設(shè)置腳本
          update-doc-searchindex.py更新索引腳本
          database.py數(shù)據(jù)庫模塊
          docs.py索引文檔模塊
          openid_auth.pyoauth認(rèn)證
          search.py搜素模塊
          utils.py工具類
          listings一些展示欄
          views藍(lán)圖模塊,包括社區(qū),擴(kuò)展,郵件列表,代碼片段等
          static網(wǎng)站的靜態(tài)資源
          templates網(wǎng)站的模版資源

          flask-website的項(xiàng)目結(jié)構(gòu),可以作為flask的腳手架,按照這個(gè)目錄規(guī)劃構(gòu)建自己的站點(diǎn):

          .
          ├──?LICENSE
          ├──?Makefile
          ├──?README
          ├──?flask_website
          │???├──?__init__.py
          │???├──?database.py
          │???├──?docs.py
          │???├──?flaskystyle.py
          │???├──?listings
          │???├──?openid_auth.py
          │???├──?search.py
          │???├──?static
          │???├──?templates
          │???├──?utils.py
          │???└──?views
          ├──?requirements.txt
          ├──?run.py
          ├──?update-doc-searchindex.py
          └──?websiteconfig.py
          • run.py作為項(xiàng)目的啟動(dòng)入口
          • requirements.txt描述項(xiàng)目的依賴包
          • flask_website是項(xiàng)目的主模塊,里面包括:存放靜態(tài)資源的static目錄; 存放模版文件的templates目錄;存放一些藍(lán)圖模塊的views模塊,使用這些藍(lán)圖構(gòu)建網(wǎng)站的不同頁面。

          網(wǎng)站入口

          網(wǎng)站的入口run.py代碼很簡(jiǎn)單,導(dǎo)入app并運(yùn)行:

          from?flask_website?import?app
          app.run(debug=True)

          app是基于flask,使用websiteconfig中的配置進(jìn)行初始化

          app?=?Flask(__name__)
          app.config.from_object('websiteconfig')

          app中設(shè)置了一些全局實(shí)現(xiàn),比如404頁面定義,全局用戶,關(guān)閉db連接,和模版時(shí)間:

          @app.errorhandler(404)
          def?not_found(error):
          ????return?render_template('404.html'),?404

          @app.before_request
          def?load_current_user():
          ????g.user?=?User.query.filter_by(openid=session['openid']).first()?\
          ????????if?'openid'?in?session?else?None

          @app.teardown_request
          def?remove_db_session(exception):
          ????db_session.remove()

          @app.context_processor
          def?current_year():
          ????return?{'current_year':?datetime.utcnow().year}

          加載view部分使用了兩種方式,第一種是使用flask的add_url_rule函數(shù),設(shè)置了文檔的搜索實(shí)現(xiàn),這些url執(zhí)行docs模塊:

          app.add_url_rule('/docs/',?endpoint='docs.index',?build_only=True)
          app.add_url_rule('/docs//',?endpoint='docs.show',
          ?????????????????build_only=True)
          app.add_url_rule('/docs//.latex/Flask.pdf',?endpoint='docs.pdf',
          ?????????????????build_only=True)

          第二種是使用flask的藍(lán)圖功能:

          from?flask_website.views?import?general
          from?flask_website.views?import?community
          from?flask_website.views?import?mailinglist
          from?flask_website.views?import?snippets
          from?flask_website.views?import?extensions
          app.register_blueprint(general.mod)
          app.register_blueprint(community.mod)
          app.register_blueprint(mailinglist.mod)
          app.register_blueprint(snippets.mod)
          app.register_blueprint(extensions.mod)

          最后app還定義了一些jinja模版的工具函數(shù):

          app.jinja_env.filters['datetimeformat']?=?utils.format_datetime
          app.jinja_env.filters['dateformat']?=?utils.format_date
          app.jinja_env.filters['timedeltaformat']?=?utils.format_timedelta
          app.jinja_env.filters['displayopenid']?=?utils.display_openid

          模版渲染

          現(xiàn)在主流的站點(diǎn)都是采用前后端分離的結(jié)構(gòu),后端提供純粹的API,前端使用vue等構(gòu)建。這種結(jié)構(gòu)對(duì)于構(gòu)建小型站點(diǎn),會(huì)比較復(fù)雜,有牛刀殺雞的感覺。對(duì)個(gè)人開發(fā)者,還需要學(xué)習(xí)更多的前端知識(shí)。而使用后端的模版渲染方式構(gòu)建頁面,是比較傳統(tǒng)的方式,對(duì)小型站點(diǎn)比較實(shí)用。

          本項(xiàng)目就是使用模版構(gòu)建,在general藍(lán)圖中:

          mod?=?Blueprint('general',?__name__)

          @mod.route('/')
          def?index():
          ????if?request_wants_json():
          ????????return?jsonify(releases=[r.to_json()?for?r?in?releases])

          ????return?render_template(
          ????????'general/index.html',
          ????????latest_release=releases[-1],
          ????????#?pdf?link?does?not?redirect,?needs?version
          ????????#?docs?version?only?includes?major.minor
          ????????docs_pdf_version='.'.join(releases[-1].version.split('.',?2)[:2])
          ????)

          可以看到首頁有2種輸出方式,一種是json化的輸出,另一種是html方式輸出,我們重點(diǎn)看看第二種方式。函數(shù)render_template傳遞了模版路徑,latest_release和docs_pdf_version兩個(gè)變量值。

          模版也是模塊化的,一般是根據(jù)頁面布局而來。比如分成左右兩欄的結(jié)構(gòu),或者上下結(jié)構(gòu),布局定義的模版一般叫做layout。比如本項(xiàng)目的模版就從上至下定義成下面5塊:

          • head 一般定義html頁面標(biāo)題(瀏覽器欄),css樣式/js-script的按需加載等
          • body_title 定義頁面的標(biāo)題
          • message 定義一些統(tǒng)一的通知,提示類的展示空間
          • body 頁面的正文部分
          • footer 統(tǒng)一的頁腳

          使用layout模版定義,將網(wǎng)站的展示風(fēng)格統(tǒng)一下來,各個(gè)頁面可以繼承和擴(kuò)展。下面是head塊和message塊的定義細(xì)節(jié):


          {%?block?head?%}
          {%?block?title?%}Welcome{%?endblock?%}?|?Flask?(A?Python?Microframework)

          type=text/css?href="{{?url_for('static',?filename='style.css')?}}">
          "shortcut?icon"?href="{{?url_for('static',?filename='favicon.ico')?}}">

          {%?endblock?%}

          ??...
          ??
          ????"{{?url_for('general.index')?}}">overview?//
          ????"{{?url_for('docs.index')?}}">docs?//
          ????"{{?url_for('community.index')?}}">community?//
          ????"{{?url_for('extensions.index')?}}">extensions?//
          ????"https://psfmember.org/civicrm/contribute/transact?reset=1&id=20">donate
          ??{%?for?message?in?get_flashed_messages()?%}
          ????{{?message?}}
          ??{%?endfor?%}
          ??...

          本項(xiàng)目首頁的general/index繼承自全局的layout,并對(duì)其中的body部分進(jìn)行覆蓋,使用自己的配置:

          {%?extends?"layout.html"?%}
          ????....
          {%?block?body?%}
          ??

            ????
          • "{{?latest_release.detail_url?}}">Download?latest?release?({{?latest_release.version?}})
            ????
          • "{{?url_for('docs.index')?}}">Read?the?documentation
            ????
          • "{{?url_for('mailinglist.index')?}}">Join?the?mailinglist
            ????
          • Fork?it?on?github
            ????
          • Add?issues?and?feature?requests
            ??

          ??...
          • 這個(gè)列表主要使用了藍(lán)圖中傳入的latest_release變量,展示最新文檔(pdf)的url

          數(shù)據(jù)庫操作

          網(wǎng)站有交互,必定要持久化數(shù)據(jù)。本項(xiàng)目使用的sqlite的數(shù)據(jù)庫,比較輕量級(jí)。數(shù)據(jù)庫使用sqlalchemy封裝的ORM實(shí)現(xiàn)。下面的代碼展示了如何創(chuàng)建一個(gè)評(píng)論:

          @mod.route('/comments//',?methods=['GET',?'POST'])
          @requires_admin
          def?edit_comment(id):
          ????comment?=?Comment.query.get(id)
          ????snippet?=?comment.snippet
          ????form?=?dict(title=comment.title,?text=comment.text)
          ????if?request.method?==?'POST':
          ????????...
          ????????form['title']?=?request.form['title']
          ????????form['text']?=?request.form['text']
          ????????..
          ????????comment.title?=?form['title']
          ????????comment.text?=?form['text']
          ????????db_session.commit()
          ????????flash(u'Comment?was?updated.')
          ????????return?redirect(snippet.url)
          ????...
          • 創(chuàng)建comment對(duì)象
          • 從html的form表單中獲取用戶提交的title和text
          • 對(duì)comment對(duì)象進(jìn)行賦值和提交
          • 刷新頁面的提示信息(在模版的message部分展示)
          • 返回到新的url

          借助sqlalchemy,數(shù)據(jù)模型的操作API簡(jiǎn)單易懂。要使用數(shù)據(jù)庫,需要先創(chuàng)建數(shù)據(jù)庫連接,構(gòu)建模型等, 主要在database模塊:

          DATABASE_URI?=?'sqlite:///'?+?os.path.join(_basedir,?'flask-website.db')
          #?創(chuàng)建引擎
          engine?=?create_engine(app.config['DATABASE_URI'],
          ???????????????????????convert_unicode=True,
          ???????????????????????**app.config['DATABASE_CONNECT_OPTIONS'])
          #?創(chuàng)建session(連接)????????????????
          db_session?=?scoped_session(sessionmaker(autocommit=False,
          ?????????????????????????????????????????autoflush=False,
          ?????????????????????????????????????????bind=engine))
          #?初始化
          def?init_db():
          ????Model.metadata.create_all(bind=engine)

          #?定義基礎(chǔ)模型
          Model?=?declarative_base(name='Model')
          Model.query?=?db_session.query_property()

          Comment數(shù)據(jù)模型定義:

          class?Comment(Model):
          ????__tablename__?=?'comments'
          ????id?=?Column('comment_id',?Integer,?primary_key=True)
          ????snippet_id?=?Column(Integer,?ForeignKey('snippets.snippet_id'))
          ????author_id?=?Column(Integer,?ForeignKey('users.user_id'))
          ????title?=?Column(String(200))
          ????text?=?Column(String)
          ????pub_date?=?Column(DateTime)

          ????snippet?=?relation(Snippet,?backref=backref('comments',?lazy=True))
          ????author?=?relation(User,?backref=backref('comments',?lazy='dynamic'))

          ????def?__init__(self,?snippet,?author,?title,?text):
          ????????self.snippet?=?snippet
          ????????self.author?=?author
          ????????self.title?=?title
          ????????self.text?=?text
          ????????self.pub_date?=?datetime.utcnow()

          ????def?to_json(self):
          ????????return?dict(author=self.author.to_json(),
          ????????????????????title=self.title,
          ????????????????????pub_date=http_date(self.pub_date),
          ????????????????????text=unicode(self.rendered_text))

          ????@property
          ????def?rendered_text(self):
          ????????from?flask_website.utils?import?format_creole
          ????????return?format_creole(self.text)

          Comment模型按照結(jié)構(gòu)化的方式定義了表名,6個(gè)字段,2個(gè)關(guān)聯(lián)關(guān)系和json化和文本化的展示方法。

          sqlalchemy的使用,在之前的文章中有過介紹,本文就不再贅述。

          openID認(rèn)證

          一個(gè)小眾的網(wǎng)站,構(gòu)建自己的賬號(hào)即麻煩也不安全,使用第三方的用戶體系會(huì)比較合適。本項(xiàng)目使用的是Flask-OpenID這個(gè)庫提供的optnID登錄認(rèn)證。

          用戶登錄的時(shí)候,會(huì)根據(jù)用戶選擇的三方登錄站點(diǎn),跳轉(zhuǎn)到對(duì)應(yīng)的網(wǎng)站進(jìn)行認(rèn)證:

          @mod.route('/login/',?methods=['GET',?'POST'])
          @oid.loginhandler
          def?login():
          ????..
          ????openid?=?request.values.get('openid')
          ????if?not?openid:
          ????????openid?=?COMMON_PROVIDERS.get(request.args.get('provider'))
          ????if?openid:
          ????????return?oid.try_login(openid,?ask_for=['fullname',?'nickname'])
          ????..

          從對(duì)應(yīng)的模版上更容易理解這個(gè)過程, 可以看到默認(rèn)支持AOL/Google/Yahoo三個(gè)賬號(hào)體系認(rèn)證:

          {%?block?body?%}
          ??""?method=post>
          ????


          ??????For?some?of?the?features?on?this?site?(such?as?creating?snippets
          ??????or?adding?comments)?you?have?to?be?signed?in.??You?don't?need?to
          ??????create?an?account?on?this?website,?just?sign?in?with?an?existing
          ??????OpenID?account.
          ????


          ??????OpenID?URL:
          ??????
          ??????
          ??????
          ????


          ??????Alternatively?you?can?directly?sign?in?by?clicking?on?one?of
          ??????the?providers?here?in?case?you?don't?know?the?identity?URL:
          ????


            ??????
          • AOL
            ??????
          • Google
            ??????
          • Yahoo
            ????

          ??
          {%?endblock?%}

          在三方站點(diǎn)認(rèn)證完成后,會(huì)建立本站點(diǎn)的用戶和openid的綁定關(guān)系:

          @mod.route('/first-login/',?methods=['GET',?'POST'])
          def?first_login():
          ????...
          ????????db_session.add(User(request.form['name'],?session['openid']))
          ????????db_session.commit()
          ????????flash(u'Successfully?created?profile?and?logged?in')
          ????...
          • session中的openid是第三方登錄成功后寫入session

          三方登錄的邏輯過程大概就如上所示,先去三方平臺(tái)登錄,然后和本地站點(diǎn)的賬號(hào)進(jìn)行關(guān)聯(lián)。其具體的實(shí)現(xiàn),主要依賴Flask-OpenID這個(gè)模塊, 我們大概了解即可。

          全文檢索

          全文檢索對(duì)于一個(gè)站點(diǎn)非常重要,可以幫助用戶在網(wǎng)站上快速找到適合的內(nèi)容。本項(xiàng)目展示了使用whoosh這個(gè)純python實(shí)現(xiàn)的全文檢索工具,構(gòu)建網(wǎng)站內(nèi)容檢索,和使用ElasticSearch這樣大型的檢索庫不一樣??傊?,本項(xiàng)目使用的都是小型工具,純python實(shí)現(xiàn)。

          全文檢索從/search/入口進(jìn)入:

          @mod.route('/search/')
          def?search():
          ????q?=?request.args.get('q')?or?''
          ????page?=?request.args.get('page',?type=int)?or?1
          ????results?=?None
          ????if?q:
          ????????results?=?perform_search(q,?page=page)
          ????????if?results?is?None:
          ????????????abort(404)
          ????return?render_template('general/search.html',?results=results,?q=q)
          • q是搜素的關(guān)鍵字,page是翻頁的頁數(shù)
          • 使用perform_search方法對(duì)索引進(jìn)行查詢
          • 如果找不到內(nèi)容展示404;如果找到內(nèi)容,展示結(jié)果

          在search模塊中提供了search方法,前面調(diào)用的perform_search函數(shù)是其別名:

          def?search(query,?page=1,?per_page=20):
          ????with?index.searcher()?as?s:
          ????????qp?=?qparser.MultifieldParser(['title',?'content'],?index.schema)
          ????????q?=?qp.parse(unicode(query))
          ????????try:
          ????????????result_page?=?s.search_page(q,?page,?pagelen=per_page)
          ????????except?ValueError:
          ????????????if?page?==?1:
          ????????????????return?SearchResultPage(None,?page)
          ????????????return?None
          ????????results?=?result_page.results
          ????????results.highlighter.fragmenter.maxchars?=?512
          ????????results.highlighter.fragmenter.surround?=?40
          ????????results.highlighter.formatter?=?highlight.HtmlFormatter('em',
          ????????????classname='search-match',?termclass='search-term',
          ????????????between=u'?…?')
          ????????return?SearchResultPage(result_page,?page)
          • 從ttile和content中搜素關(guān)鍵字q
          • 設(shè)置使用unicode編碼
          • 將檢索結(jié)果封裝成SearchResultPage

          重點(diǎn)在index.searcher()這個(gè)索引, 它使用下面方法構(gòu)建:

          from?whoosh?import?highlight,?analysis,?qparser
          from?whoosh.support.charset?import?accent_map
          ...
          def?open_index():
          ????from?whoosh?import?index,?fields?as?f
          ????if?os.path.isdir(app.config['WHOOSH_INDEX']):
          ????????return?index.open_dir(app.config['WHOOSH_INDEX'])
          ????os.mkdir(app.config['WHOOSH_INDEX'])
          ????analyzer?=?analysis.StemmingAnalyzer()?|?analysis.CharsetFilter(accent_map)
          ????schema?=?f.Schema(
          ????????url=f.ID(stored=True,?unique=True),
          ????????id=f.ID(stored=True),
          ????????title=f.TEXT(stored=True,?field_boost=2.0,?analyzer=analyzer),
          ????????type=f.ID(stored=True),
          ????????keywords=f.KEYWORD(commas=True),
          ????????content=f.TEXT(analyzer=analyzer)
          ????)
          ????return?index.create_in(app.config['WHOOSH_INDEX'],?schema)

          index?=?open_index()
          • whoosh創(chuàng)建本地的索引文件
          • whoosh構(gòu)建搜素的數(shù)據(jù)結(jié)構(gòu),包括url,title,,關(guān)鍵字和內(nèi)容
          • 關(guān)鍵字和內(nèi)容參與檢索

          索引需要構(gòu)建和刷新:

          def?update_documentation_index():
          ????from?flask_website.docs?import?DocumentationPage
          ????writer?=?index.writer()
          ????for?page?in?DocumentationPage.iter_pages():
          ????????page.remove_from_search_index(writer)
          ????????page.add_to_search_index(writer)
          ????writer.commit()

          文檔索引構(gòu)建在docs模塊中:

          DOCUMENTATION_PATH?=?os.path.join(_basedir,?'../flask/docs/_build/dirhtml')
          WHOOSH_INDEX?=?os.path.join(_basedir,?'flask-website.whoosh')

          class?DocumentationPage(Indexable):
          ????search_document_kind?=?'documentation'

          ????def?__init__(self,?slug):
          ????????self.slug?=?slug
          ????????fn?=?os.path.join(app.config['DOCUMENTATION_PATH'],
          ??????????????????????????slug,?'index.html')
          ????????with?open(fn)?as?f:
          ????????????contents?=?f.read().decode('utf-8')
          ????????????title,?text?=?_doc_body_re.search(contents).groups()
          ????????self.title?=?Markup(title).striptags().split(u'—')[0].strip()
          ????????self.text?=?Markup(text).striptags().strip().replace(u'?',?u'')
          ????
          ????@classmethod
          ????def?iter_pages(cls):
          ????????base_folder?=?os.path.abspath(app.config['DOCUMENTATION_PATH'])
          ????????for?dirpath,?dirnames,?filenames?in?os.walk(base_folder):
          ????????????if?'index.html'?in?filenames:
          ????????????????slug?=?dirpath[len(base_folder)?+?1:]
          ????????????????#?skip?the?index?page.??useless
          ????????????????if?slug:
          ????????????????????yield?DocumentationPage(slug)

          • 文檔讀取DOCUMENTATION_PATH目錄下的源文件(項(xiàng)目文檔)
          • 讀取文件的標(biāo)題和文本,構(gòu)建索引文件

          小結(jié)

          本文我們走馬觀花的查看了flask-view這個(gè)flask曾經(jīng)的主站。雖然沒有深入太多細(xì)節(jié),但是我們知道了模版渲染,數(shù)據(jù)庫操作,OpenID認(rèn)證和全文檢索四個(gè)功能的實(shí)現(xiàn)方式,建立了相關(guān)技術(shù)的索引。如果我們需要構(gòu)建自己的小型web項(xiàng)目,比如博客,完全可以以這個(gè)項(xiàng)目為基礎(chǔ),修改實(shí)現(xiàn)。

          經(jīng)過數(shù)周的調(diào)整,接下我們開始進(jìn)入python影響力巨大的項(xiàng)目之一: Django。敬請(qǐng)期待。

          小技巧

          本項(xiàng)目提供了2個(gè)非常實(shí)用的小技巧。第1個(gè)是json化和html化輸出,這樣用戶可以自由選擇輸出方式,同時(shí)站點(diǎn)也可以構(gòu)建純API的接口。這個(gè)功能是使用下面的request_wants_json函數(shù)提供:

          def?request_wants_json():
          ????#?we?only?accept?json?if?the?quality?of?json?is?greater?than?the
          ????#?quality?of?text/html?because?text/html?is?preferred?to?support
          ????#?browsers?that?accept?on?*/*
          ????best?=?request.accept_mimetypes?\
          ????????.best_match(['application/json',?'text/html'])
          ????return?best?==?'application/json'?and?\
          ???????request.accept_mimetypes[best]?>?request.accept_mimetypes['text/html']

          request_wants_json函數(shù)中判斷頭部的mime類型,進(jìn)行根據(jù)是application/json還是text/html決定展示方式。

          第2個(gè)小技巧是認(rèn)證裝飾器, 前面一個(gè)是登錄驗(yàn)證,后一個(gè)是超級(jí)管理認(rèn)證:

          def?requires_login(f):
          ????@wraps(f)
          ????def?decorated_function(*args,?**kwargs):
          ????????if?g.user?is?None:
          ????????????flash(u'You?need?to?be?signed?in?for?this?page.')
          ????????????return?redirect(url_for('general.login',?next=request.path))
          ????????return?f(*args,?**kwargs)
          ????return?decorated_function

          def?requires_admin(f):
          ????@wraps(f)
          ????def?decorated_function(*args,?**kwargs):
          ????????if?not?g.user.is_admin:
          ????????????abort(401)
          ????????return?f(*args,?**kwargs)
          ????return?requires_login(decorated_function)

          這兩個(gè)裝飾器,在view的API上使用, 比如編輯snippet需要登錄,評(píng)論需要管理員權(quán)限:

          @mod.route('/edit//',?methods=['GET',?'POST'])
          @requires_login
          def?edit(id):
          ????...

          @mod.route('/comments//',?methods=['GET',?'POST'])
          @requires_admin
          def?edit_comment(id):
          ????...

          參考鏈接

          • https://github.com/pallets/flask-website



          推薦閱讀:

          入門:?最全的零基礎(chǔ)學(xué)Python的問題? |?零基礎(chǔ)學(xué)了8個(gè)月的Python??|?實(shí)戰(zhàn)項(xiàng)目?|學(xué)Python就是這條捷徑


          干貨:爬取豆瓣短評(píng),電影《后來的我們》?|?38年NBA最佳球員分析?|? ?從萬眾期待到口碑撲街!唐探3令人失望? |?笑看新倚天屠龍記?|?燈謎答題王?|用Python做個(gè)海量小姐姐素描圖?|碟中諜這么火,我用機(jī)器學(xué)習(xí)做個(gè)迷你推薦系統(tǒng)電影


          趣味:彈球游戲? |?九宮格? |?漂亮的花?|?兩百行Python《天天酷跑》游戲!


          AI:?會(huì)做詩的機(jī)器人?|?給圖片上色?|?預(yù)測(cè)收入?|?碟中諜這么火,我用機(jī)器學(xué)習(xí)做個(gè)迷你推薦系統(tǒng)電影


          小工具:?Pdf轉(zhuǎn)Word,輕松搞定表格和水??!?|?一鍵把html網(wǎng)頁保存為pdf!|??再見PDF提取收費(fèi)!?|?用90行代碼打造最強(qiáng)PDF轉(zhuǎn)換器,word、PPT、excel、markdown、html一鍵轉(zhuǎn)換?|?制作一款釘釘?shù)蛢r(jià)機(jī)票提示器!?|60行代碼做了一個(gè)語音壁紙切換器天天看小姐姐!



          年度爆款文案


          點(diǎn)閱讀原文,看原創(chuàng)200個(gè)趣味案例!

          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产精品精品国产婷婷这里Av | 黄色录像大片 | 久久精品禁一区二区三区四区五区 | 亚洲大乱婬交换 | 玖玖视频|