2017年7月4日 星期二

Line Bot 實戰教學

環境
Windows + python

說明

可透過Line 官方推出的API 可以自行開發Line Bot,可以主動進行推撥,或是根據訊息進行相對應的回應

實作

1.申請帳號

參考下列網址申請 & 設定

2.設定ngrok


  • ngrok會給一組隨機的httpxxxxx & https xxxxxx 對應到本機localhost:xxxx 啟的server 
讓外部的網站能夠直接連接到localhost,節省掉開發的時間
主要也是因為在line bot 的設定上,需要給一組webhook url 所以可以透過該方式拿到一組https
入(下圖勾選的地方)
  • 透過linux將下載下來的ngrok最新版本移到/usr/bin,之後在bash輸入ngrok就可使用
$ sudo mv <檔案目前的位置> /usr/bin/

  • 此時使用
$ ngrok
  • 成功進行運作
  • 可透過下列方式指定要運行的port
$ ngrok http 5000

  • 下圖為運行成功後,即可看到ngrok給予的http網址,將網址貼在webhook url即可

3.執行Line API

from flask import Flask, request, abort

from linebot import (
    LineBotApi, WebhookHandler
)
from linebot.exceptions import (
    InvalidSignatureError
)
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage,
)

app = Flask(__name__)

line_bot_api = LineBotApi('YOUR_CHANNEL_ACCESS_TOKEN')
handler = WebhookHandler('YOUR_CHANNEL_SECRET')


@app.route("/callback", methods=['POST'])
def callback():
    # get X-Line-Signature header value
    signature = request.headers['X-Line-Signature']

    # get request body as text
    body = request.get_data(as_text=True)
    app.logger.info("Request body: " + body)

    # handle webhook body
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'


@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    line_bot_api.reply_message(
        event.reply_token,
        TextSendMessage(text=event.message.text))


if __name__ == "__main__":
    app.run()

4.使用Flake 作為Line bot 的server

  • 該範例會使用flask啟用一個server,當作line bot 的後端server 預設開啟為127.0.0.1:5000
可透過下方設定調整
if __name__ == "__main__":
    app.run(host='0.0.0.0',port=9000, debug=True)

5.碰到的問題,不斷的502 Bad Gateway

  • 當執行官方code後,出現systemExit:1
  • 而連結到網址,會發現ngrok出現該提示
  • 本以為是在jupyter開啟server會出問題,後來轉換成py,透過bash開啟,還是一樣,雖然似乎flask已與ngrok產生連結,但問題依舊存在,導致後面的line bot後續測試無法進行
  • 可能解決狀況
    • 把server架在vm中的docker上面,網路的設定可能有未知的問題
    • 或許可以嘗試在本機的jupyter上操作line bot 看看

6.自創的Line Bot帳號


7.嘗試在本機Jupyter使用falsk

from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
    return "Hello World!"

if __name__ == "__main__":
    app.run()

結果成功了→斷定果然是因為跑在VM上面的關係

8.在本機操作Lint Bot

按照上方的步驟,結果在驗證時出現下面錯誤,至少錯誤跟之前不一樣QQ
[2017-07-04 14:48:33,233] ERROR in app: Exception on /callback [POST]
Traceback (most recent call last):
  File "C:\Anaconda2\lib\site-packages\flask\app.py", line 1988, in wsgi_app
    response = self.full_dispatch_request()
  File "C:\Anaconda2\lib\site-packages\flask\app.py", line 1641, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "C:\Anaconda2\lib\site-packages\flask\app.py", line 1544, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "C:\Anaconda2\lib\site-packages\flask\app.py", line 1639, in full_dispatch_request
    rv = self.dispatch_request()
  File "C:\Anaconda2\lib\site-packages\flask\app.py", line 1625, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "<ipython-input-1-c3bc0381a4f5>", line 33, in callback
    handler.handle(body, signature)
  File "C:\Anaconda2\lib\site-packages\linebot\webhook.py", line 227, in handle
    func(event)
  File "<ipython-input-1-c3bc0381a4f5>", line 44, in handle_message
    TextSendMessage(text=event.message.text))
  File "C:\Anaconda2\lib\site-packages\linebot\api.py", line 94, in reply_message
    '/v2/bot/message/reply', data=json.dumps(data), timeout=timeout
  File "C:\Anaconda2\lib\site-packages\linebot\api.py", line 262, in _post
    self.__check_error(response)
  File "C:\Anaconda2\lib\site-packages\linebot\api.py", line 271, in __check_error
    raise LineBotApiError(response.status_code, error)
LineBotApiError: <LineBotApiError [Invalid reply token]>

9.成功運作(雖然出現上面那些錯誤)

但Line Bot卻可以正常運作了

10.設計回應的狀況

@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    msg = event.message.text
    print(msg)
    msg = msg.encode('utf-8')
    if msg=="DOG":
        line_bot_api.reply_message(event.reply_token,TextSendMessage(text="汪汪叫"))
    if msg=="ian":
        line_bot_api.reply_message(event.reply_token,TextSendMessage(text="甚麼"))
    if msg=="lillian_hong":
        line_bot_api.reply_message(event.reply_token,TextSendMessage(text="So cute"))
    else:
        line_bot_api.reply_message(event.reply_token,TextSendMessage(text=event.message.text))

小技巧

移除packeage

$ sudo apt-get --purge remove ngrok-client

利用docker 直接創建line bot


Python &  Line Bot 實作參考

2017年3月16日 星期四

Why 『Map<> = new HashMap()』 not 『HashMap<> = new HashMap()』

Reason
1.less typing
2.more flexible.

For example:
You can see the image below, use Map<> = new HashMap() can easily change HashMap to TreeMap

2017年3月15日 星期三

調派請求 servlet forward include sendRedirect

Reference
https://openhome.cc/Gossip/ServletJSP/DispatchRequest.html
https://www.javaworld.com.tw/jute/post/view?bid=6&id=50491&sty=1&tpg=1&age=1
https://read01.com/R3dy.html
http://fecbob.pixnet.net/blog/post/43270957-%5Bjsp%5D-servlet%E7%9A%84%E5%B9%BE%E7%A8%AE%E9%A0%81%E9%9D%A2%E8%B7%B3%E8%BD%89%E6%96%B9%E5%BC%8F

Sevlet 請求轉發主要的三種方式

redirect:
1.重定向,包含兩次瀏覽器請求,瀏覽器根據url請求一個新的頁面,所有的業務處理都轉到下一個頁面,url發生改變。
2.可以將頁面跳轉到任何頁面,不一定局限於本『web內部應用中』,如:
response.sendRedirect("HTTP://www.ycul.com");
3.跳轉後瀏覽器網址列變化。
這種方式要傳值出去的話,只能在url中帶parameter或者放在session中,無法使用request.setAttribute來傳遞。
response.sendRedirect("other.view");


forward:
1.轉發,將當前request和response對象保存,交給指定的url處理。並沒有表示頁面的跳轉,所以url不會發生改變。
2.頁面的路徑是相對路徑。forward方式『只能跳轉到本web應用中的頁面上。
3.跳轉後瀏覽器網址列不會變化。
使用這種方式跳轉,傳值可以使用三種方法:url中帶
白話:就這樣了,我不會再對請求和回應做任何處理了
parameter,session,request.setAttribute
req.getRequestDispatcher("hello.jsp").forward(req, resp);


include
(通常不會在servlet內使用,而是應用在jsp<jsp:include>):

意為包含,將當前request和response對象保存,交給指定的url處理,沒有頁面的跳轉,所以url不會發生改變,但是會將include的Servlet回應包括至目前的回應之中。
例如:
1.servletA 若include→servletB
2.servletB forward→jsp
則jsp會含有servletA『轉向前&轉向後執行的所有資訊』+ servletB『轉向前的資訊』
白話:servletA請servletB幫忙,等他完成請求和回應後,我會再對最後的請求和回應進行處理
RequestDispatcher dispatcher = req.getRequestDispatcher("other.view");
dispatcher.include(req, resp);

小提示
1.如果路徑以斜線("/")開頭,Container會將他視為『從這個Web應用程式的根目錄算起』,如果該路徑不是斜線開頭,就會被視為『相對於原始的請求』,但不能誘騙Container至Web以外的路徑,這是行不通的
總結
1.redirect與include、forward的區別在於是不是同一個Request,redirect會有兩次交互。

2.include與forward的區別在於輸出的內容,include包含本身servlet與跳轉頁面內容的結果,而forward不包含本身servlet的內容。
---------------------------------------------------------------------------------
include 相關補充

Refrence
https://www.javaworld.com.tw/jute/post/view?bid=6&id=50491&sty=1&tpg=1&age=1

在JSP技術中,您可以選擇在
1.編譯時期include一個網頁 <%@include> 
2.執行時期include一個網頁 <jsp:include> 

include一個網頁表示暫時將response的權限交給被include的網頁,在include的網頁執行完畢或送出內容之後,response的權限會再度回到要求include的原網頁。

1.<%@include> 選擇在編譯時期(程式碼compile的時候)include網頁好處是效能,JSP引擎不用再動態呼叫被include的網頁,被include的網頁被當作要求include的網頁的一部份,您可以使用指令元素include來於編譯時期include網頁
通常用來include『靜態的網頁』
(當進到該網頁的第一次時,沒有帶入任何動態參數時適用)
(但該include網頁中依然可以寫JSTL or EL 的語法,依據某些情況,接收參數)


