曾經有人說我前端很水,那么在這一系列文章中我打算把前后端融合在一起來做一次網站的全面重構,希望可以把剛剛入行的同學帶上正途
請尊重原創,轉載請注明來源網站www.shareditor.com以及原始鏈接地址
聊聊工程
如今,數據科學家已經逐漸取代現在的“軟件工程師”成為IT行業的主流職業,和“全民都在聊人工智能”一樣,可能全部IT工作者都要天天研究算法、琢磨模型、跑數據、調參數、跑數據、調參數,那些被淘汰的“軟件工程師”會真的成為民工一樣的行業,但是我覺得任何算法都離不開工程實現,再好的模型沒有底層架構的支撐和上層產品應用的展現也無法發揮作用,所以對於一個技術人士,不擅長工程終難把能力發揮到極致。隨着中國和國外前沿科技的接軌,將來一定是小創業團隊成為主流,一個小創業團隊更喜歡算法+工程的全能型人才,跟着我一起學習進步,你將來也許就會是其中一個。
聊聊重構
說起重構,很多人都有感慨,因為只有當遇到比較大的問題的時候才會考慮重構,比如技術人員流動大導致代碼中風格百出,百花齊放,無用代碼一大堆不敢刪,奇葩邏輯遍地皆是卻沒有一行注釋、一篇文檔。在這種無奈情況下,我們不得已選擇了重構,寄希望於解決所有問題,但往往代價比收益高出一個數量級,很多人因為重構被迫出局。但重構這件事情是一件必經之路,任何一個產品從誕生到成熟都會經歷幾次重構,因為沒有人能在最初的時候就預示到最終的邏輯(如果能預示那何必有最初呢),就算像BAT這樣成熟的公司,他們內部的系統也是平均兩年做一次重構。回過頭來說一下我的網站重構的初衷:1)我也是不斷成長的,作為一個想做全棧的工程師來說,新思路總想去嘗試;2)很多關注我的網友覺得我之前寫的教程總有意猶未盡的感覺,希望能深入寫一點;3)php終究不是世界上最好的語言(此處可能引發戰爭),用來用去覺得還是遷移python為好,也和我們的機器學習知識做個融合;
技術棧選擇
首先說語言。我曾經說過,語言只是工具,每門語言都有它存在的理由,它擅長用在什么地方就用在什么地方,不擅長的不要勉強,不歧視、不在一棵樹上吊死、哪個行就上哪個。后端語言我選擇python,因為python是社區最活躍的語種之一且呈上升趨勢,另外也是大數據與人工智能方向的主流語言。web框架我選擇django,因為它更專業更強大,擴展性強,社區也更活躍。前端框架我選擇直接用django模板渲染,沒有選擇angular等前端框架,因為seo不友好
服務端容器選擇
在tomcat、apache httpd、nginx等web服務器下游,需要部署python的應用服務器容器,我選擇uwsgi,它類似於nginx,通過一個守護進程把不同的http請求轉交給子進程並發處理,並且支持多線程的方式,性能較高,更重要的,django會自動幫我們生成wsgi的配置,天然對uwsgi友好
總結
開篇就講這么多,主要還是得看后面我的重構過程,咳咳!出發!
http://www.shareditor.com/blogshow/?blogId=126
安裝開發和運行的基本環境
首先,python是必須的,我們選擇python2.7,沒有安裝可以根據不同的操作系統安裝,如果是rhel或centos可以用yum install python,如果是ubuntu可以用apt-get install python,如果是mac可以用brew install python,如果以上都不行可以直接下官方包安裝(https://www.python.org/downloads/)
然后,安裝django相關組件(當前最新版是1.11):
pip install django
安裝web容器:
pip install uwsgi
小技巧:如果使用pip install安裝庫比較慢,可以用豆瓣的鏡像,方法類似下面:
pip install django -i http://pypi.douban.com/simple --trusted-host pypi.douban.com
創建開源代碼庫
在github中創建倉庫shareditor,並在本地創建空倉庫提交
github庫在:https://github.com/warmheartli/shareditor
本地倉庫如下:
[lichuang@localhost:~/Developer/shareditor $] ls README.md [lichuang@localhost:~/Developer/shareditor $] pwd /Users/lichuang/Developer/shareditor
創建django工程
在安裝django時已經自動幫我們安裝了django-admin工具,執行如下命令自動創建一個完整的工程目錄(其中最后一個參數是工程目錄,倒數第二個參數是工程名):
django-admin startproject shareditor /Users/lichuang/Developer/shareditor
這時能夠找到自動創建的manage.py文件(一個工具腳本,不需要修改),和工程總目錄shareditor(里面包含了配置文件settings.py、總路由配置urls.py、wsgi協議配置文件wsgi.py)
下面我們在這個工程里創建我們網站app:
django-admin startapp web
我們看到它自動創建了web目錄,並且自動幫我們組織了一些文件,包括:
admin.py:數據庫表的后台管理類一般定義在這里
apps.py:這個app的配置信息,這個文件一般不動
migrations目錄:存儲數據庫遷移相關的臨時文件,不需要動
models.py:和數據庫對應的model類一般定義在這里
tests.py:自動化腳本
views.py:視圖層腳本,我一般會把控制邏輯寫到這里
這些文件全都看不懂也沒有關系,到現在為止,我們的網站已經可以運行了,執行:
python manage.py runserver
我們可以看到一些提示,直接訪問http://127.0.0.1:8000/就可以訪問網頁了,如下:
上面的頁面是django展示的默認頁面,下面我們稍作修改來看看django框架是怎么按照我們的指示工作的
helloworld
修改web/views.py,增加如下函數:
from django.http import HttpResponse def index(request): return HttpResponse('Hello World!')
這僅僅是定義一個函數,然並卵
請尊重原創,轉載請注明來源網站www.shareditor.com以及原始鏈接地址
我們來修改一下我們的路由規則,修改shareditor/urls.py,把內容改成:
from web import views urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^$', views.index) ]
下面我們重新執行python manage.py runserver,並打開瀏覽器看看是不是看到了高大上的Hello World!
讓網站更專業
上面執行的python manage.py runserver實際上只是django的一個用於開發和調試的方法,它只是一個進程一個線程在運行,無法支持網站的高並發訪問,下面我們介紹一下如何部署一個專業的網站。
首先我們配置好我們的web容器,在shareditor目錄下創建uwsgi.ini,內容如下:
[uwsgi] chdir = /Users/lichuang/Developer/shareditor http = 127.0.0.1:8080 http-keepalive = 1 module = shareditor.wsgi:application master = true processes = 4 daemonize = /Users/lichuang/Developer/shareditor/logs/uwsgi.log disable-logging = 1 buffer-size = 16384 harakiri = 5 post-buffering = 8192 post-buffering-bufsize = 65536 pidfile = /Users/lichuang/Developer/shareditor/logs/uwsgi.pid enable-threads = true single-interpreter = true
這里的目錄要隨着你部署的目錄做相應修改
因為logs目錄還不存在,所以我們手工mkdir創建一個
下面執行啟動命令:
uwsgi --ini shareditor/uwsgi.ini
這時我們可以查看一下logs/uwsgi.log文件,如果沒有異常信息說明網站已經部署成功了,我們ps ux|grep uwsgi看一下進程:
lichuang 13390 0.4 0.0 2425088 300 s004 S+ 10:19下午 0:00.00 grep --color uwsgi lichuang 13307 0.0 0.0 2491336 924 ?? S 10:18下午 0:00.00 uwsgi --ini shareditor/uwsgi.ini lichuang 13306 0.0 0.0 2491336 2540 ?? S 10:18下午 0:00.00 uwsgi --ini shareditor/uwsgi.ini lichuang 13305 0.0 0.0 2491336 2520 ?? S 10:18下午 0:00.00 uwsgi --ini shareditor/uwsgi.ini lichuang 13304 0.0 0.0 2491336 2484 ?? S 10:18下午 0:00.00 uwsgi --ini shareditor/uwsgi.ini
可以看到啟動了4個進程,其中一個守護進程用來接收和分發請求,3個子進程(對應配置文件里的processes = 4)用來處理請求
這時我們打開瀏覽器訪問:http://127.0.0.1:8080/又能看到Hello World!了
高可用性部署(新手可略過)
另外為了讓我們的網站具有高可用性(高可用就是掛掉一台機器不影響服務),一台機器啟動服務還不行,我們至少要部署兩台完全對等的web服務來同時提供服務,那么用戶在瀏覽器里訪問時到底訪問的是哪個機器呢?這里有兩種實現方案,一種是配置DNS記錄,同一個域名對應多個ip,那么當一個ip不可用時瀏覽器會自動嘗試另外的ip,還有一種方法就是通過穩定的代理服務器(如nginx、apache httpd等)來配置成一個負載均衡代理,對外暴露的一個ip,對內連接到多台web服務器
http://www.shareditor.com/blogshow/?blogId=127
創建和鏈接數據庫
首先,我們需要有一個mysql的服務,你可以選擇自己安裝啟動一個mysql服務,我選擇了阿里雲的雲數據庫RDS(和本地搭建沒有區別,收費但不貴),我創建了一個數據庫名叫db_shareditor(如果本地搭建的mysql,執行命令是create database db_shareditor)
下面我們配置我們的網站工程來連接這個數據庫,修改shareditor/settings.py里的DATABASE改成如下:
DATABASES = {
'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db_shareditor', 'USER': 'username', 'PASSWORD': 'password', 'HOST': 'hostip', 'PORT': '3306', 'OPTIONS': { 'sql_mode': 'traditional', } } }
這里的USER、PASSWORD、HOST、PORT都配置成你自己的,然后我們來檢測一下數據庫配置是否正確
[lichuang@localhost:~/Developer/shareditor $] python manage.py check System check identified no issues (0 silenced).
看到這樣的信息說明我們的配置沒有問題,否則會拋出異常,這時我們根據異常信息再追查問題
創建model並自動生成數據庫表
這里解釋一下什么是model,model就是數據庫表在內存里的數據結構,比如某個數據庫表有A和B兩個字段,那么它對應的model一般也會寫成A和B兩個成員,這樣我們在代碼里操作model的實例就是在操作數據庫
每個model的設計都需要精心打磨,我們首先創建一個BlogPost的model,修改web/models.py,增加如下類定義:
class BlogPost(models.Model): title = models.CharField(max_length=255, verbose_name='文章標題') body = models.TextField(verbose_name='文章內容') create_time = models.DateTimeField(verbose_name='創建時間')
這個類實際上定義了一個數據庫的結構,下面我們用django工具來自動根據這個結構定義生成對應的數據庫表,執行:
python manage.py migrate
這時我們再看一下數據庫多出了這些數據庫表
auth_group
auth_group_permissions
auth_permission
auth_user
auth_user_groups
auth_user_user_permission
django_admin_log
django_content_type django_migrations django_session
這里比較奇怪的是怎么多出了這么一批數據而沒有找到我們的blogpost呢?這是因為settings.py里的INSTALLED_APPS默認安裝了一些其他的玩意,而並沒有安裝我們的web這款app,好,那現在我們暫時先保留默認安裝的app(以后有用),把我們的web添加進去,如下:
INSTALLED_APPS = [
'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes' 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'web', ]
現在我們重新生成migrate文件並創建數據庫表,執行:
python manage.py makemigrations
python manage.py migrate
這時再看數據庫表多出了web_blogpost(django會自動用app名字小寫加下划線加model名的小寫來作為數據庫表的名字,實際上數據庫表名我們也是可以自己來配置的,感興趣自己google一下吧)
創建有關聯關系的數據庫表
我們的每篇文章都會有一個類別(subject,如大數據、全棧技術等),每個類別會對應多篇文章,也就是1對多的關系,那么我們可以利用django的models里的外鍵類型來關聯,如下:
class Subject(models.Model): name = models.CharField(max_length=255, verbose_name='類別名稱') introduce = models.CharField(max_length=255, verbose_name='類別簡介') image = models.ImageField(verbose_name='類別圖片') class BlogPost(models.Model): title = models.CharField(max_length=255, verbose_name='文章標題') body = models.TextField(verbose_name='文章內容') create_time = models.DateTimeField(verbose_name='創建時間') subject = models.ForeignKey(Subject, verbose_name='類別', null=True)
我們重新執行:
python manage.py makemigrations
python manage.py migrate
這時再看數據庫表多出了web_subject,同時web_blogpost也自動多出了一個subject_id字段
有人問了,這用不用外鍵有什么關系呢,手工寫好一個subject_id字段,然后在代碼邏輯里就把這個字段作為查詢subject表的key不就行了嗎?看來該是介紹IDE的時候了,我來給大家介紹一款棒棒的開發工具PyCharm,具體安裝方法自己去百度,我現在裝的是PyCharm 2017.1.4版本,記得一定要配置好Project Interpreter為系統里安裝好django的那個python環境
下面見證奇跡的時刻到了,用PyCharm打開我們上面的shareditor工程,並打開views.py文件,我們來編輯如下一段代碼:
我的天啊!好強大有木有!當我們輸入幾個字母前綴的時候,它會把我們用外鍵關聯的類的各種方法都給我們列出來,再也不用苦逼的查文檔了
創建多對多關系的數據庫表
我們的每篇文章都會有多個標簽(tag, 如:從0到1搭建個人網站、自己動手做聊天機器人等),每個標簽會對應多篇文章,也就是多對多的關系,那么我們可以利用django的models里的ManyToMany類型來關聯,我們添加Tag類如下:
class Tag(models.Model): name = models.CharField(max_length=255, verbose_name='標簽名稱')
並為BlogPost類添加如下成員:
tags = models.ManyToManyField(Tag, verbose_name='標簽')
我們重新執行:
python manage.py makemigrations
python manage.py migrate
這時再看數據庫表多出了web_tag和web_blogpost_tag兩個表,這里的web_blogpost_tag實際上是一個關系表,也就是說,BlogPost類多了tags成員,但web_blogpost表里並沒有多任何字段,但當我們在PyCharm中輸入tags前綴的時候依然會看到相關提示
總結
這一節我們介紹了數據庫表和model之間的關系,以及一對多、多對多關系的使用,下一節我們來繼續討論利用model對數據庫做讀寫
http://www.shareditor.com/blogshow/?blogId=128
django-admin的賬戶管理
當我們直接打開http://127.0.0.1:8000/admin時,雖然能夠看到管理后台登陸界面,但是我們沒有賬號密碼是無法登陸的,需要我們初始化一個超級用戶,那么方法如何呢?我們可以通過執行python manager.py輸出的提示來找到createsuperuser這個命令,執行:
python manage.py createsuperuser
按照提示輸入賬號、郵箱、密碼,然后再次進入http://127.0.0.1:8000/admin就可以登陸了
登陸進入后我們看到了Group和Users兩個管理項,這實際上對應着數據庫里的auth_group和auth_user表,在Users里能看到我們剛剛創建的超級用戶,在這里我們可以添加新的用戶,並為不同用戶配置不同權限
配置admin管理數據庫
還記得上一節我們創建的三個Model及其數據庫表嗎?BlogPost、Subject、Tag,那么怎么才能在django-admin管理后台管理這三個表的內容呢?
修改web/admin.py,添加如下內容:
from django.contrib import admin from .models import BlogPost class BlogPostAdmin(admin.ModelAdmin): list_display = ('title', 'create_time', 'subject', 'tags') admin.site.register(BlogPost, BlogPostAdmin)
重新登陸http://127.0.0.1:8000/admin管理頁面,我們看到:
點進去可以看到文章的管理頁面,因為我們在BlogPost這個model里設置的subject和tags字段不可以為空,因此我們需要提前添加類別和標簽,下面我們再完善一下Subject和Tags的管理類,如下:
class SubjectAdmin(admin.ModelAdmin): list_display = ('name',) class TagAdmin(admin.ModelAdmin): list_display = ('name',) admin.site.register(Subject, SubjectAdmin) admin.site.register(Tag, TagAdmin)
這時我們再嘗試在管理頁面新建一個Subject、一個Tag、一個BlogPost吧,建好之后可以在后台數據庫直接查看到數據已經寫到了數據庫中
管理界面的定制化
愛美的同學可能會發現后台管理界面還不夠漂亮和友好,比如頁面頂部寫的“Django administrator”能不能換成“SharEDITor管理后台”,比如管理首頁里的“WEB”能不能改成“網站”,“Blog posts”能不能改成“文章”,另外我們在新建BlogPost的時候,類別和標簽這兩項里寫的是“Subject object”和"Tag object",都不知道具體信息,下面我們來各個擊破做一下定制
首先我們修改web/models.py,為Subject類添加如下成員:
class Meta: verbose_name_plural = '類別' def __unicode__(self): return self.name
再為Tag類添加如下成員:
class Meta: verbose_name_plural = '標簽' def __unicode__(self): return self.name
再為BlogPost類添加如下成員:
class Meta: verbose_name_plural = '文章' def __unicode__(self): return self.title
解釋一下,這里的verbose_name_plural就是在這個結構在管理頁面里的展示名稱,__unicode__就是這個結構里每一個對象的展示形式,不用多說,直接看一下你的管理頁面的效果就知道了
管理頁面總標題因為是django-admin自身的內容,因此做定制有些復雜些,此處新手可以略過
請尊重原創,轉載請注明來源網站www.shareditor.com以及原始鏈接地址
在根目錄下創建如下目錄templates/admin,並新建base_site.html文件,內容如下:
{% extends "admin/base.html" %} {% load i18n %} {% block title %}{{ title }} | {% trans 'SharEDITor后台管理' %}{% endblock %} {% block branding %} <h1 id="site-name">{% trans 'SharEDITor后台管理' %}</h1> {% endblock %} {% block nav-global %}{% endblock %}
修改shareditor/settings.py文件,在TEMPLATES => DIRS配置項中添加'templates',這樣就可以自動找到模板目錄了,實際上這里的admin/base_site.html是重寫了django-admin的源碼。重新打開后台管理界面看下效果吧
圖片管理(高級內容,新手略過)
我們在新建一個類別的時候要為image字段選擇一張圖片,我們看到圖片實際上上傳到了根目錄下。這種方式存在一些問題:1)如果要在網站中展示這張圖片需要為其單獨指定路由;2)如果網站多機部署無法實時同步數據;3)如果圖片很大,會耗費很多帶寬,響應慢
為了解決如上問題,我們引入阿里雲的對象存儲OSS服務(收費,但不貴,這里不是幫阿里雲打廣告,但是阿里雲確實做的比較好),它的優點是有CDN加速,也就是不同地域都有鏡像,訪問快,而且價格低廉,可比同樣的網絡帶寬便宜多了
OSS的使用請見官方文檔,我這里直接貼代碼,懂的可以參考,不懂的可以直接用
首先要在阿里雲的OSS中創建一個Bucket,如shareditor-shareditor,讀寫權限一定要選擇“公共讀”
其次要安裝oss2庫,執行:
pip install oss2
然后在我們代碼的根目錄創建commons目錄(用於放置所有公共組件),並在其中創建一個空的__init__.py(作為lib的目錄都要有這個文件,否則無法import),並創建ossutils.py文件,內容如下:
# -*- coding: utf-8 -*- import oss2 import time AccessKeyId = '你的AccessKey' AccessKeySecret = '你的AccessKey密碼' Endpoint = 'oss-cn-beijing.aliyuncs.com' InternalEndpoint = 'oss-cn-beijing-internal.aliyuncs.com' def upload_oss(bucket_name, file_name, bytes_content): """ :param bucket_suffix: 區分測試環境和線上環境 :param file_name: 會自動添加時間戳 :param bytes_content: 二進制的文件內容 :return: 外網可以訪問的url """ auth = oss2.Auth(AccessKeyId, AccessKeySecret) bucket = oss2.Bucket(auth, Endpoint, bucket_name) file_path = 'dynamic/' + str(int(time.time())) + '_' + file_name result = bucket.put_object(file_path, bytes_content) if result.status == 200: return 'http://' + bucket_name + '.oss-cn-beijing.aliyuncs.com/' + file_path else: return None
下面我們重載Subject的image的上傳邏輯,修改web/admin.py,引入ossuitls:
from commons.ossutils import upload_oss
聲明BucketName變量下面會用到:
BucketName = 'shareditor-shareditor'
修改SubjectAdmin類,添加如下方法:
def save_model(self, request, obj, form, change): if 'image' in request.FILES: image_name = request.FILES['image'].name image_content = request.FILES['image'].read() url = upload_oss(BucketName, image_name, image_content) if url: obj.image = url super(SubjectAdmin, self).save_model(request, obj, form, change)
這時我們重新修改一個類目,重新上傳圖片,我們發現圖片已經不再保存到本地文件了,而在阿里雲的OSS里找到了上傳的文件,而在我們的數據庫里存儲了這個圖片在阿里雲OSS中的url,可以直接訪問
總結
有關admin管理后台的內容以上這些基本夠用了,剩下的就是根據你的業務邏輯去設計自己的表結構,發揮自身的主動性啦
http://www.shareditor.com/blogshow/?blogId=129