<[object Object]>

 web 负基础入门--把手教你撸出选课系统

TIP

学习 web 不是一朝一夕的事, 这篇文章只能帮你快速做出一个简单的选课系统. 如果你想真正学习 web 建议从基础慢慢看起.

网页是什么

通俗的讲, 一张网页由 html , css , JavaScript 组成.

html & css

html 通常决定了你的网站里有什么, css 通常决定了你的网站什么样.

<div style="color:red"> Hello </div>
<div> Wrold </div>
Hello
Wrold

带尖括号的,我们把它称为元素,它是 html 最基本的组成单位.

<div>是 html 中最常用的元素, 它代表一个容器, 这个容器里可以嵌套任何其他元素.

<div>括号里的style叫做元素的属性,除style之外还有很多,有些属性所有的元素都有, 有些则某些元素才有.

style里的就是css代码了, css 的代码可以直接写在元素里(内联属性), 也可以从外部引入. 通过元素的classid属性对元素进行选择:

<div id="container">
    <h1 class="text">Hello </h1>
    <p class="text">world</p>
</div>

<style>
    .text{
        color:#409eff;
    }
    #container{
        border:1px solid #bcbcbc;
        padding:1em;
    }
</style>

Hello

world

TIP

看不懂 css 代码? 没关系,我们之后用到什么讲什么.

JavaScript

简称 js. 它是具有逻辑的脚本语言,可以让网页实现一些复杂的功能. 当然我们要以简单为核心, 能不用尽量就不用~

web 的交互流程

想一想,如果让你用 C 写出一个小小的桌面应用程序.要求按照某种格式展示数据库里某张表的的数据. 你会怎么做?

你一定会觉得很简单: 从数据库里拿到数据 -> 处理格式 -> 将变量绑定到控件上. 在 web 的世界中也是这样,但又略有不同.

下面我们把浏览器称为客户端, 服务器称为服务端.

一次典型的交互

简单来讲, 当你打开浏览器去访问一个网址,  就是在发送一次请求(request), 你输入的网址(URL)指向了服务器上特定的资源, 服务端收到请求后,响应(response)对应的资源.

这个资源可以是一张图片, 一个文件, 也可以是一个 html 文档. html 文档在浏览器会自动解析为可以直接看到的页面.

有些时候, 客户端并不会请求一个资源,而是请求修改服务端的某些内容. 如上传文件.

我们将这两类请求粗略的分为

  • GET 类型,即请求资源
  • POST 类型,即请求修改

Web VS 桌面应用

  1. Web 与桌面应用最大的不同之处在于, Web 客户端对数据库的请求全部是通过服务端中转的, 因此也衍生出了很多标准, 如MVC模型, Restful风格的 API.

    那肯定有同学问了, 让服务端只做资源的分发, 对数据库的操作全部让客户端来做, 这样可行吗?

    答案是不可行. 虽然技术层面上完全可以实现:

    • js 是动态语言, 不需要编译, 所有代码全部都暴露在客户端中. 在客户端中连接数据库无疑是让你的数据库裸奔.
    • 数据库驱动的庞大的代码量会使你的网页异常臃肿.
  2. 传统桌面应用是基于 socket 的双向数据流交互, 而 web 中只有客户端给服务端发起请求, 服务端才能返回资源.

    这也解释了为什么一般网页需要刷新(重新发起请求)一次才可以更新页面上的数据.

    TIP

    实际上,现代 web 技术已经实现了高性能的双向交互,感兴趣可以查一查 WebSocket 技术,这并不是这篇文章涉及的.

搭建你的第一个网站

从本节开始就要进入到实际的代码编写环节了. 经过一定的调研, 本篇文章只介绍利用MVC模型搭建一个简单网站的一般方法. 这可以最大限度的减小你的网页对 js 的依赖.

这是搭建初代网站的基本方法, 依赖服务端. 所有的逻辑事务都在服务端进行, 客户端只负责展示和收集数据.

TIP

现代 web 体系中, 随着 js 的迅猛发展,复杂的事务处理逐渐转向了客户端,服务端只提供最基本的数据接口.

web 框架

WARNING

本篇文章只介绍基于 python 的 web 框架,需要你对 python3 的语法较为熟悉