例如:

2.<jsp:include>來於動態時期include網頁,並可搭配<jsp:param>動作元素來指定參數給被include的網頁,被include的網頁執行完後,response的權限會交回到要求include的網頁中,我們使用以下這個例子來說明:
user=Jstin 此參數,是動態時期給予的





2017年3月9日 星期四

基本的 jQuery 插件 - How to Create a Basic Plugin

Reference:
https://learn.jquery.com/plugins/basic-plugin-creation/
http://maidot.blogspot.tw/2011/04/jquery-this-this-different-between-this.html


1.創建一個基礎的jQuery plugin

-把我們創造的plugin加入至$.fn之中
(擴展$.fn.abc(),即$.fn.abc()是對jquery擴展了一個abc方法,那麼後面你的每一個jquery實例都可以引用這個方法了)
$.fn.greenify = function() {
    this.css( "color", "green" );
};
 
$( "a" ).greenify(); // Makes all the links green.

-注意這邊是使用css(),所以我們也用this,而非$(this),
因為在JQuery中this代表目前的DOM對象,$(this)代表我們用JQuery所選取的JQuery對象,
在上述方法中,this代表greenify(),而他與.css()都同屬同一個jQuery object的一部份


2.Chaining 讓自建jQuery plugin可與其他方法相連
新增一行『return this』把該jQuery Object 傳回
$.fn.greenify = function() {
    this.css( "color", "green" );
    return this;
}
 
$( "a" ).greenify().addClass( "greenified" );

3.確保plugin不會與其他JavaScript libraries產生衝突
(Protecting the $ Alias and Adding Scope)
在最外層多加一層$()宣告
(function ( $ ) {
 
    $.fn.greenify = function() {
        this.css( "color", "green" );
        return this;
    };
 
}( jQuery ));

4.讓一個plugin根據參數,做出不同的動作
(function( $ ) {
 
    $.fn.popup = function( action ) {
 
        if ( action === "open") {
            // Open popup code.
        }
 
        if ( action === "close" ) {
            // Close popup code.
        }
 
    };
 
}( jQuery ));

5.透過each(),對多個指定的DOM elements進行處理
$.fn.myNewPlugin = function() {
 
    return this.each(function() {
        // Do something to each element here.
    });
 
};
Notice that we return the results of .each() instead of returning this. Since .each() is already chainable, it returns this, which we then return. This is a better way to maintain chainability than what we've been doing so far.

.each() 方法,會遍歷jQuery對象中的每一個DOM元素
然後透過$(this) 將每一個取到的DOM變成jQuery Object
 return this.each(function() {
    var link = $(this);
    link.css("color", setting.color)
});
6.讓一個plugin接受『一組設定』
(function ( $ ) {
 
    $.fn.greenify = function( options ) {
 
        // This is the easiest way to have default options.
        var settings = $.extend({
            // These are the defaults.
            color: "#556b2f",
            backgroundColor: "white"
        }, options );
 
        // Greenify the collection based on the settings variable.
        return this.css({
            color: settings.color,
            backgroundColor: settings.backgroundColor
        });
 
    };
 
}( jQuery ));
Example usage:
$( "div" ).greenify({
    color: "orange"
});

defaults 設定的顏色透過$.extend()把覆蓋成傳進來的值
The default value for color of #556b2f gets overridden by $.extend() to be orange.

7.完整範例
my_plugin.js
(function($) {

    $.fn.greenify = function(action, options) {
        var setting = $.extend({
            color: "green",
            backgroundColor: "white"
        }, options);

        if (action === 'div') {

            return this.css({
                color: setting.color,
                backgroundColor: setting.backgroundColor
            });
        };

        if (action === 'a') {
            return this.each(function() {
                var link = $(this);
                link.css("color", setting.color)
            });

        }

    };

}(jQuery));
template.html
<!doctype html>
<html>

<head>
    <meta charset="utf-8">
    <script src="js/jquery-1.12.0.min.js"></script>
    <script src="js/my_plugin.js"></script>
    <script>
    $(document).ready(function() {
        $("a").greenify("a", {
            color: "red"
        });

        $("div").greenify("div", {
            color: "white",
            backgroundColor: "red"
        });
    });
    </script>
    <title>jQuery</title>

    <body>
        <a href="www.google.com.tw">GoogleGoog</a>
        <a href="www.yahoo.com.tw">YahooYahoo</a>
        <div>
            Hello
        </div>
    </body>

</html>

2017年2月5日 星期日

用 cmd (命令列) 執行java程式

參考
https://caterpillar.gitbooks.io/javase6tutorial/content/c2_2.html

1.環境設定
設定 Path 變數是為了讓作業系統找到指定的工具程式(以 Windows 來說的話就是找到 .exe 檔案),則設定 Classpath 的目的就是為了讓Java執行環境找到指定的 Java 程式(也就是.class檔案)。

