Skip to content

Commit

Permalink
Merge pull request #383 from talebook/auto-fill-metadata
Browse files Browse the repository at this point in the history
自动更新图书信息
  • Loading branch information
talebook committed Mar 31, 2024
2 parents 7688a49 + b5eefde commit d15c9e9
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 24 deletions.
52 changes: 45 additions & 7 deletions app/src/pages/admin/books.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
<v-card-text> 此表格仅展示图书的部分字段,点击即可快捷修改。完整图书信息请点击链接查看书籍详情页面</v-card-text>
<v-card-actions>
<v-btn :disabled="loading" outlined color="primary" @click="getDataFromApi"><v-icon>mdi-reload</v-icon>刷新</v-btn>
<template v-if="books_selected.length > 0">
<v-btn :disabled="loading" outlined color="info" @click="auto_fetch"><v-icon>mdi-delete</v-icon>自动填充空缺字段 </v-btn>
</template>
<v-btn :disabled="loading" outlined color="info" @click="meta_dialog = !meta_dialog"><v-icon>mdi-info</v-icon>自动更新图书信息... </v-btn>
<v-spacer></v-spacer>
<v-text-field cols="2" dense v-model="search" append-icon="mdi-magnify" label="搜索" single-line hide-details></v-text-field>
</v-card-actions>
Expand Down Expand Up @@ -174,14 +172,36 @@
</v-data-table>

<!-- 小浮窗提醒 -->
<v-snackbar v-model="snack" :timeout="1000" :color="snackColor">
<v-snackbar v-model="snack" top :timeout="3000" :color="snackColor">
{{ snackText }}

<template v-slot:action="{ attrs }">
<v-btn v-bind="attrs" text @click="snack = false"> 关闭 </v-btn>
</template>
</v-snackbar>
</v-card>

<!-- 提醒拉取图书的规则说明 -->
<v-dialog v-model="meta_dialog" persistent transition="dialog-bottom-transition" width="500">
<v-card>
<v-toolbar flat dense dark color="primary"> 提醒 </v-toolbar>
<v-card-title></v-card-title>
<v-card-text>
<p> 即将从互联网拉取所有图书的书籍信息,请了解以下功能限制:</p>
<p> 1. 请在「系统设置」中配置好「互联网书籍信息源」,启用豆瓣插件;</p>
<p> 2. 本操作只更新「没有封面」或「没有简介」的图书;</p>
<p> 3. 受限于豆瓣等服务的限制,每秒钟仅更新1本书; </p>
<br></br>
<p> 预计需要运行 {{auto_fill_mins}} 分钟,在此期间请不要停止程序</p>
</v-card-text>
<v-card-actions>
<v-btn @click="meta_dialog = !meta_dialog">取消</v-btn>
<v-spacer></v-spacer>
<v-btn color="primary" @click="auto_fill">开始执行!</v-btn>
</v-card-actions>
</v-card>
</v-dialog>

</v-card>
</template>