一定有很多人有疑问, 网站和 python 有什么关系? 简单来说,python 可以捕获到客户端的请求并返回'数据'. 文件, 图片, html 文档都是'数据'的一种.

如果你想了解的更深, 可以查阅 http 原理, 显然这不是这篇文章将要涉及的.

配置环境

  1. 安装Flask

    pip install flask

    TIP

    Flask 是轻量 web 框架, 相比于 Django 这种完善的'巨无霸', 更适合入门.

  2. 创建入口文件在项目根目录下创建app.py文件, 并输入:

    # my-project/app.py
    
    from flask import Flask
    
    # 实例化
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        return 'Home'
    
    @app.route('/hello')
    def hello():
        return 'Hello World!'
    
    if __name__ == '__main__':
        app.run(debug=True)
    

    输出:

    * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
    * Restarting with stat
    * Debugger is active!
    * Debugger PIN: 198-300-784
    

Hello World

访问http://127.0.0.1:5000http://127.0.0.1:5000/hello 可以看到浏览器显示出了我们在app.py中返回的字符串.

@app.route('/') 这类函数在 python 中称为装饰器, 如果你不懂, 没有关系, 只需要知道:客户端访问的 URL 符合route中参数的匹配规则(rule)时,就会触发相应的函数.

这看起来不像是网页? 让我们稍作修改:

@app.route('/hello')
def hello():
    return '''
    <h1 style="color:#409eff">Hello world</h1>
    <p>This is a demo page!</p>
    '''

TIP

color : 改变当前元素下所有子元素字体颜色

再次访问http://127.0.0.1:5000/hello

Hello world

This is a demo page!

参数处理

给你的地址添加变量部分,可以捕获地址中携带的参数

新增一个处理函数:

@app.route('/hello/<name>')
def say_hello(name):
    return '''
    <h1 style="color:#409eff">Hello world</h1>
    <p>Welcome! %s</p>
    ''' % name

现在试着在地址后增加一个变量:

http://localhost:5000/hello/wsq

Hello world

Welcome! wsq

现在, 你可能惊奇的发现, 我们的网页再也不是静态的'死'网页了, 它可以根据用户给出的参数进行动态的数据显示.

除此之外, 还有很多携带参数的方式. 最常用的携带在请求(request)中的参数:

引入request

from flask import request

处理携带在GET请求中的参数

现在, 修改我们的首页处理函数:

@app.route('/')
def index():
    res = 'The method of your request is [%s], and your arguments are %s'
    return res % (request.method, request.args)

现在让我们携带参数来访问首页: http://localhost:5000/?username=wsq&&password=test

The method of your request is [GET], and your arguments are ImmutableMultiDict([('username', 'wsq'), ('password', 'test')])

携带在 URL 中的参数已经转化为Dict的形式了, 我们像处理普通字典一样对参数进行处理.

处理携带在POST请求中的参数

在我们的MVC模型中,客户端是无法直接发起POST请求的. 只能先通过GET请求, 拿到一张预先设定好格式的表单(form), 通过表单来提交(submit)POST请求.

修改我们的首页处理函数:

@app.route('/')
def index():
    if request.method == 'GET':
        return '''
        <form method="POST">
            <input name="username" placeholder="请输入您的用户名"/>
            <input name="password" type="password" placeholder="请输入您的密码"/>
            <button type="submit">提交</button>
        </form>
        '''

刷新页面我们可以看到一张朴素的表单. 输入内容,点击提交按钮:

Method Not Allowed

The method is not allowed for the requested URL.

出现这个页面的原因是因为, 我们的首页处理函数默认只允许客户端发送GET请求. 所以, 继续对我们的函数进行修改:

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return '''
        <form method="POST">
            <input name="username" placeholder="请输入您的用户名"/>
            <input name="password" type="password" placeholder="请输入您的密码"/>
            <button type="submit">提交</button>
        </form>
        '''
    else:
        res = 'The method of your request is [%s], and your form is %s'
        return res % (request.method, request.form)

如果你还处在这个页面, 刷新时浏览器会提示我们是否重新发送表单,点击是.

或者你已经回退(back)到之前的页面,输入值重新点击提交:

