2020-04-30

MongoDB 索引

MongoDB 索引

MongoDB 和傳統關聯式資料庫概念相念,都離不開 CRUD,裡面也是讀取(查詢)資料是最多變化的,也可能是最複雜的。其他三個動作,只要讀取熟悉了之後,就相對簡單。

讀取資料,經常需要排序,為了效能也有索引的設置,其中包含單欄(single field)索引和複合索引(compound index)。

首先先使用 explain() 查看讀取的策略,如下列(省略了不相干的內容)。在 queryPlanner.winningPlan 可以看到 stage 值為 COLLSCAN,代表 collections scan,也就是逐筆讀取。

> db.inventory.find() { "_id" : ObjectId("5ea4715cda0c749138d46e52"), "item" : "paper", "qty" : 100 } { "_id" : ObjectId("5ea4715cda0c749138d46e53"), "item" : "journal", "quantity" : 25 } { "_id" : ObjectId("5ea4715cda0c749138d46e54"), "item" : "planner", "qty" : 75 } { "_id" : ObjectId("5ea4715cda0c749138d46e55"), "item" : "postcard", "qty" : 45 } > db.inventory.find({item: 'postcard'}).explain() { "queryPlanner" : { "winningPlan" : { "stage" : "COLLSCAN", "filter" : { "item" : { "$eq" : "postcard" } }, "direction" : "forward" } } }

接著我們使用 createIndex() 新增一個 item 欄位的升冪索引。numIndexesAfter 表示加入索引後的索引數量。再使用 explain() 查看讀取的策略,stage 變成 FETCH。原則上使用 item 這欄為條件讀取資料時,速度會加快許多。

