# 程式碼實作
**2 hr**
[ToC]
## 1. Django 設定
1. 安裝套件
```
$ pip install Django
```
```
$ pip install line-bot-sdk
```
```
$ pip install pymysql
```
2. 建立專案
```
$ django-admin startproject '專案名稱'
```
```
cd '專案名稱'
```
3. 建立 APP:可以進行多個 Line Bot 開發
```
$ python manage.py startapp 'APP名稱'
```
4. 新建兩個資料夾
```
$ md static
$ md templates
```
static 用於靜態資料如圖片、檔案
templates 用於放寫好的 html
## 2. 在 `settings.py` 進行
1. 在最上面
```python
import pymysql
pymysql.install_as_MySQLdb()
```
2. 設定 Channel Access Token & Channel Secret
第 22 行左右的地方
```python=
CHANNEL_ACCESS_TOKEN = 'Channel_access_token'
CHANNEL_SECRET = 'Channel_secret'
```
3. 於 INSTALLED_APPS 中設定建立的 APP 名稱
```python=
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'【app】' #新增 app 名稱
]
```
4. 於 TEMPLATES 中設定 templates 的資料夾路徑
```python
'DIRS'[os.path.join(BASE_DIR,'templates')]
```
5. staitc
```python
STATIC_URL = '/static'
STATICILES_DIRS = [os.path.join(BASE_DIR,'static')]
```
6. 語言及時區設定
## 利用 Django 連接資料庫 (共 7 個步驟)
1. `settings.py`
```python=
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': '【your DB name】',
'USER': '【your user name】',
'PASSWORD': '【your password】',
'HOST': '',
'PORT': '3306',
}
}
```
2. debug
> 如果MySQL 服務器配置為使用 caching_sha2_password 或 sha256_password 作為身份驗證插件時,PyMySQL(這是 Django 用來與 MySQL 通信的一個 Python 庫)需要 cryptography 套件來加密密碼。
```
$ pip install cryptography
```
3. 同步資料庫
```
$ python manage.py inspectdb > 【your_app_name】/models.py
```
4. 檢查 `model.py` 有沒有你的 database
5. `admin.py` (後台資料)
5-1 from `models.py` import the database table
```python=
from django.contrib import admin
# Register your models here.
from 【your_app_name】.models import *
#【below is optional,想要做 5-2 再做就可以】
class user_message_admin(admin.ModelAdmin):
list_display = ('uid', 'name', 'message', 'time')
admin.site.register(user_message, user_message_admin)
```
5-2 optional
若我們想從 Linebot 中記錄用戶 ID 以及訊息內容
```python=
#from django.db import models
# Create your models here.
class user_message(models.Model):
uid = models.CharField(max_length=50, null=False, primary_key=True) # user id
name = models.CharField(max_length=50, blank=True, null=False) # LINE 名字
message = models.CharField(max_length=600, blank=True, null=False) # 文字訊息紀錄
time = models.DateTimeField(auto_now=True) # 日期時間
def __str__(self):
return self.name # print the user name in terminal
```
7. 更新資料庫
之後在 mysql workbench 有異動都可以直接輸這個更新
```
$ python manage.py makemigrations
```
```
$ python manage.py migrate
```
7-1. debug
有可能出現
>SyntaxError: source code string cannot contain null bytes
- 意思是有 null bytes 出現,而非一般的空格(ex. a + b,就屬於一般空格)
- 解決方式:
(1) 下載 hex editor (發行者 :microsoft)
(2) 右鍵點選檔案>開啟方式> 16 進位編輯器 > crtl+F 查找`00`
$\quad$在 16 進位中,一般空格應顯示`20`
$\quad$範例圖示如下
STEP1 
STEP2 
STEP 3 注意要選擇「以二進位模式搜尋」(會自動變成 16 進位不用擔心)