The method of your request is [POST], and your form is ImmutableMultiDict([('username', 'wsq'), ('password', 'test')])

携带在表单中的数据已经全部转化为Dict的形式了, 表单中<input>name属性为键(key), 输入的内容为值(value).

模板渲染

目前为止, 我们所有的 html 代码全部都在 python 中以字符串的形式手写, 这十分无趣,而且相当繁琐,因为你必须手动对 html 做转义来保证应用的安全。

引入

flask 提供了一个模板渲染包(render_template), 可以让我们 html 模板(template)截获变量,并重新生成完整的 html 文档,这个过程称为渲染(render)

from flask import render_template

现在, 修改我们的首页处理函数:

@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'GET':
        return render_template('index.html')
    else:
        return render_template(
            'index_res.html',
            method=request.method, form=request.form
        )

创建模板

现在,在根目录下创建一个新的文件夹:templates, 并在此文件夹内新建模板:index.htmlindex_res.html, flask 默认会在这个文件夹内寻找模板.

<!-- my-project/templates/index.html -->

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
 <form method="POST">
    <input name="username" placeholder="请输入您的用户名"/>
    <input name="password" type="password" placeholder="请输入您的密码"/>
    <button type="submit">提交</button>
</form>
</body>
</html>
<!-- my-project/templates/index_res.html -->

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
'The method of your request is [{{ method }}], and your form is {{ form }}'
</body>
</html>

TIP

不懂这一大坨标签是什么鬼? 没关系, 我们只关注 <body>内的元素

我们在渲染index_res.html的时候, 传入的两个模板变量(context), 在模板中可以通过嵌套的两个花括号的形式获取到.

模板语法

观察这两个模板文件, 它们的唯一区别是:是否有context传入. flask 给出的模板具有一套类似于 python 的语法, 我们将要用到的只有简单的判断, 循环, 随机访问. 我们可以通过判断来合并这两个模板.

现在更改我们的首页处理函数:

@app.route('/', methods=['GET', 'POST'])
def index():
    # mock user data
    username = ['wsq', 'lmy']
    userinfo = {
        'wsq': 'test',
        'lmy': 'test'
    }
    context = None

    if request.method == 'POST':
        if request.form['username'] not in username:
            context = "username does not exist"
        elif not request.form['password'] == userinfo[request.form['username']]:
            context = "password error"
        else:
            context = "success"
    return render_template('index.html', context=context)

修改我们的 template

<!DOCTYPE html>
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>Index</title>
</head>
<body>
{% if context %}
    {{ context }}
{% else %}
    <form method="POST">
        <input name="username" placeholder="请输入您用户名"/>
        <input name="password" type="password" placeholder="请输入您的密码"/>
        <button type="submit">提交</button>
    </form>
{% endif %}
</body>
</html>

TIP

如果你的 IDE 是 Pycharm , 会有完善的语法提示和高亮.

重新提交表单,观察返回结果.

静态文件

目前为止, 我们的 web 框架只提供了字符串类型的数据. 我们要在网页中展示一张图片, 或者引用一个文件是通过 URL 的形式去定位的.

<h1>
    <img
        src="https://wsq.cool/icons/apple-touch-icon-60x60.png"
        style="vertical-align:middle">
    wsq.cool
</h1>

wsq.cool

TIP

vertical-align是一个较为复杂的属性,字面意思是设置垂直对齐方式.

通常情况下, 这些静态文件由反向代理处理, 为了减少学习成本, 我们用 flask 将本地文件映射到相应的 URL 中.

现在, 在你的项目根目录创建文件夹static,并放入一张你喜欢的图片并命名为logo.png

因为要用 URL 定位这张图片, 所以文件名最好不要含特殊字符,如中文,转义字符,空格等.

<img src="/static/logo.png">

flask 会将项目根目录下的static文件夹映射至/static上,  通过嵌套的层级关系可以访问到此文件夹下的所有文件.

美化你的页面

目前为止,我们已经成功的做出一个朴素的用户登录系统. 下面我们从界面(UI)开始, 慢慢完善我们的页面.

对于初学者来说, 想快速写出美观实用的界面几乎是天方夜谭. 最好的办法是用别人已经写好的组件库来快速构建我们的页面. 大名鼎鼎的 Bootstrap 就是干这种事的.