-設定path(jdk路徑)
-設定classpath(之後要去哪裡找class檔)

方法一
最簡單的方法是在系統變數中新增 Classpath 環境變數,在上圖中的「系統變數」按下「新增」鈕,在「變數名稱」欄位中輸入「Classpath」,在「變數值」欄位中輸入 Java 類別檔案的位置,例如可以輸入「.;C:\Program Files\Java\jdk1.6.0\ lib\tools.jar; C:\Program Files\Java\jre1.6.0\lib\rt.jar」(jar 檔是 zip 壓縮格式,當中就包括了 .class 檔案以及 jar 中的 Classpath 設定),每一筆資料中間必須以「;」作為分隔。

方法二
使用的 JDK 工具程式有 Classpath 指令選項,在執行工具程式時一併指定 Classpath:

javac -classpath classpath1;classpath2 …

方法三
文字模式下執行以下的指令,以直接設定目前的環境變數包括 Classpath 變數(這個設定在下次重新開啟文字模式時就不再有效):

set CLASSPATH=.;classpath1;classpath2

重點觀念:
少了 < .; > 會造成執行正確和錯誤的強烈差距
.』這個小細節是有一些原因的.
在沒有做任何設定之下 , 預設的CLASSPATH就是一個『.
這個『.』代表 命令提示字元的當前目錄
沒設定比設定錯誤還好一點點(對新手而言) , 只要 .class 在當前目錄 , 你還是可以執行程式

但是你若下了設定 
SET CLASSPATH=C:\j2sdk1.4.1_03\lib;C:\j2sdk1.4.1_03\lib\tools.jar;
少了這個. 若你當前的目錄卻不是 C:\j2sdk1.4.1_03\lib , 就會發生NoClassDefFoundError

正確的設定
SET CLASSPATH=.;C:\j2sdk1.4.1_03\lib;C:\j2sdk1.4.1_03\lib\tools.jar;

1.表示當前的目錄 , 
2.C:\j2sdk1.4.1_03\lib\ , 
3.以及 tools.jar 內的class都可以載入 


2.範例
a.撰寫java程式(帶有package test; → 之後執行時,路徑要特別注意)

b.進行編譯javac (順便指定classpath)
javac -classpath C:\Users\ytchen\Desktop\test test6.java

c.執行class檔
路徑:C:\Users\ytchen\Desktop\test>
命令:java test6
若是在程式碼中,有設定package的情況下,路徑要修改,否則會找不到檔案
(程式會去找test\test\test6)

所以要往上一層
並且利用『.』的方式,設定目錄,告知檔案在哪
路徑:C:\Users\ytchen\Desktop>
命令:java test.test6




2017年1月19日 星期四

RDB - 文字型態的資料要用什麼樣的格式儲存? char, varchar, nchar, nvarchar??

參考網址:
https://dotblogs.com.tw/topcat/archive/2008/03/04/1144.aspx


定義:
charnchar『宣告的是固定的長度』,因此如果宣告char(5),但是只放a這個字,那麼就會補另外4個空白,會補空白補滿是他的特性,而nchar(5)也是會放滿5個字,但是每個字無論中英數都會用2個Bytes來存放


varcharnvarchar都是『不定長度』,因此如果宣告varchar(5),但是只放a這個字,那麼就會放一個a,不會補空白。而如果宣告nvarhcar(5)那還是只放a這個字,但是會用2Bytes來存放。如果放了【中】這個中文字,在varchar會占用2個位子,但是nvarchar指占用1個位子。


結論:
在規畫上,只要存放的資料有可能有中文,就會加n。而除非確定資料固定會是幾位不變,不然資料長度不特定就會宣告加上var

2016年12月24日 星期六

永久修改 Ipython Nortebook 或 Jupter Notebook 的默認目錄

參考:
http://xuanchao1980.com/2016/05/12/jupyterDirectory/

步驟:
Ipython Notebook

1.打開命令行(初始化配置文件):
ipython profile create

這個命令,會初始化一份配置文件ipython_notebook_config.py,輸入後找到下面這行

# c.NotebookManager.notebook_dir = u'D:\\love\\cat'

如果没有,請找這行:

# c.NotebookApp.notebook_dir = u'Z:\\luv\\cat'

2.去掉註解&空格,修改路徑路徑為自己想要的

Jupyter Notebook

1.打開命令行(初始化配置文件):
jupyter notebook --generate-config
這個命令,會初始化一份配置文件.jupyter\jupyter_notebook_config.py
輸入後找到下面這行

# c.NotebookApp.notebook_dir = u'D:\\love\\you'


2.去掉註解&空格,修改路徑路徑為自己想要的