(3) TIP
$\quad$因為 `model.py` 的 table 都是從 mysql 導入,比較有可能出錯,所以可以先從這邊下手
(4)
$\quad$ 4-1 找到有錯的檔案之後,用文字編輯器打開,複製所有程式。
$\quad$$\quad$$\quad$重新建立一個`.py`檔案,貼上 `models.py` 的內容。
$\quad$ 4-2 貼上後一樣用 16 進位的方式確認沒有出現 null bytes (`00`)
$\quad$ 4-3 用新檔案取代舊的 `models.py` (一樣的路徑、一樣的名字)
(5) 重新輸入 `$ python manage.py makemigrations`,確認沒有問題
- 這樣就 debug 結束了~
## 資料表操作
1. `views.py`
2. 將` models.py` 資料表匯入 `views.py`
```python
from 【your_app_name】.models import *
```
3. 修改 `views.py`
```python=
from django.shortcuts import render
# Create your views here.
from app.models import *
import re
import logging
from django.conf import settings
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.views.decorators.csrf import csrf_exempt
from linebot import LineBotApi, WebhookParser
from linebot.exceptions import InvalidSignatureError, LineBotApiError
from linebot.models import MessageEvent, TextSendMessage
from .models import SnakeSpecies, Product, CareRequirements
line_bot_api = LineBotApi(settings.CHANNEL_ACCESS_TOKEN)
parser = WebhookParser(settings.CHANNEL_SECRET)
# 設置日誌
logger = logging.getLogger(__name__)
@csrf_exempt
def callback(request):
if request.method != 'POST':
return HttpResponseBadRequest()
signature = request.META.get('HTTP_X_LINE_SIGNATURE', '')
body = request.body.decode('utf-8')
# 日誌輸出
logger.info(f"Request body: {body}")
logger.info(f"Signature: {signature}")
try:
events = parser.parse(body, signature)
except InvalidSignatureError:
logger.error("Invalid signature error")
return HttpResponseForbidden()
except LineBotApiError as e:
logger.error(f"LineBotApiError: {str(e)}")
return HttpResponseBadRequest()
for event in events:
if isinstance(event, MessageEvent):
handle_message(event)
return HttpResponse('OK')
def handle_message(event):
message_text = event.message.text.strip()
if "蛇的學名" in message_text:
scientific_name = extract_scientific_name(message_text)
species_exists = SnakeSpecies.objects.filter(scientific_name=scientific_name).exists()
if species_exists:
reply_text = f"蛇類 {scientific_name} 已經存在於資料庫中。"
else:
reply_text = f"您所輸入的蛇類不存在,請您建立新的檔案。若要建立,請回覆 OK"
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
elif "new" in message_text:
# 调用 handle_snake_species 并传递 message_text,这个函数负责发送回复
handle_snake_species(event, message_text)
elif "【學名】" in message_text:
handle_read(event, message_text)
elif "刪除產品編號" in message_text:
handle_delete(event, message_text)
elif "更新" in message_text:
handle_update(event, message_text)
def extract_scientific_name(text):
# 假设用户会按照【學名】snake的格式输入
pattern = r'蛇的學名[::](.+)'
match = re.search(pattern, text)
return match.group(1) if match else None
def extract_scientific_name2(text):
# 假设用户会按照【學名】snake的格式输入
pattern = r'【學名】(\w+\s?\w*)'
match = re.search(pattern, text)
return match.group(1) if match else None
def handle_snake_species(event, message_text):
data = parse_data(message_text)
if data:
species, created = SnakeSpecies.objects.get_or_create(
scientific_name=data['scientific_name'],
chinese_name=data['chinese_name'],
defaults={'amount': data['amount']}
)
if created:
#reply_text = f"儲存成功,蛇類編號為 {species.id}" //這個有錯,好像讀不出來整個掰掰
reply_text = "儲存成功"
else:
reply_text = "儲存失敗"
else:
reply_text = "儲存失敗,請確認訊息格式是否有誤"
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
def parse_data(text):
pattern = r'.*\n【學名】(\w+)\n【中文名稱】(\S+)\n【數量】(\d+)'
match = re.search(pattern, text, re.MULTILINE | re.DOTALL)
if match:
return {
'scientific_name': match.group(1),
'chinese_name': match.group(2),
'amount': int(match.group(3))
}
return None
def handle_read(event, message_text):
# 提取学名
scientific_name = extract_scientific_name2(message_text)
if not scientific_name:
line_bot_api.reply_message(event.reply_token, TextSendMessage(text="格式错误,请重新输入。"))
return
# 查询数据库,获取所有匹配的 SnakeSpecies 对象
matching_species = SnakeSpecies.objects.filter(scientific_name=scientific_name)
if matching_species:
# 构建产品名称列表
product_names = [f"{species.chinese_name} (數量: {species.amount})" for species in matching_species]
product_names_str = "\n".join(product_names)
reply_text = f"以下是與學名 '{scientific_name}' 匹配的產品:\n\n{product_names_str}"
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
return
# 如果没有匹配的对象
line_bot_api.reply_message(event.reply_token, TextSendMessage(text="没有找到对应的蛇类。"))
return
def handle_update(event, message_text):
pattern = r'【產品編號:(\d+)】(\d+)'
match = re.search(pattern, message_text)
if match:
product_id, new_price = match.groups()
try:
# 尝试获取产品
product = Product.objects.get(product_id=int(product_id))
# 更新价格
product.price = float(new_price)
product.save()
reply_text = f"產品編號 {product_id} 的價格已更新為 {new_price}"
except Product.DoesNotExist:
reply_text = f"没有找到產品編號 {product_id} 的產品。"
except Exception as e:
reply_text = f"錯誤: {str(e)}"
else:
reply_text = "格式錯誤"
# 发送回复消息
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
def handle_delete(event, message_text):
# 假设用户输入格式为:“【刪除產品編號】12345”
pattern = r'【刪除產品編號】(\d+)'
match = re.search(pattern, message_text)
if not match:
line_bot_api.reply_message(event.reply_token, TextSendMessage(text="格式錯誤"))
return
product_id = int(match.group(1))
try:
product = Product.objects.get(product_id=product_id)
product.delete()
reply_text = f"產品編號 {product_id} 已成功删除。"
except Product.DoesNotExist:
reply_text = f"沒有找到 {product_id} "
except Exception as e:
reply_text = f"刪除過程中出現錯誤 {str(e)}"
line_bot_api.reply_message(event.reply_token, TextSendMessage(text=reply_text))
```