TIP

bootstrap 过于庞大, 为了减小学习成本,我们引入一套轻量级的, 遵循 Material Design 的框架 MDUI

引入 MDUI

1. 下载

  1. 将 css,font,js,icons 文件夹复制到项目根目录下的static文件夹内.

  2. 在 template 中引入

    <!doctype html>
    <html lang="zh">
    <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
        <!-- MDUI CSS -->
        <link rel="stylesheet" href="/static/css/mdui.min.css">
    
        <title>Index</title>
    </head>
    <body>
    
    <!-- 分割线 -->
    
        <h1>Hello, world!</h1>
    
    <!-- 分割线 -->
    
    <!-- Optional JavaScript -->
    <script src="/static/js/mdui.min.js"></script>
    </body>
    </html>
    

TIP

看起来很多, 其实我们真正关注的只是中间的hello world 这一行

使用 MDUI

static/css文件夹中创建style.css 我们接下来所有的 css 代码都写入这个文件中.

在 template 中的<head>元素中引入我们新增的 css 文件:

 <link rel="stylesheet" href="/static/css/style.css">

style.css文件中写入:

.my-container {
  min-height: 100vh; /*1vh = 1%的屏幕高度, vw同理*/
  display: flex; /*flex 弹性布局*/
  align-items: center; /*主轴居中(水平)*/
  justify-content: center; /*交叉轴居中(垂直)*/
}

.my-logo {
  display: flex;
  align-items: center;
  font-size: 1.5em; /* em 为相对于字体的大小单位 如果当前元素的字体大小为16px 则 1em = 16px*/
}

/*选择my-logo类下的img元素*/
.my-logo > img {
  width: 2em;
  height: 2em;
  margin-right: 0.5em; /*margin为外边距*/
}

.login-dialog {
  width: 30em;
}

body {
  background-color: #eaeaea;
}

.error-tip {
  color: red;
}

.login-btn {
  width: 100%;
}

修改<body>及其子元素

<!-- 设置主题色与强调色 -->
<body class="mdui-theme-primary-blue mdui-theme-accent-light-blue">


<!--容器属性-->
<div class="my-container mdui-container">
    <!--卡片属性-->
    <div class="login-dialog mdui-card">
        <!--卡片标题-->
        <div class="mdui-card-header">
            <div class="my-logo">
                <img src="/static/logo.png">
                <span>用户登录</span>
            </div>
        </div>
        <!--卡片内容-->
        <div class="mdui-card-content">
            <form method="POST">
                <!--文本框属性-->
                <div class="mdui-textfield mdui-textfield-floating-label">
                    <!--浮动标签-->
                    <label class="mdui-textfield-label">用户名</label>
                    <input name="username" class="mdui-textfield-input" required/>
                    <div class="mdui-textfield-error">用户名不能为空</div>

                </div>
                <div class="mdui-textfield mdui-textfield-floating-label">
                    <label class="mdui-textfield-label">密码</label>
                    <input name="password" class="mdui-textfield-input" type="password" required/>
                    <div class="mdui-textfield-error">密码不能为空</div>

                </div>
                {% if  context %}
                    <span class="error-tip">{{ context }}</span>
                {% endif %}
                <!--蓝色白字按钮-->
                <button type="submit" class="login-btn mdui-btn mdui-color-blue mdui-text-color-white-text mdui-m-t-3">
                    登录
                </button>
            </form>
        </div>
    </div>
</div>

<!-- 分割线 -->


<!-- Optional JavaScript -->
<script src="/static/js/mdui.min.js"></script>
</body>

TIP

充分利用你的搜索引擎, 并阅读 MDUI 的相关文档

这是这一阶段的效果图:

与数据库的交互

目前为止, 我们的分发给客户端的 html 页面已经与服务端完全连接起来, 并且我们做出了一个简易的登录系统.

  • html 文档完全由 python 渲染完成.
  • 我们可以截获用户的提供的参数并作出一定处理.

接下来我们要关心的是: 如何通过 python 操作数据库.

WARNING

阅读以下内容前, 你需要对关系型数据库(SQL)有一定的了解

数据库的配置

本篇文章中我们将使用mysql 5.7 未完..