> db.inventory.createIndex({item: 1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } > db.inventory.getIndexes() // 查看所有索引 [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.inventory" }, { "v" : 2, "key" : { "item" : 1 }, "name" : "item_1", "ns" : "test.inventory" } ] > db.inventory.find({item: 'postcard'}).explain() { "queryPlanner" : { "winningPlan" : { "stage" : "FETCH", "inputStage" : { "stage" : "IXSCAN", "keyPattern" : { "item" : 1 }, "indexName" : "item_1", "isMultiKey" : false, "multiKeyPaths" : { "item" : [ ] }, "isUnique" : false, "isSparse" : false, "isPartial" : false, "indexVersion" : 2, "direction" : "forward", "indexBounds" : { "item" : [ "[\"postcard\", \"postcard\"]" ] } } } } }

組合索引的官方說明 先新增測試的資料,並建立複合索引,使用 {a: 1, b: 1}

> db.compoundTest.insertMany([ ... {a: 10, b: 2}, {a: 10, b: 8}, {a: 10, b: 6}, ... {a: 70, b: 2}, {a: 70, b: 8}, {a: 70, b: 6}, ... {a: 30, b: 2}, {a: 30, b: 8}, {a: 30, b: 6}, ... ]) > db.compoundTest.createIndex({a:1, b:1}) { "createdCollectionAutomatically" : false, "numIndexesBefore" : 1, "numIndexesAfter" : 2, "ok" : 1 } > db.compoundTest.getIndexes() [ { "v" : 2, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "test.compoundTest" }, { "v" : 2, "key" : { "a" : 1, "b" : 1 }, "name" : "a_1_b_1", "ns" : "test.compoundTest" } ]

以下是測試及取得 queryPlanner.winningPlan.stage 的結果:

db.compoundTest.explain().aggregate({$sort:{a: 1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: -1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: 1, b:1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: 1, b:-1}}) // COLLSCAN db.compoundTest.explain().aggregate({$sort:{a: -1, b:-1}}) // FETCH db.compoundTest.explain().aggregate({$sort:{a: -1, b:1}}) // COLLSCAN db.compoundTest.explain().aggregate({$sort:{b: 1}}) // COLLSCAN db.compoundTest.explain().aggregate({$sort:{b: -1}}) // COLLSCAN

由結果可以知道,複合索引的欄位是有優先順序的。我們 a 放在前面,b 放在後面,所以用 a 去排序的時候都是有效的。a 加上 b 時,只有 {a: 1, b:1}{a: -1, b:-1} 是有效的,因為 {a: -1, b:-1} 是設定的相反順序排序。 所有 b 為主要順序欄位的,索引都無法發揮省時的效果,只能逐筆排查詢。

2020-04-29

MongoDB 移除資料

MongoDB 移除資料

承上篇 MongoDB 更新資料,延用相同的 db 和 collections。

官方的文件說明

使用 mongo shell 操作,刪除資料使用 deleteOne() 或 deleteMany():

> db.inventory.find() { "_id" : ObjectId("5ea4715cda0c749138d46e52"), "item" : "paper", "qty" : 100 } { "_id" : ObjectId("5ea4715cda0c749138d46e53"), "item" : "journal", "quantity" : 25 } { "_id" : ObjectId("5ea4715cda0c749138d46e54"), "item" : "planner", "qty" : 75 } { "_id" : ObjectId("5ea4715cda0c749138d46e55"), "item" : "postcard", "qty" : 45 } { "_id" : ObjectId("5ea6f2ab40e9db6af06a231a"), "item" : "notebook1", "qty" : 50 } { "_id" : ObjectId("5ea6f53240e9db6af06a231b"), "item" : "notebook2", "quantity" : 30 } { "_id" : ObjectId("5ea6f53240e9db6af06a231c"), "item" : "notebook3", "quantity" : 13 } > db.inventory.deleteOne({item:/note/}) { "acknowledged" : true, "deletedCount" : 1 } > db.inventory.deleteMany({item:/note/}) { "acknowledged" : true, "deletedCount" : 2 }

MongoDB 更新資料

MongoDB 更新資料

承上篇 MongoDB 新增資料,延用相同的 db 和 collections。

官方的文件說明

使用 mongo shell 操作,更新單筆資料使用 updateOne():

> db.inventory.updateOne( ... { ... item: 'notebook2' ... }, { ... $set: {qty: 30}, ... $currentDate: { lastModified: true } ... }) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

第一個參數為條件限定,可以使用 $lt 等運算子。 第二個參數的 $set 為所要變更的設定值;$currentDate 為要設定為當下時間的屬性。

更新多筆使用 updateMany():

> db.inventory.updateMany( ... { qty: { $lte: 30 } }, ... { ... $set: { 'size.uom': "in" }, ... $rename: { qty: 'quantity' }, ... $unset: { status: '' }, ... $currentDate: { lastModified: true } ... } ... ) { "acknowledged" : true, "matchedCount" : 3, "modifiedCount" : 3 }

$rename 為變更欄位( 屬性)名稱;$unset 刪除欄位。

replaceOne() 用來取代某個 document:

> db.inventory.replaceOne( ... { item: /notebook/ }, ... { 'hello': 'my-test' } ... ) { "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }

replaceOne() 的第一個參數通常會使用 _id 或不會重複的欄位來篩選資料。

2020-04-28

設定 MongoDB Atlas

設定 MongoDB Atlas

Atlas 是 MongoDB 的資料庫雲端服務,這個服務建立在 AWS、GCP 和 Azure 三大雲端服務之上,提供了許多線上功能和工具。

對一般用戶來說,比較吸引人的是可以在註冊帳號後,免費使用一個基本款固定容量的雲端資料庫(號稱永久免費)。當然如果有更多容量和效能的需求,可以使用付費服務。

Atlas 可以使用自家的 MongoDB Compass 或 mongo shell 操作和管理,當然也可以使用 Robo 3T 等第三方工具管理。

以下是設定 Atlas 的過程簡述:

  • 到 MongoDB 官網註冊一個帳號,或按「try free」註冊。

  • 登入後,點按「Build a Cluster」按鈕,以建立 Altas Cluster。供應商不知道選誰就使用預設 AWS,服務中心(地區)也是一樣不知道就選預設值。然後給個 Cluster Name,使用變數命名規則即可,我是命名為 ClusterShin。

  • 點選左側的 SECURITY > Database Access,新增可存取資料庫的用戶。

  • 點選左側的 SECURITY > Network Access,新增可存取資料庫的 IP。測試的話,可以先選「ALLOW ACCESS FROM ANYWHERE」。若是 production 環境建議使用固定 IP。

  • 點選左側的 DATA STORAGE > Clusters,然後在 Clusters 畫面中,找到你的 Cluster,點選「conect」按鈕。

  • 在跳出的 lightbox 中,點選「Connect with the mongo shell」,點選「I have the mongo shell installed」,拷備連結的字串,如下。

mongo "mongodb+srv://clustershin-g2os9.mongodb.net/test" --username shinderlin

把拷備的字串貼到 terminal 執行,接著輸入密碼即可操作。

2020-04-27

MongoDB 新增資料

MongoDB 新增資料

同樣使用 mongo shell 操作,新增單筆使用 insertOne():

> use test switched to db test > db.inventory.insertOne({ ... "item": "notebook1", ... "qty": 50, ... "size": { ... "h": 8.5, ... "w": 11, ... "uom": "in" ... }, ... "status": "A" ... }) { "acknowledged" : true, "insertedId" : ObjectId("5ea6f2ab40e9db6af06a231a") }

新增多筆使用 insertMany():

> db.inventory.insertMany([ ... { ... "item": "notebook2", ... "qty": 10 ... }, ... { ... "item": "notebook3", ... "qty": 13 ... } ... ]) { "acknowledged" : true, "insertedIds" : [ ObjectId("5ea6f53240e9db6af06a231b"), ObjectId("5ea6f53240e9db6af06a231c") ] }

尋找剛剛新增的兩筆:

> db.inventory.find({item:/^notebook[23]/}).pretty() { "_id" : ObjectId("5ea6f53240e9db6af06a231b"), "item" : "notebook2", "qty" : 10 } { "_id" : ObjectId("5ea6f53240e9db6af06a231c"), "item" : "notebook3", "qty" : 13 }

2020-04-26

使用 MongoDB 的 find() 及 aggregate()

使用 MongoDB 的 find() 及 aggregate()

db.collection.find() 官方的說明文件在 https://docs.mongodb.com/manual/reference/method/db.collection.find/

先滙入 https://github.com/ozlerhakan/mongodb-json-files 裡 datasets 的 books.json 到 test 資料庫:

$ mongoimport --db test --collection books --file ~/downloads/books.json 2020-04-26T08:21:27.742+0800 connected to: mongodb://localhost/ 2020-04-26T08:21:29.561+0800 431 document(s) imported successfully. 0 document(s) failed to import.

先來看篩選(filter),以下是使用 mongo shell 的操作:

> use test > db.books.findOne() # 先找出第一筆查看大概的結構 { "_id" : 3, "title" : "Specification by Example", "isbn" : "1617290084", "pageCount" : 0, "publishedDate" : ISODate("2011-06-03T07:00:00Z"), "thumbnailUrl" : "https://s3.amazonaws.com/AKIAJC5RLADLUMVRPFDQ.book-thumb-images/adzic.jpg", "status" : "PUBLISH", "authors" : [ "Gojko Adzic" ], "categories" : [ "Software Engineering" ] } > db.books.find({isbn: 1617290084}) #找不到資料,型別不對 > db.books.find({isbn: '1617290084'}) #找到第一筆 # categoriries 為陣列,使用字串篩選時,結果會是陣列中有包含該字串的資料都會找出來 > db.books.find({categories: 'Software Engineering'}) # 計算個數 > db.books.find({categories: 'Software Engineering'}).count() # 只查看前兩筆 > db.books.find({categories: 'Software Engineering'}).limit(2) # 跳過 3 筆,取 5 筆 > db.books.find({categories: 'Software Engineering'}).skip(3).limit(5) # categoriries 為陣列,使用陣列篩選時,值必須相同(序順也必須一樣) > db.books.find({categories: ['Software Engineering']}) # 以下搜尋的結果會不同 > db.books.find({categories: ['Java', 'Software Engineering']}) > db.books.find({categories: ['Software Engineering', 'Java']}) # 陣列中特定位置的值 > db.books.find({'categories.1': 'Java'}) > db.books.find({'categories.1': 'Software Engineering'}) # 若是物件也是類似的作法 {'name.first': 'Bill'} # $all 同時包含(AND運算,不管順序) > db.books.find({categories: { $all: ['Software Engineering', 'Java']}}) # $in 包含任一個(OR運算) > db.books.find({categories: { $in: ['Software Engineering', 'XML']}}) # $nin 不包含任一個 > db.books.find({categories: {$nin: ['Java']}}).count() # 包含 'Software Engineering',但不包含 'Java' > db.books.find({categories: { $in: ['Software Engineering'], $nin: ['Java']}}) # 使用 Regular Expression > db.books.find({categories: /Software/}) # 陣列中任一符合 > db.books.find({title: /Practice/}) # 字串 # 沒有某個欄位 > db.books.find({publishedDate: {$exists: false} }).count() # 有某個欄位 > db.books.find({publishedDate: {$exists: true} }).count() # 某個物件的值大於、等於、小於、不等於、大於等於、小於等於、範圍 > db.inventory.find({'size.h': {$gt: 10}}) > db.inventory.find({'size.h': {$eq: 10}}) > db.inventory.find({'size.h': {$lt: 10}}) > db.inventory.find({'size.h': {$ne: 10}}) > db.inventory.find({'size.h': {$gte: 10}}) > db.inventory.find({'size.h': {$lte: 10}}) > db.inventory.find({'size.h': {$gt: 9, $lt: 15}}) # 日期 > db.books.find({publishedDate: {$gte: new Date('2014-01-01')} })

投射(project),就是要看哪些欄位,要看給 1,不看給 0。 可以使用 find() 的第二個參數,或使用 aggregate()

# 只顯示 title, _id 預設是顯示的 # 使用 find() 的第二個參數 > db.books.find({}, {title:1, _id:0}) > db.books.find({publishedDate: {$gte: new Date('2014-01-01')} }, {title:1, publishedDate:1}) # 使用 aggregate() > db.books.aggregate({$project: {title:1, _id:0}}) > db.books.aggregate([{ $match: { publishedDate: { $gte: new Date('2014-01-01') } } },{ $project: {title:1, publishedDate:1} }]) # 不顯示設定的欄位,使用 find() 的第二個參數 > db.books.find({}, {shortDescription: 0, longDescription: 0}).limit(5) # 不顯示設定的欄位,使用 aggregate() > db.books.aggregate([{$project: {shortDescription: 0, longDescription: 0}}, {$limit: 5}])

排序(sort),依欄位,升冪給 1,降冪給 -1。

> db.books.aggregate([{ $match: { categories: 'XML' } }, { $project: {title:1, publishedDate:1} }, { $sort: {publishedDate: -1} }])

複雜的操作都落在 aggregate() 上。

使用 mongo shell 建立資料庫

使用 mongo shell 建立資料庫

在 Mac 安裝好 MongoDB 後,在 terminal 輸入 mongo 即可進入 mongo shell,以下是建立資料庫和 collections 的方式:

> show databases # 顯示所有資料庫列表 admin 0.000GB config 0.000GB local 0.000GB

Mongo shell 沒有 create database 之類的指令,而是直接使用 use,不論該資料庫是否存在。使用新的 collection 也是同樣的情況。

> use test #切換到現有的資料庫或新資料庫 switched to db test > db #查看目前操作的資料庫 test > db.testCol.insertOne({a:1}) #直接在新的 collection 新增資料 { "acknowledged" : true, "insertedId" : ObjectId("5ea46d5864bc492ceab2d55e") } > show collections #顯示資料庫內的 collections testCol > db.dropDatabase() #刪除資料庫,沒事別亂用 { "dropped" : "test", "ok" : 1 } >

MongoDB 的核心是 JavaScript 引擎,mongo shell 在很多地方用起來也像是在寫 JS 一樣。

另外,官方有使用 mongoimport 滙入資料的說明在 https://docs.mongodb.com/guides/server/import/。 以下是沒使用帳密的滙入方式:

$ mongoimport --db test --collection inventory --file ~/downloads/inventory.crud.json 2020-04-26T01:20:28.490+0800 connected to: mongodb://localhost/ 2020-04-26T01:20:28.981+0800 5 document(s) imported successfully. 0 document(s) failed to import. $

滙出資料的說明在 https://docs.mongodb.com/manual/reference/program/mongoexport/

$ mongoexport --collection=inventory --db=test --out=/Users/shinder/downloads/inventory.json 2020-04-26T01:30:09.932+0800 connected to: mongodb://localhost/ 2020-04-26T01:30:10.641+0800 exported 5 records $

2020-04-25

在 Mac 上安裝 MongoDB community

在 Mac 上安裝 MongoDB community

官方文件說明 其實很清楚了,但還是筆記一下。

首先 Mac 的環境要有 homebrew。

追踪 mongodb 官方的 brew 版本資訊:

brew tap mongodb/brew

安裝 mongodb-community,目前的穩定版本為 4.2

brew install mongodb-community

安裝後的設定檔及資料檔案位置:

  • 設定檔位置: /usr/local/etc/mongod.conf
  • 日誌檔位置: /usr/local/var/log/mongodb
  • 資料檔位置: /usr/local/var/mongodb

安裝好就可以啟動服務了:

brew services start mongodb-community

停止服務:

brew services stop mongodb-community

Python 的安裝套件記錄

Python 的安裝套件記錄

在 Python 的 venv 專案環境,可以使用 pip freeze 顯示安裝的套件。也常使用下式,將安裝的套件記錄在文字檔:

pip freeze > requirements.txt

文字檔名稱通常使用 requirements.txt,但不是硬性規定。當專案是 git clone 下來時,則使用下式安裝所需套件:

pip install -r requirements.txt

2020-04-23

Flask 內建的 session 功能

Flask 內建的 session 功能

本文的參考專案 https://github.com/shinder/flask-practice

  • 使用 session 時,需要設定 cookie 的加密字串(secret key)。
  • 取得 session 值,使用 session.get('鍵')
  • 設定 session 值,使用 session['鍵']=值
from flask import session from datetime import timedelta import os app.config['SECRET_KEY'] = os.urandom(24) app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=60) @app.route('/try-session') def try_session(): if not session.get('what'): session['what'] = 1 else: session['what'] += 1 return str( session.get('what') )

Flask 路由路徑參數

Flask 路由路徑參數

本文的參考專案 https://github.com/shinder/flask-practice

  • 路徑參數,可以使用角括號標示,如下範例的 action 和 id。
  • 參數也可以指定類型,如 int。
  • 可將多個路由定義疊起來,使用相同的函式處理,並能使用預設值。
@app.route('/params/') @app.route('/params/<action>/') @app.route('/params/<action>/<int:id>') def my_params(action='none', id=0): return ( { 'action': action, 'id': id } )

2020-04-22

Flask 接收表單資料及 query string

Flask 接收表單資料及 query string

本文的參考專案 https://github.com/shinder/flask-practice

  • request.args 可取得 GET 參數(query string)
  • request.form 可取得 POST 參數(表單資料)
  • request.files 可取得表單上傳的檔案

以下為取得 GET 和 POST 參數的例子:

@app.route('/try-qs') def queryString(): # query string 轉成 dict # http://localhost:5000/try-qs?a[]=1&b=34&a[]=5 output = { 'args': request.args, 'a[]': request.args.getlist('a[]'), 'get_b': request.args.get('b'), 'get_a[]': request.args.get('a[]'), } return output @app.route('/try-post', methods=['POST']) # 限定使用 POST def try_post(): # 表單資料 urlencoded, form-data 皆可, 使用 postman 測試 output = { 'form': request.form, 'a[]': request.form.getlist('a[]'), 'post_b': request.form.get('b'), 'post_a[]': request.form.get('a[]'), } return output @app.route('/try-post2', methods=['POST']) def try_post2(): # 使用 postman post json 資料: {"a":11,"b":22} output = { 'content_type': request.content_type, 'data': request.data.decode('utf-8'), 'json': request.get_json(), } return output

測試時,/try-post 和 /try-post2 可以使用 postman 測試。 getlist() 能取得所有相同名稱的參數,而拿到 list 類型的物件(陣列)。但不會將名稱有帶中括號的參數自動轉換為陣列。

使用 postman 將 json 文件 post 給 /try-post2 路由。request.data.decode('utf-8') 可以拿到字串;request.get_json() 可以拿到由 JSON 字串轉換而成的 dict 或 list。

2020-04-20

Flask 的樣版系統 Jinja

Flask 的樣版系統 Jinja

本文的參考專案 https://github.com/shinder/flask-practice

Flask 樣版說明可以參考 這裡

在主程式加入以下片段:

from flask import render_template @app.route('/basic-template') def basic_template(): return render_template('basic.html', name='是在哈囉', age=25) @app.route('/basic-template2') def basic_template2(): output = { 'name': '小明', 'age': 30 } return render_template('basic.html', ** output)

render_template() 用來呈現頁面,第一個參數為樣版檔(樣版檔必須放在 app/templates 資料夾內),第二個參數之後為欲傳入樣版的資料。

如 basic_template2() 也可以將資料包成 dict 然後再傳入。

樣版 app/templates/basic.html 的內容如下:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{{ name }}</title> </head> <body> <h2>Hello, {{ name }}</h2> <h6>{{ age }}</h6> </body> </html>

2020-04-19

Flask 靜態檔案資料夾

在專案的資料夾的 app 內建立 static 資料夾,並在建立 app 物件時給第二個參數,指定路徑。

app = Flask(__name__, '/')

static 資料夾就會看成類似 apache 的 document root。

將處理的方法放到不同的 .py 檔

將處理的方法放到不同的 .py 檔

本文的參考專案 https://github.com/shinder/flask-practice

將處理的方法寫到 modules/functions.py 裡

from flask import request # 滙入 request def show_cookies(): return request.cookies

在主要檔案 main.py 滙入:

import modules.functions # 語法:add_url_rule(rule, endpoint=None, view_func=None, provide_automatic_options=None, **options) app.add_url_rule('/show-cookies', 'show-cookies', modules.functions.show_cookies) # app.add_url_rule('/show-cookies', 'show-cookies') # app.view_functions['show-cookies'] = modules.functions.show_cookies
  • 使用 add_url_rule() 可以將處理函式設定給路由。
  • endpoint 用來做處理函式及路由的對應。給 None 時,則使用函式名稱。
  • 這樣的方式可以將處理函式放在不同的檔案,以方便維護。

將 request headers 寫入檔案

將 request headers 寫入檔案

本文的參考專案 https://github.com/shinder/flask-practice

from flask import request # 滙入 request import json @app.route('/save-headers') def save_headers(): dict1 = { 'cookies': {} } for i in request.headers: print(i) # 查看取出的 headers 資料 dict1[i[0]] = i[1] # 查看 cookies for i in request.cookies: dict1['cookies'][i] = request.cookies[i] file1 = open('headers.json', 'w') file1.write(json.dumps(dict1)) # 存成 JSON return dict1
  • dict1 為用來暫存資料的 dict,並設定好結構
  • request.headers 用 for/in 取出,為 tuple
  • request.cookies 為 dict
  • json 為預設套件,dumps 為轉換為 JSON 字串
  • return dict 會自動轉換為 JSON 格式
  • 拜訪 localhost:5000/save-headers 可以看到結果

建立 Flask 專案

建立 Flask 專案

本文的參考專案 https://github.com/shinder/flask-practice

首先如之前的文章 建立專案

md flask-practice #建立專案資料夾 cd flask-practice #到專案目錄 python3 -m venv venv #安裝虛擬環境 source venv/bin/activate #啟動虛擬環境(mac)

安裝 Flask:

pip install flask

可以使用下列兩個命列中的一個查看安裝的套件:

pip list pip freeze

建立 app 資料夾,用來存放自己撰寫的程式,並在裡面建立 main.py

from flask import Flask # __name__ 用來 application 的相對位置 # 若是直接啟動的程式 __name__ 為 '__main__' # 若是被滙入, __name__ 會是被滙入的名稱 app = Flask(__name__) # decorators 後面定義的 function 會變成 decorators 的參數 # 類似 JavaScript 的 callback function @app.route('/') def index(): return '<h2>哈囉 Flask</h2>'

在專案目錄建立執行的 run.sh,在 mac 記得將檔案屬性設定為 +x 可執行:

# mac export FLASK_APP=app/main.py export FLASK_ENV=development flask run --host=localhost --port=5000 # windows # set FLASK_APP=app/hello.py # set FLASK_ENV=development # flask run

在 terminal 執行 run.sh,並在瀏覽器拜訪 localhost:5000 即可看到我們的第一個頁面。

2020-04-15

VSCode 簡便的 MySQL 管理外掛

VSCode 外掛 MySQL (MySQL management tool)
作者: Jun Han
外掛 ID: formulahendry.vscode-mysql

算是簡單易用的 MySQL 管理工具

2020-04-09

在 VSCode 上編寫 Markdown 文件

若要在 VSCode 上編寫 MD 文件,官方的建議,我自己安裝了:
  • markdownlint
  • Markdown Theme Kit
  • Markdown Shortcuts
另外還安裝了:
  • Markdown All in One
  • Markdown PDF
這樣基本工具都有了,Markdown PDF 可以很方便輸出成 pdf 及 html。

以下是語法的小筆記:


# 大標題

## 次標題

### 細標題

另一種大標 (不建議使用)
===

另一種次標  (不建議使用)
---

條列方式一: - (建議不要用 * 和 +)

- 123
- 456
- 789

條列方式二: *  (不建議使用)

* 123
* 456
* 789

條列方式三: +  (不建議使用)

+ 123
    + 111
        + 333
    + 222
+ 456
+ 789

條列方式四: 使用數字加點  (數字可以不管順序,但不建議)

1. 123
    1. 333
    2. 777
2. 456
3. 789

### 程式碼呢? 前面加 \t 或 4個空白

    const func = a => a*a;
    
    const func2 = ()=>{
        let r = 0;
        for(let i=1; i<=10; i++){
            r+=i;
        }
        return r;
    };

    const func = a => a*a;

``` javascript
const func2 = ()=>{
    let r = 0;
    for(let i=1; i<=10; i++){
        r+=i;
    }
    return r;
};
```

### 行內程式碼

這是個行內的 `console.log(me);` 程式碼

這是個行內的 ``console.log(bill`s);`` 程式碼

---
上下是分隔線  (不建議使用 *** )

***

*斜體*
**粗體**
_斜體_
__粗體__

區塊引言
>123
>456
>78945

### 連結

[This link](http://example.net/) has no title attribute.

### 表格

 | column0 | column1 | aaa
 | ------- | ------- | -----
 | 123 | 456 | 789
 | abc | def | ghi
 | aaa | bbb | ccc



FB 留言