各类的网盘索引程序有很多了,笔者尝试了PanIndex,最后使用的是JustList,也经历了一翻踩坑,遂总结如下。
使用网盘索引程序,再搭配上免费的应用云或者函数计算,就可以制作一个免费的视频播放网站啦。当然,视频播放站点只是一个引子,做做静态博客之类的也不错。
方案一:heroku 方案
heroku不多说了,国外免费的PaaS平台,可以免费部署一个应用。使用heroku托管JustList的主要步骤如下:
- 新建一个空的heroku应用,起个名字
myappheroku
- 将JustList复制到本地
git clone https://github.com/txperl/JustList
- 适配heroku启动环境
修改步骤2得到的JustList
文件夹中的内容:
-
requirements.txt
添加一行gunicorn
- 新建文件
Procfile
内容为web: gunicorn main:app
- 适配入口函数,对
main.py
做以下修改:
# ...
rootPath = os.path.dirname(os.path.abspath(__file__)) + "/" # 修改rootPath,避免gunicorn读取到错误的rootPath无法启动:boolean型变量无法取下标
# ...
# if __name__ == '__main__': # 移动到下面,避免gunicorn启动时Altfe未初始化的问题:获取列表的接口未正常注册
# Altfe 框架初始化
classRoot.setENV("rootPath", rootPath)
classRoot.setENV("rootPathFrozen", rootPathFrozen)
bridge.bridgeInit().run(hint=True)
# 调整日志等级
logging.getLogger("werkzeug").setLevel(logging.ERROR)
if __name__ == '__main__':
# 启动
# app.run(host="127.0.0.1", port=5000, debug=True, threaded=True, use_reloader=False)
http_server = WSGIServer(("0.0.0.0", 5000), app)
http_server.serve_forever()
对main.py
的修改主要有两处:一是修改了rootPath
,二是移动了if __name__ == '__main__'
语句。分别解决了boolean型变量无法取下标和获取文件列表的接口未注册的问题。
- 修改帐号配置
帐号配置在app/config/cloud189.yml
路径下,修改为自己的天翼云盘帐号密码即可。
⚠注意事项:请不要将自己的帐号密码明文写在公开的仓库里面。可以采用本地存储通过heroku cli工具上传至heroku。如果使用GitHub仓库,请使用私有仓库,或从环境变量等中读取帐号密码。
如果采用环境变量读取帐号密码,请修改app/lib/core/cloud189/main.py
:
# ...
import os
# ...
self.api[u] = cloud189.cloud189(
os.environ.get(self.conf["accounts"][u][0]), os.environ.get(self.conf["accounts"][u][1])
)
#...
同时,需要配置heroku的环境变量:
heroku config:set USER189=USER --app myappheroku
heroku config:set PASS189=PASS --app myappheroku
myappheroku
应当与你的heroku应用名称相同,USER189
和PASS189
是填写在cloud189.yml
的信息,USER
和PASS
是真实的帐号密码。
- 部署
heroku login
heroku git:clone -a myappheroku
cd myappheroku
然后将Justlist
文件夹中的内容全部(除隐藏文件夹.git
外)复制到myappheroku
文件夹,之后:
git add .
git commit -am "make it better"
git push heroku master
就可以愉快地玩耍了~
方案二:阿里云 函数计算 方案
为什么采用阿里云的函数计算,不采用某讯云呢?因为某讯云的函数计算是收费的,而阿里云的免费额度对个人来说基本够用。注意:需要使用的是阿里云-函数计算 FC-应用
,不是阿里云-Serverless应用引擎 SAE的应用。这两者的区别还是蛮大的。重要的是,SAE的所有内容都是计费的,而函数计算 FC-的应用
在轻量使用下仅出网流量计费。对网盘索引类程序而言,视频和图片等的流量走的都是网盘服务商(例如天翼云盘),自身站点的流量主要是一个静态网页和几个API接口,流量很少,这点流量对于函数计算来说完全是挠痒痒。
- 创建应用
一开始阿里云会让你关联GitHub,关联之后创建应用即可:函数计算 FC (aliyun.com)https://fcnext.console.aliyun.com/applications/create
JustList采用的是Flask框架,打开链接后,搜索框搜索flask
,使用该模板(立即创建)。这里推荐将该应用
与Github的一个私有仓库建立关联,这样每次Push之后,阿里云会自动重新部署应用。
将私有仓库复制到本地后,之后开始修改大改:
- 修改帐号配置
仓库的目录结构如上图所示,需要将JustList中的内容复制到
code
文件夹下。
帐号配置在code/app/config/cloud189.yml
路径下,修改为自己的天翼云盘帐号密码即可。
⚠注意事项:阿里云在部署应用时提供了可视化的环境变量配置,但是未自动将环境变量穿透Docker内部。这就导致了python代码
os.environ.get
一度获取不到环境变量。如果python代码需要获取环境变量,需要Dockerfile再传递一次(我要这可视化配置有何用)。
我一开始是使用环境变量设置的帐号密码,结果一直无法获取天翼网盘的token,最后才找到原因。这里推荐大家用私有仓库,直接写帐号密码,这样比较方便,还可以避免许多问题。
- 适配函数计算环境
之后,按照模板要求,需要将code/main.py
重命名为code/index.py
,并将端口修改为9000。
对index.py
做以下修改:
# ...
rootPath = os.path.dirname(os.path.abspath(__file__)) + "/" # 修改rootPath
# ...
# if __name__ == '__main__': # 这个index.py是入口,所以这一行动不动都行
# Altfe 框架初始化
classRoot.setENV("rootPath", rootPath)
classRoot.setENV("rootPathFrozen", rootPathFrozen)
bridge.bridgeInit().run(hint=True)
# 调整日志等级
logging.getLogger("werkzeug").setLevel(logging.ERROR)
if __name__ == '__main__':
# 启动
http_server = WSGIServer(("0.0.0.0", 9000), app)
http_server.serve_forever()
对index.py
的修改主要有两处:一是修改了rootPath
,二是修改了端口号。
git push
之后,阿里云会自动重新部署。打开函数计算的网页,发现函数计算已经开始生效了,可以看到JustList的前端网页,但前端显示的列表内容是空的。经过一番调试之后,发现获取文件列表的接口返回了404。和先前状态码为200,但返回内容为方法不存在不同,这次是直接返回了404状态码。
- 优化触发器
经过仔细排查,终于找到了原因。阿里云函数计算的Flask应用模板默认的触发器设置使得函数仅在GET
请求下触发,导致JustList的POST
接口全部404
。我在调试的时候一度找不到问题所在。(而且,这个函数计算 FC-应用
调试起来挺费劲的,缺少简单的可视化日志,只有冗长的日志配置,关键还要收费。)
对仓库下面的几个.yaml
文件做以下修改:
methods:
- GET
- POST # 新增
√
当我以为,到这里,已经万事俱备,该晴天大好的时候。然而并不是。果然是我太天真了。
- 移除定时函数
改好了之后,获取全部文件列表的POST接口终于不404
了,也不是200
+方法不存在,而是返回了一个空对象{}
。真是奇怪了。经过深度调试(十几次commit),最终,终于找到了问题所在。函数计算和heroku不同,函数计算是由触发器动态触发,并动态计费的,而heroku是常驻后台的。这里和Serverless应用引擎 SAE也不一样,SAE只要不停止应用,就会一直计费。
可能是出于设计范式的原因,函数计算 FC的单个函数不允许同时执行多个线程(未找到相关文档)。因而,code/app/lib/core/cloud189/main.py
中的代码未正常执行:
t = threading.Timer(0, self.__childth_check)
t.setDaemon(True)
t.start()
print('Are you OK?')
def __childth_check(self):
print("I'm okay*1")
while True:
print("I'm okay*2")
tim = time.time()
try:
print("I'm okay*3")
self.__update_token(tim)
if tim > self.listOutdated:
self.load_list()
except Exception as e:
self.STATIC.localMsger.error(e)
time.sleep(self.conf["sys_checkTime"])
上面的代码中,可以得到Are you OK?
,但得不到任何一个I'm okay
。
临时的解决办法:
self.__childth_check()
# t = threading.Timer(0, self.__childth_check)
# t.setDaemon(True)
# t.start()
print('Are you okay?')
def __childth_check(self):
tim = time.time()
try:
self.__update_token(tim)
if tim > self.listOutdated:
self.load_list()
except Exception as e:
self.STATIC.localMsger.error(e)
这样每次请求后端都会刷新token,由于前端有缓存,因而并不会每次都请求所有文件列表的接口,所以代价并不大。更优雅的实现应当是将这个定时任务交给一个定时函数,并在多个函数间共享token(日后再议)。
至此,终于Opps了~
成品如下图: