校園中的資訊系統
網頁的 / 手機的 / 會用的
呂紹榕 Louie Lu
我是
- 呂紹榕
- 國立高雄應用科技大學 資訊工程系 二年級
- grapherd@gmail.com
- http://grd.idv.tw
我是
- 呂紹榕
- 國立高雄應用科技大學 資訊工程系 二年級
- grapherd@gmail.com
- http://grd.idv.tw
Agenda
一些故事
使用的資源與技術
Q&A
第一個故事
校園中的資訊系統
同學們常用的系統
-
數位學習平台
-
e-mail信箱
-
校務行政資訊系統
-
選課系統
-
線上請假系統
-
校車系統
- ...etc
數位學習平台
http://ilearning.kuas.edu.tw
e-mail 信箱
http://mail.kuas.edu.tw
校務行政資訊系統
http://ap.kuas.edu.tw
選課系統
http://selcourse.kuas.edu.tw
線上請假審核系統
http://leave.kuas.edu.tw
校車系統
http://bus.kuas.edu.tw
從大家的觀點來看
覺得這些系統如何
從學生的觀點來看
覺得這些系統如何
從使用者的觀點來看
覺得這些系統如何
系統容易操作嗎?
系統支援多瀏覽器嗎?
IE ONLY
IE ONLY
系統UI好看嗎?
CrossLink.tw
CrossLink.tw
校務行政資訊系統
時代向前了10年
校園中的資訊系統
卻停滯不前
WHY?
過了多久
我們才使用到新的校務系統?
整整一年
一年!!!
事後跟學校合作的時候
事後跟學校合作的時候
- 人力不足
- 排程很多
- 很多新系統
事後跟學校合作的時候
- 很多苦衷
- 總之就是沒辦法弄
還有
They use ASP.NET
更重要的一點
產品開發已經變成更快速、更有彈性的流程,
明顯更好的產品不再是站在巨人的肩膀上打造出來
而是站在大量迭代(iterations)的肩膀上。
- Google模式 p30
遺憾的是,跟羅森柏格那個失敗的關卡式(Gate-based)產品開發架構一樣,現今許多公司採行的管理流程設計原理跟不上時代的趨勢變化 ...... ,這種方式旨在減緩速度,而它也的確非常有效地減緩速度,但這意味的是,當企業必須永久加速時,它們的架構卻會絆住它們,讓它們快不起來。
- Google模式 p31
以海報為例
迭代
以海報為例
Very fast prototype
iterate, iterate, iterate
Each
Iterate
Get Responsive
From User
關卡
以海報為例
- 提案
- 層層上報
- 審核
- 層層審核
- 討論
- 層層討論
- 開發
- 開發開發開發
- 檢查
- 層層檢查
- pre release
- release
學校
很難聽到使用者的的需求
聽不到
就自己做吧
第二個故事
以開源軟體和行動裝置整合校園校務系統
HTML (with only IE)
JSON
HTML5
為什麼我們要做這個?
我正要成為
大一新鮮人的事情
IE Only
可是我用linux
那時候不會寫 APP,
就用 GreaseMonkey 解決這個問題
事情就這樣告一段落了
畢竟我的繳費單印完了,我也能不用 IE 就查成績
直到今年6月,
同學搞定Python登入的方式
import requests
AP_LOGIN_URL = "http://140.127.113.227/kuas/perchk.jsp"
def login(username, password):
s = requests.session()
data = {"uid": username, "pwd": password}
r = s.post(AP_LOGIN_URL, data=data)
return s
我們弄出了一個
API SERVER
API SERVER
- /ap/login
- /ap/query
- /leave/query
有了API SERVER
還不夠滿足
Mobile First
使用者人數?
BUT
正常的, 要放假了
使用人數
@Google Play & App Store
所以,在校務通中,
我們用了什麼技術?
系統架構
中介系統
- 無法修改原有系統
- 寫死在 mobile app 不好
- 靠中介系統處理資料
中介系統
中介系統
中介系統
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
中介系統
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()
HTTP stuff
requests-python
requests.get("http://tao.kuas.cc")
requests.post("http://ap.kuas.edu.tw",
data={"uid": "123",
"pwd": "123"}
)
session = requests.Session()
session.get()
session.post()
session.cookies
def login(session, username, password):
data = {"uid": username, "pwd": password}
r = session.post(AP_LOGIN_URL, data=data, timeout=LOGIN_TIMEOUT)
root = etree.HTML(r.text)
try:
is_login = not root.xpath("//script")[-1].text.startswith("alert")
except:
is_login = False
return is_login
def login(session, username, password):
data = {"uid": username, "pwd": password}
r = session.post(AP_LOGIN_URL, data=data, timeout=LOGIN_TIMEOUT)
root = etree.HTML(r.text)
try:
is_login = not root.xpath("//script")[-1].text.startswith("alert")
except:
is_login = False
return is_login
<script language='javascript'>
alert('[11] 無此帳號或密碼不正確,請重新輸入。');
top.location.href='index.html';
</script>
@app.route('/ap/login', methods=['POST'])
@cross_origin(supports_credentials=True)
def login_post():
if request.method == "POST":
session.permanent = True
# Start login
username = request.form['username']
password = request.form['password']
s = requests.Session()
is_login = function.login(s, username, password)
if is_login:
# Serialize cookies with domain
session['c'] = dump_cookies(s.cookies)
session['username'] = username
return "true"
else:
return "false"
return render_template("login.html")
def dump_cookies(cookies_list):
cookies = []
for c in cookies_list:
cookies.append({
'name': c.name,
'domain': c.domain,
'value': c.value
})
return cookies
Multi domain cookies
def set_cookies(s, cookies):
for c in cookies:
s.cookies.set(
c['name'],
c['value'],
domain=c['domain']
)
Multi domain cookies
def dump_cookies(cookies_list):
cookies = []
for c in cookies_list:
cookies.append({
'name': c.name,
'domain': c.domain,
'value': c.value
})
return cookies
Authenticate
def authenticate(func):
@wraps(func)
def call(*args, **kwargs):
if 'c' in session:
return func(*args, **kwargs)
else:
return "false"
return call
Before
Authenticate
@app.route("/ap/login_test")
def login_test():
if 'c' not in session:
return "false"
return "You are login."
After
Authenticate
@app.route("/ap/login_test")
@authenticate
def login_test():
return "You are login."
>>> import requests
>>> s = requests.Session()
>>> r = s.post("http://localhost:5000/ap/login",
... data={"username": "1102108133", "password": "111"}
... )
>>> r.text
'true'
query_url = "http://140.127.113.227/kuas/%s_pro/%s.jsp?"
def query(session, qid=None, args=None):
ls_random = random_number(session, RANDOM_ID)
data = {"arg01": "", "arg02": "", "arg03": "",
"fncid": "", "uid": "", "ls_randnum": ""}
data['ls_randnum'] = ls_random
data['fncid'] = qid
for key in args:
data[key] = args[key]
try:
content = session.post(
query_url % (qid[:2], qid),
data=data,
timeout=QUERY_TIMEOUT
).content
except requests.exceptions.ReadTimeout:
content = ""
return content
>>> r.text
'true'
>>> r = s.post("http://localhost:5000/ap/query",
... data={"fncid": "ag222", "arg01": "103", "arg02": "01"}
... )
>>> r.text
<input type="button" class=button value="回上一頁" style="cursor:hand;height:20px"
onclick="vbscript:history.go(-1)"><font style=\'font-style: normal; font-variant:
normal; font-weight: normal; font-size: 9pt; font-family: 細明體\'> 學生:『<font
color=\'blue\'>呂紹榕</font>』課表資料如下:</font><form name=thisform method=post ><
div align=center><font style="font-size: 9pt; font-family: 細明體" color="#000000">
【選 課 清 單】</font></div><table border="1" align="center" cellspacing="0" cellpa
dding="4" width="100%" bgcolor="#cccccc" bordercolor="#999999" bordercolordark="w
hite"><tr align=center bgcolor=\'#ebebeb\'><td align=\'left\' nowrap><font style=\
'font-style: normal; font-variant: normal; font-weight: normal; font-size: 9pt; font-family: 細明體\'>選課代號:</font></td><td
Parse raw data
def course(cont):
"""Parse raw kuas ap course data
Return:
parse data: json
have_saturday: bool
have_sunday: bool
except_text: string
"""
root = etree.HTML(cont)
try:
center = root.xpath("//center")[0]
center_text = list(center.itertext())[0]
except:
center = ""
center_text = ""
# Return if no course data
if center_text.startswith(u'學生目前無選課資料!'):
return [[], False, False, center_text]
tbody = root.xpath("//table")[-1]
course_table = {}
for r_index, r in enumerate(tbody[1:]):
row = {}
for index, c in enumerate(r.xpath("td")):
r = list(filter(lambda x: x != u"\xa0", c.itertext()))
if index == 0:
row['time'] = r[0].replace(" ", "")
else:
row[index] = {
"course_name": "",
"course_teacher": "",
"course_classroom": ""
}
if r:
while len(r) < 3:
r.append("")
row[index]["course_name_simple"] = r[0][:2]
row[index]["course_name"] = r[0]
row[index]["course_teacher"] = r[1]
row[index]["course_classroom"] = r[2]
course_table[r_index] = row
# Check if over 8th didn't have class
token_b = False
token_night = False
for r in course_table:
for c in course_table[r]:
if r > 9 and c != "time" and course_table[r][c]['course_name']:
if r == 10:
token_b = True
else:
#print(course_table[r][c]['course_name'])
token_night = True
#print(token_b, token_night)
if token_night:
pass
elif token_b:
for i in [11, 12, 13, 14]:
del course_table[i]
else:
for i in [10, 11, 12, 13, 14]:
del course_table[i]
# Check Saturday and Sunday class
have_saturday = False
have_sunday = False
for r in course_table:
if not isinstance(course_table[r], bool) and course_table[r][6]["course_name"]:
have_saturday = True
if not isinstance(course_table[r], bool) and course_table[r][7]["course_name"]:
have_sunday = True
return [course_table, have_saturday, have_sunday, False]
>>> r = s.post("http://localhost:5000/ap/query",
... data={"fncid": "ag222", "arg01": "103", "arg02": "01"}
... )
>>> r.text
'[{"0": {"1": {"course_name": "", "course_teacher": "", "course_c...
Parse to json
流程
- 想辦法抓取原始資料
- 有登入的地方要登入
- 有其他加密?
- Header
- 解析原始資料
- HTML
- JSON
- ...etc
- 轉換成其他格式
- JSON
- YAML
POST?
e.g. Submit Leave or Submit Bus?
校車預定
session.post('http://bus.kuas.edu.tw/API/Reserves/add',
data="{busId:"+ kid +"}",
headers=headers,
proxies=proxies
)
JS Encryption?
Execjs - Python
Run js in python
def init(session):
global js
#session.get('http://bus.kuas.edu.tw/', headers=headers, proxies=proxies)
session.head("http://bus.kuas.edu.tw")
js = execjs.compile(
js_function + session.get('http://bus.kuas.edu.tw/API/Scripts/a1',
headers=headers,
proxies=proxies
).content
)
data['n'] = js.call('loginEncryption', str(uid), str(pwd))
Long response time
Make some cache
redis-python
- Memory database
- Key-Value
- Fast
ap_query_key = qid +
hashlib.sha512(
str(username) +
str(args) +
SERECT_KEY
).hexdigest()
ag2221b8d45dd7722324855941fe59dc8c29c74f7a83a22518
f54bea2d2b2cdcd61251121b7cb252d5d960b105757ec
4a2d855a3906e0698e003a54080f5b628d1bd5
if not red.exists(ap_query_key):
ap_query_content = parse.parse(qid, ap.query(session, qid, args))
red.set(ap_query_key, json.dumps(ap_query_content))
red.expire(ap_query_key, AP_QUERY_EXPIRE)
else:
ap_query_content = json.loads(red.get(ap_query_key))
Mobile APP
Web app
- No need native performance
- Fast development
Apache Cordova
Bundle web to multi platform
- Android
- iOS
- BlackBerry
- Firefox extensions
- Chrome extensions
- ...etc
Prototype by JQM
因為 UI 關係
轉換到 Ionic UI framework
不同的螢幕大小
不同的 CSS Style
Responsive Web Design
Meda Queries
- By Device Width
- @media screen and (max-width: 320px)
- By Device resolution
- @media print and (min-resolution: 300dpi)
.course-table button {
@extend .button-light;
border: 0px;
width: 100%;
}
.course-prompt-saturday, .course-prompt-sunday {
margin-bottom: -13px;
}
@media screen and (max-width: 360px) {
.course-table button {
height: 40px;
min-width: 40px;
}
.course-table-saturday {
display: none;
}
}
@media screen and (max-width: 430px) {
.course-table-sunday {
display: none;
}
}
SASS
css with superpowers
變數
$link-color-blue: #0f0fff
$link-font-size: 16px
.header a
color: $link-color-black
font-size: $link-font-size
.menu a
color: $link-color-blue
font-size: $link-font-size
計算
$link-color-blue: #0f0fff
$link-font-size: 16px
.header a
color: $link-color-black
font-size: $link-font-size * 2
.menu a
color: $link-color-blue
font-size: $link-font-size * 0.8
@extend
.base-table {
border-collapse: separate;
border-spacing: 0;
border: 2px solid #CCC;
border-radius: 5px;
-moz-border-radius: 5px;
margin: 0px auto;
margin-top: 20px;
width: 90%;
}
@extend
.course-table {
@extend .base-table
width: 100%;
font-size: 20px;
}
我們解決問題了嗎?
打造一個受歡迎的成功 app,最關鍵的三大部分是:
- 問題:你的 app 真能解決人們在意的問題嗎?
- 市場:有上述問題的使用者夠多嗎?
- 產品:你的 app 真的有解決上述那群人所在意的問題嗎?
- Perfectly executing the wrong plan Tomer Sharon
http://www.inside.com.tw/2014/07/29/googles-6-reasons-why-people-do-not-use-your-app
還記得
標題「有用的」
SITCON
學生計算機年會
Live Demo
Q&A
Thanks!
Louie Lu
grapherd@gmail.com
iii
By Louie Lu
iii
- 1,392