曾经有人说我前端很水,那么在这一系列文章中我打算把前后端融合在一起来做一次网站的全面重构,希望可以把刚刚入行的同学带上正途
请尊重原创,转载请注明来源网站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