Python Flask 是一种轻量级的网页框架,只要五行程序码,就可以架设网页服务器 :
$ pip install Flask
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
$ flask run
出现「Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)」的文字
浏览器访问 127.0.0.1:5000,出现 Hello World ,代表架设完成 !
在 Flask 的官网,对於 「micro」 有这样的定义 :
轻量并不代表缺少功能,而是在保持核心简单的情况下,留有扩展空间。
做法就是不为你事先做出决定,只会包含「你需要的一切,而没有你不需要的」。
除了说明上述五行程序码代表的含义,还会延伸几项後端开发常见的配置:
从 Flask 入门网站开发,可以带你快速的了解,网页框架通常会具备的功能,以及累积网站开发後端的知识。
对於後续要了解 Python 在网路领域,相关的技术与应用,例如: 爬虫的原理或者资料库的数据分析,
都可以大幅提升学习的效率。
章节中的程序码范例
Python 版本建议 3.4 以上,内置的 pip 工具可以节省很多时间
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello, World!"
第一行 : from flask import Flask
另外一种更常见的 import 方式,可以简化第二行的建构式宣告。
方法一:
import Flask
app = flask.Flask(__name__)
方法二:
from flask import Flask
app = Flask(__name__)
方法二为官网的范例。
第二行 : app = Flask(__name__)
Flask 类别 初始化时 传入的 __name__ 参数,代表当前模组的名称。
是固定用法,以便让 Flask 知道在哪里寻找资源。
(例如: 模板和静态文件)
第三行 : @app.route("/")
装饰器是告诉 Flask,哪个 URL 应该触发我们的函式。
斜线代表的就是网站的根目录,可以叠加。
例如: 新增一个 /hello 的位址
@app.route("/")
@app.route("/hello")
def hello():
return "Hello, World!"
网站访问首页与/hello,呈现的同样是 hello 函式,回传的 Hello World 文字。
def hello():
: 被触发的函式 第四行
return "Hello, World!"
: 函式回传的文字字串,也等於 Web API 回传的内容。第五行
flask run 指令
官方范例,档名为 app.py 使用的是 flask run 指令,可以直接启动网站。
在日常的开发中,可以再加上 python 的 main 方法,
执行 app.run() 函式,执行网页服务器的启动动作。
调整後 : hello-world.py
# save this as app.py
import flask
app = flask.Flask(__name__)
@app.route("/")
@app.route("/hello")
def hello():
return "Hello, World!"
if __name__ == '__main__':
app.run()
除了固定的导向位址,URL 也可以成为函式接收的参数
routing.py
@app.route('/data/appInfo/<name>', methods=['GET'])
def queryDataMessageByName(name):
print("type(name) : ", type(name))
return 'String => {}'.format(name)
<name>
代表接收 name 参数为 字串型态
http://127.0.0.1:5000/data/appInfo/FlaskSE
type(name) : <class 'str'>
String => FlaskSE
@app.route('/data/appInfo/id/<int:id>', methods=['GET'])
def queryDataMessageById(id):
print("type(id) : ", type(id))
return 'int => {}'.format(id)
http://127.0.0.1:5000/data/appInfo/id/5
type(id) : <class 'int'>
int => 5
@app.route('/data/appInfo/version/<float:version>', methods=['GET'])
def queryDataMessageByVersion(version):
print("type(version) : ", type(version))
return 'float => {}'.format(version)
http://127.0.0.1:5000/data/appInfo/version/1.01
type(version) : <class 'float'>
float => 1.01
Python Flask 使用 Jinja2 的模板引擎
@app.route('/text')
def text():
return '<html><body><h1>Hello World</h1></body></html>'
Hello World
元素简单的格式还可以,复杂一点,回传 html 档案会较为理想。
在 python 执行档的目录下,创建 templates 资料夹,html 档案放置於此
render.py
@app.route('/home')
def home():
return render_template('home.html')
templates/home.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home</title>
</head>
<body>
<h1>My Website Text</h1>
<table border="1">
<tr>
<td>Text Text Text</td>
<td>Text Text Text</td>
<td>Text Text Text</td>
</tr>
<tr>
<td>Text Text Text</td>
<td>Text Text Text</td>
<td>Text Text Text</td>
</tr>
<tr>
<td>Text Text Text</td>
<td>Text Text Text</td>
<td>Text Text Text</td>
</tr>
</table>
</body>
</html>
http://127.0.0.1:5000/home
API 返回网页时,可以做更多的事情
先看完整的程序码,接续有说明。
render.py
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/text')
def text():
return '<html><body><h1>Hello World</h1></body></html>'
@app.route('/home')
def home():
return render_template('home.html')
@app.route('/page/text')
def pageText():
return render_template('page.html', text="Python Flask !")
@app.route('/page/app')
def pageAppInfo():
appInfo = { # dict
'id': 5,
'name': 'Python - Flask',
'version': '1.0.1',
'author': 'Enoxs',
'remark': 'Python - Web Framework'
}
return render_template('page.html', appInfo=appInfo)
@app.route('/page/data')
def pageData():
data = { # dict
'01': 'Text Text Text',
'02': 'Text Text Text',
'03': 'Text Text Text',
'04': 'Text Text Text',
'05': 'Text Text Text'
}
return render_template('page.html', data=data)
@app.route('/static')
def staticPage():
return render_template('static.html')
if __name__ == '__main__':
app.run()
templates/page.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Template - Page</title>
</head>
<body>
<h1>Template - Page </h1>
<h2>{{text}}</h2>
{% if appInfo != undefined %}
<h2>AppInfo : </h2>
<p>id : {{appInfo.id}}</p>
<p>name : {{appInfo.name}}</p>
<p>version : {{appInfo.version}}</p>
<p>author : {{appInfo.author}}</p>
<p>remark : {{appInfo.remark}}</p>
{% endif %}
{% if data != undefined %}
<h2>Data : </h2>
<table border="1">
{% for key, value in data.items() %}
<tr>
<th> {{ key }} </th>
<td> {{ value }} </td>
</tr>
{% endfor %}
</table>
{% endif %}
</body>
</html>
API 附带参数: @app.route('/page/text')
render.py
@app.route('/page/text')
def pageText():
return render_template('page.html', text="Python Flask !")
templates/page.html
<h1>Template - Page </h1>
<h2>{{text}}</h2>
{{ text }}
就可以将资料显示在画面上API 字典型态与页面条件式 : @app.route('/page/app')
render.py
@app.route('/page/app')
def pageAppInfo():
appInfo = { # dict
'id': 5,
'name': 'Python - Flask',
'version': '1.0.1',
'author': 'Enoxs',
'remark': 'Python - Web Framework'
}
return render_template('page.html', appInfo=appInfo)
@app.route('/page/app')
render_template('page.html', appInfo=appInfo)
templates/page.html
{% if appInfo != undefined %}
<h2>AppInfo : </h2>
<p>id : {{appInfo.id}}</p>
<p>name : {{appInfo.name}}</p>
<p>version : {{appInfo.version}}</p>
<p>author : {{appInfo.author}}</p>
<p>remark : {{appInfo.remark}}</p>
{% endif %}
{% if boolean %}
{% endif %}
if appInfo != undefined
: 如果 appInfo 有资料,html 标签内容生效{{appInfo.object}}
: 字典参数取用资料方法,同样两个大括号包覆後生效。API : 字典型态与页面回圈
render.py
@app.route('/page/data')
def pageData():
data = { # dict
'01': 'Text Text Text',
'02': 'Text Text Text',
'03': 'Text Text Text',
'04': 'Text Text Text',
'05': 'Text Text Text'
}
return render_template('page.html', data=data)
@app.route('/page/data')
return render_template('page.html', data=data)
templates/page.html
{% if data != undefined %}
<h2>Data : </h2>
<table border="1">
{% for key, value in data.items() %}
<tr>
<th> {{ key }} </th>
<td> {{ value }} </td>
</tr>
{% endfor %}
</table>
{% endif %}
{% if boolean %}
{% endif %}
{% for key, value in data.items() %}
{{ key }}
: 回圈 key 值{{ value }}
: 回圈 value 值{% endfor %}
在 python 执行档的目录下,创建 static 资料夹,.js 与 .css 档案放置於此
前端开发的 javascript 与 css 档案必须放在 static 资料夹才会生效。
static.py
@app.route('/static')
def staticPage():
return render_template('static.html')
static/script.js
function sayHello(){
alert("Hello World");
}
templates/static.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Static - Page</title>
</head>
<body>
<h1>Template - Page</h1>
<button id="btnHello" onClick="sayHello()">Say Hello</button>
<!-- <script src="../static/script.js"></script> -->
<script type = "text/javascript"
src = "{{ url_for('static', filename = 'script.js') }}" ></script>
</body>
</html>
{{ url_for('static', filename = 'script.js') }}
结合路由注册与网页模版,完整实现前端与後端的资料交换
前端 : 示范两种
後端 : JSON 格式
同样先看代码,接续代码说明
form.py
from flask import Flask, request, render_template, redirect, url_for
app = Flask(__name__)
@app.route('/form')
def formPage():
return render_template('Form.html')
@app.route('/submit', methods=['POST', 'GET'])
def submit():
if request.method == 'POST':
user = request.form['user']
print("post : user => ", user)
return redirect(url_for('success', name=user, action="post"))
else:
user = request.args.get('user')
print("get : user => ", user)
return redirect(url_for('success', name=user, action="get"))
@app.route('/success/<action>/<name>')
def success(name, action):
return '{} : Welcome {} ~ !!!'.format(action, name)
if __name__ == '__main__':
app.run()
templates/form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form - Submit</title>
</head>
<body>
<h2>POST</h2>
<form action="/submit" method="post">
<h2>Enter Name:</h2>
<p><input type="text" name="user" /></p>
<p><input type="submit" value="submit" /></p>
</form>
<h2>GET</h2>
<form action="/submit" method="get">
<h2>Enter Name:</h2>
<p><input type="text" name="user" /></p>
<p><input type="submit" value="submit" /></p>
</form>
</body>
</html>
前端 : Form 表单提交
templates/form.html
<form action="/submit" method="post">
<h2>Enter Name:</h2>
<p><input type="text" name="user" /></p>
<p><input type="submit" value="submit" /></p>
</form>
<h2>GET</h2>
<form action="/submit" method="get">
<h2>Enter Name:</h2>
<p><input type="text" name="user" /></p>
<p><input type="submit" value="submit" /></p>
</form>
<form></form>
: 两个 form 表单元素action
: 提交目标,也就是路由的 URLmethod
: http 常见的提交方法,这里分别实作 get 与 post 方法<input type="text" name="user" />
: 传递表单参数 name<input type="submit" value="submit" />
: html 元素,form 提交按钮後端 : 网页模版与资料接口
form.py
@app.route('/form')
def formPage():
return render_template('Form.html')
from flask import ... , request, redirect, url_for
@app.route('/submit', methods=['POST', 'GET'])
def submit():
if request.method == 'POST':
user = request.form['user']
print("post : user => ", user)
return redirect(url_for('success', name=user, action="post"))
else:
user = request.args.get('user')
print("get : user => ", user)
return redirect(url_for('success', name=user, action="get"))
@app.route('/submit', methods=['POST', 'GET'])
@app.route('/submit', ... )
: 注册路由为 /submit@app.route( ... , methods=['POST', 'GET'])
: 接收 POST 与 GET 方法request
: import 导入,物件的 method 成员,可以知道前端传递的是使用 HTTP 的哪种方法if request.method == 'POST':
: POST 方法必须使用 request.form 的变数else:
: GET 方法必须使用 request.args 的变数return redirect(url_for('success', name=user, action="post"))
@app.route('/success/<action>/<name>')
def success(name, action):
return '{} : Welcome {} ~ !!!'.format(action, name)
完整 form 表单提交 与 接收成功後转址流程
同样先看代码,接续代码说明
文件结构
ajax.py
from flask import Flask, render_template, request, jsonify, json
app = Flask(__name__)
@app.route('/data')
def webapi():
return render_template('data.html')
@app.route('/data/message', methods=['GET'])
def getDataMessage():
if request.method == "GET":
with open('static/data/message.json', 'r') as f:
data = json.load(f)
print("text : ", data)
f.close
return jsonify(data) # 直接回传 data 也可以,都是 json 格式
@app.route('/data/message', methods=['POST'])
def setDataMessage():
if request.method == "POST":
data = {
'appInfo': {
'id': request.form['app_id'],
'name': request.form['app_name'],
'version': request.form['app_version'],
'author': request.form['app_author'],
'remark': request.form['app_remark']
}
}
print(type(data))
with open('static/data/input.json', 'w') as f:
json.dump(data, f)
f.close
return jsonify(result='OK')
if __name__ == '__main__':
app.run()
data.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="../static/jquery-3.6.0.min.js"></script>
<title>WebAPI</title>
</head>
<body>
<h1>DATA : API</h1>
<h2>API : GET</h2>
<button id='btnGet'>GET</button>
<hr>
<h2>API : POST</h2>
<p>APP_ID :
<input id="app_id" name="app_id" type="number" />
</p>
<p>APP_NAME :
<input id="app_name" name="app_name" type="text" />
</p>
<p>APP_VERSION :
<input id="app_version" name="app_version" type="text" />
</p>
<p>APP_AUTHOR :
<input id="app_author" name="app_author" type="text" />
<p>APP_REMARK :
<input id="app_remark" name="app_remark" type="text" />
</p>
<button id="btnPost">POST</button>
<hr>
<h3>Console : </h3>
<div id="console"></div>
<script>
$(function () {
var $btnGet = $('#btnGet');
var $console = $('#console');
var $btnPost = $('#btnPost');
var $edtAppId = $('#app_id');
var $edtAppName = $('#app_name');
var $edtAppVersion = $('#app_version');
var $edtAppAuthor = $('#app_author');
var $edtAppRemark = $('#app_remark');
$btnGet.off('click').on('click', function () {
$.ajax({
url: '/data/message',
data: {},
type: 'GET',
success: function (data) {
$console.text("");
$console.append("data[id] : " + data.appInfo.id + "---");
$console.append("data[name] : " + data.appInfo.name + "---");
$console.append("data[version] : " + data.appInfo.version + "---");
$console.append("data[author] : " + data.appInfo.author + "---");
$console.append("data[remark] : " + data.appInfo.remark + "---");
$edtAppId.val(data.appInfo.id);
$edtAppName.val(data.appInfo.name);
$edtAppVersion.val(data.appInfo.version);
$edtAppAuthor.val(data.appInfo.author);
$edtAppRemark.val(data.appInfo.remark);
},
error: function (xhr) {
alert('Ajax request 发生错误');
}
});
})
$btnPost.off('click').on('click',function(){
$.ajax({
url: '/data/message',
data: {
"app_id" : $edtAppId.val() ,
"app_name" : $edtAppName.val(),
"app_version" : $edtAppVersion.val(),
"app_author" : $edtAppAuthor.val(),
"app_remark" : $edtAppRemark.val(),
},
type: 'POST',
success: function (data) {
$console.text("result = ");
$console.append(data.result);
},
error: function (xhr) {
alert('Ajax request 发生错误');
}
});
});
});
</script>
</body>
</html>
/static/jquery-3.6.0.min.js
jQuery 函式库,Ajax() 函式会使用到
/data/input.json
空白内容,用於写入资料的目标档案
/data/message.json
{
"appInfo" : {
"id" : 5,
"name" : "Python - Flask" ,
"version" : "1.0.1" ,
"author" : "author" ,
"remark" : "Python - Web Framework"
}
}
後端 : 网页模版与资料接口
ajax.py
@app.route('/data')
def webapi():
return render_template('data.html')
@app.route('/data/message', methods=['GET'])
def getDataMessage():
if request.method == "GET":
with open('static/data/message.json', 'r') as f:
data = json.load(f)
print("text : ", data)
f.close
return jsonify(data) # 直接回传 data 也可以,都是 json 格式
@app.route('/data/message', methods=['GET'])
: 注册路由为 data/message
,接收 Get 方法with open('static/data/message.json', 'r') as f:
: 读取静态文件 data 资料夹下的 message.json 档案
data = json.load(f)
: 使用 json 物件,读取 message.json 档案内的 json 文字格式。f.close
: 读取完成後,关闭文件。return jsonify(data)
: data 为 JSON 格式的字串,可以直接回传,可以使用 jsonify() 函式,序列化後再进行传递。@app.route('/data/message', methods=['POST'])
def setDataMessage():
if request.method == "POST":
data = {
'appInfo': {
'id': request.form['app_id'],
'name': request.form['app_name'],
'version': request.form['app_version'],
'author': request.form['app_author'],
'remark': request.form['app_remark']
}
}
print(type(data))
with open('static/data/input.json', 'w') as f:
json.dump(data, f)
f.close
return jsonify(result='OK')
@app.route('/data/message', methods=['POST'])
: 注册路由为 data/message
,接收 Get 方法data = { ... }
: 从 request 取得前端的提交的表单内容,储存在字典型态的变数中with open('static/data/input.json', 'w') as f:
: 写入静态文件 data 资料夹下的 input.json 档案
json.dump(data, f)
: 使用 json 物件,写入 json 文字格式到 input.json 档案。f.close
: 读取完成後,关闭文件。return jsonify(result='OK')
: 使用 jsonify() 函式,序列化 JSON 格式的字串,回传 resulut = OK 的内容前端 : jQuery 按钮,Ajax 请求 Get 与 Post
<!DOCTYPE html>
<html lang="en">
<head>
...
<script src="../static/jquery-3.6.0.min.js"></script>
...
</head>
<body>
...
</body>
</html>
<h1>DATA : API</h1>
<h2>API : GET</h2>
<button id='btnGet'>GET</button>
<hr>
<h2>API : POST</h2>
<p>APP_ID :
<input id="app_id" name="app_id" type="number" />
</p>
<p>APP_NAME :
<input id="app_name" name="app_name" type="text" />
</p>
<p>APP_VERSION :
<input id="app_version" name="app_version" type="text" />
</p>
<p>APP_AUTHOR :
<input id="app_author" name="app_author" type="text" />
<p>APP_REMARK :
<input id="app_remark" name="app_remark" type="text" />
</p>
<button id="btnPost">POST</button>
<hr>
<h3>Console : </h3>
<div id="console"></div>
<button id='btnGet'>GET</button>
: GET 按钮,用来触发 Ajax 访问取得资料的 API<button id="btnPost">POST</button>
: POST 按钮,用来将五项输入框的文字,透过 Ajax 传递给 API
<input id="app_id" name="app_id" type="number" />
<input id="app_name" name="app_name" type="text" />
<input id="app_version" name="app_version" type="text" />
<input id="app_author" name="app_author" type="text" />
<input id="app_remark" name="app_remark" type="text" />
<div id="console"></div>
: Console 文字区块,用来打印 Ajax 接收的资料内容var $btnGet = $('#btnGet');
var $console = $('#console');
var $btnPost = $('#btnPost');
var $edtAppId = $('#app_id');
var $edtAppName = $('#app_name');
var $edtAppVersion = $('#app_version');
var $edtAppAuthor = $('#app_author');
var $edtAppRemark = $('#app_remark');
$btnGet.off('click').on('click', function () {
$.ajax({
url: '/data/message',
data: {},
type: 'GET',
success: function (data) {
$console.text("");
$console.append("data[id] : " + data.appInfo.id + "---");
$console.append("data[name] : " + data.appInfo.name + "---");
$console.append("data[version] : " + data.appInfo.version + "---");
$console.append("data[author] : " + data.appInfo.author + "---");
$console.append("data[remark] : " + data.appInfo.remark + "---");
$edtAppId.val(data.appInfo.id);
$edtAppName.val(data.appInfo.name);
$edtAppVersion.val(data.appInfo.version);
$edtAppAuthor.val(data.appInfo.author);
$edtAppRemark.val(data.appInfo.remark);
},
error: function (xhr) {
alert('Ajax request 发生错误');
}
});
})
$btnGet.off('click').on('click', function () {})
: GET 按钮点击事件$.ajax({}) : 使用 jQuery 的 Ajax 函式
url: '/data/message'
: 访问路径为 /data/meesage
data: {}
: 不传递资料type : 'GET'
: 使用 GET 方法success: function (data) {}
: 成功的话,触发函式动作
$console.text(""); + $console.append("...")
: 将资料打印在 Console 元件上$edtAppXXX.val(...);
: 将数值内容填入到,画面上的五个输入框。error: function (xhr) {}
: 失败的话,触发函式动作
alert('Ajax request 发生错误');
: 显示对话视窗,Ajax request 发生错误$btnPost.off('click').on('click',function(){
$.ajax({
url: '/data/message',
data: {
"app_id" : $edtAppId.val() ,
"app_name" : $edtAppName.val(),
"app_version" : $edtAppVersion.val(),
"app_author" : $edtAppAuthor.val(),
"app_remark" : $edtAppRemark.val(),
},
type: 'POST',
success: function (data) {
$console.text("result = ");
$console.append(data.result);
},
error: function (xhr) {
alert('Ajax request 发生错误');
}
});
});
$btnPost.off('click').on('click',function(){})
: POST 按钮点击事件$.ajax({}) : 使用 jQuery 的 Ajax 函式
url: '/data/message'
: 访问路径为 /data/meesage
data: { ... }
: 传递五个输入框的文字内容
"app_id" : $edtAppId.val()
"app_name" : $edtAppName.val()
"app_version" : $edtAppVersion.val()
"app_author" : $edtAppAuthor.val()
"app_remark" : $edtAppRemark.val()
type : 'POST'
: 使用 POST 方法success: function (data) {}
: 成功的话,触发函式动作
$console.text("result = "); + $console.append("...")
: 将资料打印在 Console 元件上error: function (xhr) {}
: 失败的话,触发函式动作
alert('Ajax request 发生错误');
: 显示对话视窗,Ajax request 发生错误get 按钮,点击後资料传输流程
post 按钮,点击後资料传输流程
写入的 input.json 资料内容与输入框文字相同
补充两个开发时,应该要知道的配置
先前的程序码,如果试着使用,除 127.0.0.1 或 localhost 以外的网址,
例如: 区域网路 192.168.2.12,浏览器会显示「无法连上这个网站」
Flask 预设配置 是不允许外部的访问
增加配置
if __name__ == '__main__':
app.run('0.0.0.0')
在 main 方法的 app.run() 函式中,加上 0.0.0.0 的字串。
配置到产品的服务器中,客户端的电脑才能够连接上网站的服务器。
先前的程序码中,任何的修改都必须要重新启动。(网页的程序码也是如此)
增加配置
if __name__ == '__main__':
app.run('0.0.0.0', debug=True)
在 main 方法的 app.run() 函式中,加上 debug = True 开启 Debug 模式。
程序码的任何修改,储存後就会立刻生效,省去许多服务器重新启动的时间。
dev-config.py
import flask
app = flask.Flask(__name__)
@app.route("/")
@app.route("/hello")
def hello():
return "Hello, World ~ !!! Text Text Text ~ !!!"
if __name__ == '__main__':
app.run('0.0.0.0', debug=True)
第三章节 Ajax 资料交换,後端 Python 的实作是使用读写 json 的档案,来模拟资料持久化的部分。
不过真实的後端开发,都应该会使用关联式的资料库,例如 : MySQL、MS-SQL 来进行数据的保存。
这部分,不在 flask 的套件里面,必须安装 flask_sqlalchemy 与相对应的 python sql 套件,才能够进行实作。
後续会独立说明。
https://flask.palletsprojects.com/en/2.0.x/
https://www.tutorialspoint.com/flask/index.htm
>>: Day03. 进入No code/Low code 的世界- 安装 Blue Prism
IAP Https 今天来说说IAP在连线网页上的实作以及运用,昨天已有大致的提到了IAP对应Htt...
在这本GOF的设计模式中 共有23种设计模式 作者将这些设计模式又依照目的性粗略分为三大类,分别是:...
《刑法》第315之1条:「无故利用『工具』或『设备』窥视、窃听」或无故以『录音』、『照相』、『录影...
Reader 是我们 Android library 里面最外层的 API ,要测试它要先考虑它有跟...
开发 .NET 程序之前,需要安装两个东西: IDE: 让我们编写程序与 debug .NET SD...