<script>
Expand All @@ -190,6 +210,7 @@ export default {
snack: false,
snackColor: "",
snackText: "",
meta_dialog: false,
books_selected: [],
tag_input: null,
Expand Down Expand Up @@ -225,6 +246,11 @@ export default {
deep: true,
},
},
computed: {
auto_fill_mins: function() {
return Math.floor(this.total/60) + 1;
}
},
methods: {
getDataFromApi() {
this.loading = true;
Expand Down Expand Up @@ -261,8 +287,20 @@ export default {
this.loading = false;
});
},
auto_fetch() {
this.$alert("error", "功能正在开发中");
auto_fill() {
this.$backend("/admin/book/fill", {
method: "POST",
body: JSON.stringify({"idlist": "all"}),
})
.then((rsp) => {
this.meta_dialog = false;
if (rsp.err != "ok") {
this.$alert("error", rsp.msg);
}
this.snack = true;
this.snackColor = "success";
this.snackText = rsp.msg;
})
},
delete_book(book) {
this.loading = true;
Expand Down
21 changes: 18 additions & 3 deletions app/src/pages/admin/settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,23 @@ export default {
fields: [ ],
show_friends: true,
},
{
show: false,
title: "互联网书籍信息源",
fields: [
{ icon: "", key: "auto_fill_meta", label: "自动从互联网拉取新书的书籍信息", type: 'checkbox' },
{ icon: "info", key: "douban_baseurl", label: "豆瓣插件API地址(例如 http://10.0.0.1:8080 )" },
{ icon: "info", key: "douban_max_count", label: "豆瓣插件API查询结果数量" },
],
tips: [
{
text: "若需要启用豆瓣插件,请参阅安装文档的说明。",
link: "https://github.com/talebook/talebook/blob/master/document/INSTALL.zh_CN.md#%E5%85%B6%E4%BB%96%E9%85%8D%E7%BD%AE",
}
],
},
{
show: false,
title: "高级配置项",
Expand All @@ -207,8 +224,6 @@ export default {
},
{ icon: "info", key: "avatar_service", label: "可使用www.gravatar.com或cravatar.cn头像服务" },
{ icon: "info", key: "MAX_UPLOAD_SIZE", label: "文件上传字节数限制(例如100MB或100KB)" },
{ icon: "info", key: "douban_baseurl", label: "豆瓣插件API地址(例如 http://10.0.0.1:8080 )" },
{ icon: "info", key: "douban_max_count", label: "豆瓣插件API查询结果数量" },
{ icon: "lock", key: "cookie_secret", label: "COOKIE随机密钥" },
{ icon: "info", key: "scan_upload_path", label: "批量导入扫描目录" },
{ icon: "info", key: "push_title", label: "邮件推送的标题" },
Expand All @@ -218,7 +233,7 @@ export default {
],
tips: [
{
text: "若需要调整Logo,或启用豆瓣插件,请参阅安装文档的说明。",
text: "若需要调整Logo,请参阅安装文档的说明。",
link: "https://github.com/talebook/talebook/blob/master/document/INSTALL.zh_CN.md#%E5%85%B6%E4%BB%96%E9%85%8D%E7%BD%AE",
}
],
Expand Down
2 changes: 1 addition & 1 deletion app/src/pages/book/_bookid.vue
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@
</template>
</v-toolbar>
<v-row>
<v-col class="ma-auto" cols="8" sm="4">
<v-col class="mx-auto" cols="8" sm="4">
<v-img class="book-img" :src="book.img" :aspect-ratio="11 / 15" max-height="500px"
contain></v-img>
</v-col>
Expand Down
25 changes: 25 additions & 0 deletions webserver/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import tornado

from webserver import loader
from webserver.services.autofill import AutoFillService
from webserver.services.mail import MailService
from webserver.handlers.base import BaseHandler, auth, js, is_admin
from webserver.models import Reader
Expand Down Expand Up @@ -250,6 +251,7 @@ def post(self):
"douban_apikey",
"douban_baseurl",
"douban_max_count",
"auto_fill_meta",
"push_title",
"push_content",
"site_title",
Expand Down Expand Up @@ -470,6 +472,28 @@ def get(self):
return {"err": "ok", "items": books, "total": total}


class AdminBookFill(BaseHandler):
@js
@is_admin
def post(self):
req = tornado.escape.json_decode(self.request.body)
idlist = req["idlist"]
if not idlist:
return {"err": "params.error", "msg": _(u"参数错误")}

if idlist == "all":
idlist = list(self.cache.search(""))
elif isinstance(idlist, list):
for bid in idlist:
if not isinstance(bid, int):
return {"err": "params.error.idlist", "msg": _(u"idlist参数错误")}
else:
return {"err": "params.error.idlist", "msg": _(u"idlist参数错误")}

AutoFillService().auto_fill_all(idlist)
return {"err": "ok", "msg": _(u"任务启动成功!请耐心等待,稍后再来刷新页面")}


def routes():
return [
(r"/api/admin/ssl", AdminSSL),
Expand All @@ -478,4 +502,5 @@ def routes():
(r"/api/admin/settings", AdminSettings),
(r"/api/admin/testmail", AdminTestMail),
(r"/api/admin/book/list", AdminBookList),
(r"/api/admin/book/fill", AdminBookFill),
]
3 changes: 3 additions & 0 deletions webserver/handlers/book.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from tornado import web

from webserver import loader, utils
from webserver.services.autofill import AutoFillService
from webserver.services.convert import ConvertService
from webserver.services.extract import ExtractService
from webserver.services.mail import MailService
Expand Down Expand Up @@ -453,6 +454,8 @@ def post(self):
item.book_id = book_id
item.collector_id = self.user_id()
item.save()

AutoFillService().auto_fill(book_id)
return {"err": "ok", "book_id": book_id}


Expand Down
7 changes: 3 additions & 4 deletions webserver/plugins/meta/baike/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@

import logging
import re
import traceback

import requests
from gettext import gettext as _

from webserver.plugins.meta.douban import str2date
from .baidubaike.baidubaike import Page
Expand Down Expand Up @@ -39,8 +38,8 @@ def get_book(self, title):
def _baike(self, title):
try:
return Page(title)
except Exception:
logging.error(traceback.print_exc())
except Exception as err:
logging.error(_(f"百科接口异常: {err}"))
return None

def _metadata(self, baike):
Expand Down
23 changes: 14 additions & 9 deletions webserver/plugins/meta/douban.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import logging
import re
import sys
import traceback
from gettext import gettext as _

import requests
Expand Down Expand Up @@ -63,21 +62,21 @@ def request(self, url, params={}):
try:
rsp = requests.get(url, headers=CHROME_HEADERS, params=params)
except Exception as e:
logging.error("douban API error, request fail, err=%s", str(e))
logging.error("豆瓣接口异常: request fail, err=%s", str(e))
return None

if rsp.status_code != 200:
logging.error("douban API error: status_code[%s] != 200 OK", rsp.status_code)
logging.error("豆瓣接口异常: status_code[%s] != 200 OK", rsp.status_code)
return None

try:
data = rsp.json()
except json.JSONDecodeError:
logging.error("douban API error: json decode fail, content:\n%s", rsp.content)
logging.error("豆瓣接口异常: json decode fail, content:\n%s", rsp.content)
return None

if "code" in data and data["code"] != 0:
logging.error("douban API error: code=%d, msg=%s", rsp["code"], rsp["msg"])
logging.error("豆瓣接口异常: code=%d, msg=%s", rsp["code"], rsp["msg"])
return None
return data

Expand Down Expand Up @@ -114,6 +113,12 @@ def get_book_by_title(self, title, author=None):
def get_book(self, md):
return self.get_metadata(md)

def get_book_detail(self, md):
# 字典结构体,转化格式
douban_id = md['id'] if isinstance(md, dict) else md.douban_id
info = self.get_book_by_id(douban_id)
return self._metadata(info)

def get_metadata(self, md):
book = None
if md.douban_id:
Expand Down Expand Up @@ -178,17 +183,17 @@ def get_douban_metadata(mi):
api = DoubanBookApi()
try:
return api.get_metadata(mi, False)
except Exception:
logging.error(traceback.format_exc())
except Exception as err:
logging.error(f"豆瓣接口异常: {err}")
return None


def select_douban_metadata(mi):
api = DoubanBookApi()
try:
return api.get_metadata(mi, True)
except Exception:
logging.error(traceback.format_exc())
except Exception as err:
logging.error(f"豆瓣接口异常: {err}")
return None


Expand Down
13 changes: 13 additions & 0 deletions webserver/services/async_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,19 @@ def async_mode(self):
''' for unittest '''
return True

@staticmethod
def register_function(service_func):
name = service_func.__name__
logging.error("service register <%s>", name)

def func_wrapper(ins: AsyncService, *args, **kwargs):
s = AsyncService()
ins.setup(s.db, s.scoped_session)
logging.error("[FUNC ] service call %s(%s, %s)", name, args, kwargs)
return service_func(ins, *args, **kwargs)

return func_wrapper

@staticmethod
def register_service(service_func):
name = service_func.__name__
Expand Down
Loading

0 comments on commit d15c9e9

Please sign in to comment.