app.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. # 程序的主入口
  2. # 承担服务器容器+程序作用
  3. # 服务器容器:提供http容器服务,程序放置于该容器中运行
  4. # 程序:本体-智能瞭望与智能问数系统 B/s架构
  5. import os
  6. import sys
  7. import tornado.ioloop
  8. import tornado.web
  9. from tornado.httpserver import HTTPServer
  10. # from app.controllers.base import BaseHandler
  11. # 引入auth - controller层
  12. from app.controllers.auth import LoginHandler,LogoutHandler
  13. from app.controllers.home import IndexHandler, DashboardHandler, DashboardApiHandler
  14. from app.controllers.user import UserPageHandler, UserApiHandler
  15. from app.controllers.menu import MenuPageHandler, MenuApiHandler
  16. from app.controllers.role import RolePageHandler, RoleApiHandler
  17. from app.controllers.model_engine import ModelEnginePageHandler, ModelEngineApiHandler, ModelTestApiHandler
  18. from app.controllers.spy_source import SpySourcePageHandler, SpySourceApiHandler
  19. from app.controllers.data_warehouse import DataWarehousePageHandler, DataWarehouseApiHandler, DataWarehouseDeepCrawlSseHandler
  20. from app.controllers.spy import SpyPageHandler, SpyRunApiHandler, SpyCommitApiHandler
  21. from app.controllers.api_manage import ApiManagePageHandler, ApiManageApiHandler, ApiInterfaceProxyHandler
  22. from app.controllers.digital_employee import DigitalEmployeePageHandler, DigitalEmployeeApiHandler, DigitalEmployeeChatApiHandler
  23. from app.controllers.chat_admin import ChatSessionsAdminPageHandler, ChatSessionsAdminApiHandler, ChatMessagesAdminPageHandler, ChatMessagesAdminApiHandler
  24. from app.controllers.skill_manage import SkillManagePageHandler, SkillManageApiHandler
  25. from app.controllers.portal import PortalRootHandler, PortalLoginHandler, PortalRegisterHandler, PortalChatPageHandler, PortalLogoutHandler, PortalModelsApiHandler, PortalEmployeesApiHandler, PortalSessionsApiHandler, PortalMessagesApiHandler, PortalChatStreamApiHandler, PortalAudioProxyHandler
  26. # 引入db - model层
  27. from app.models.db import init_db, get_connection
  28. from app.models.user import UserRepository
  29. from app.models.role import RoleRepository
  30. from app.models.menu import MenuRepository
  31. try:
  32. if hasattr(sys.stdout, "reconfigure"):
  33. sys.stdout.reconfigure(encoding="utf-8", errors="replace")
  34. if hasattr(sys.stderr, "reconfigure"):
  35. sys.stderr.reconfigure(encoding="utf-8", errors="replace")
  36. except Exception:
  37. pass
  38. # class HealthHandler(tornado.web.RequestHandler):
  39. # def get(self):
  40. # self.write({"status":"ok"})
  41. # class LoginHandler(tornado.web.RequestHandler):
  42. # def get(self):
  43. # self.write(f"""<h3>模拟登录验证测试BaesHandler</h3>
  44. # <form method="post">
  45. # <button type="submit">登录admin</button>
  46. # """
  47. # + self.xsrf_form_html() +
  48. # """
  49. # </form>
  50. # """)
  51. # def post(self):
  52. # next_url = self.get_argument("next","/private")
  53. # self.set_secure_cookie("username","admin")
  54. # # 写完安全的cookie以后,跳转到目标地址
  55. # self.redirect(next_url)
  56. # class PrivateHandler(BaseHandler):
  57. # @tornado.web.authenticated
  58. # def get(self):
  59. # self.write(self.current_user)
  60. def make_app():
  61. # return tornado.web.Application([
  62. # ("/abc",HealthHandler),
  63. # ("/login.jsp",HealthHandler),
  64. # ("/",HealthHandler),
  65. # ("/login.php",HealthHandler)
  66. # ],debug=True)
  67. # return tornado.web.Application([
  68. # (r"/",LoginHandler),
  69. # (r"/login",LoginHandler),
  70. # (r"/abc",HealthHandler),
  71. # (r"/private",PrivateHandler)
  72. # ],
  73. # cookie_secret="demo-cookie-secret-change-me",
  74. # login_url="/",
  75. # xsrf_cookies=True,
  76. # debug=True
  77. # )
  78. base_url = os.path.dirname(os.path.abspath(__file__))
  79. settings = dict(
  80. # 预留view 层的内容配置
  81. template_path=os.path.join(base_url,"app","templates"),
  82. static_path=os.path.join(base_url,"app","static"),
  83. cookie_secret="demo-cookie-secret-change-me",
  84. login_url="/admin/login",
  85. xsrf_cookies=True,
  86. debug=True,
  87. autoreload=True
  88. )
  89. return tornado.web.Application([
  90. (r"/", PortalRootHandler),
  91. (r"/login", PortalLoginHandler),
  92. (r"/register", PortalRegisterHandler),
  93. (r"/chat", PortalChatPageHandler),
  94. (r"/logout", PortalLogoutHandler),
  95. (r"/api/models", PortalModelsApiHandler),
  96. (r"/api/employees", PortalEmployeesApiHandler),
  97. (r"/api/sessions", PortalSessionsApiHandler),
  98. (r"/api/messages", PortalMessagesApiHandler),
  99. (r"/api/chat/stream", PortalChatStreamApiHandler),
  100. (r"/api/audio_proxy", PortalAudioProxyHandler),
  101. (r"/u/?", tornado.web.RedirectHandler, {"url": "/"}),
  102. (r"/u/login", tornado.web.RedirectHandler, {"url": "/login"}),
  103. (r"/u/register", tornado.web.RedirectHandler, {"url": "/register"}),
  104. (r"/u/chat", tornado.web.RedirectHandler, {"url": "/chat"}),
  105. (r"/u/logout", tornado.web.RedirectHandler, {"url": "/logout"}),
  106. (r"/api/u/models", tornado.web.RedirectHandler, {"url": "/api/models"}),
  107. (r"/api/u/employees", tornado.web.RedirectHandler, {"url": "/api/employees"}),
  108. (r"/api/u/sessions", tornado.web.RedirectHandler, {"url": "/api/sessions"}),
  109. (r"/api/u/messages", tornado.web.RedirectHandler, {"url": "/api/messages"}),
  110. (r"/api/u/chat/stream", tornado.web.RedirectHandler, {"url": "/api/chat/stream"}),
  111. (r"/admin/?", IndexHandler),
  112. (r"/admin/login", LoginHandler),
  113. (r"/admin/logout", LogoutHandler),
  114. (r"/admin/dashboard", DashboardHandler),
  115. (r"/dashboard", DashboardHandler),
  116. (r"/api/dashboard", DashboardApiHandler),
  117. (r"/auth/login", LoginHandler),
  118. (r"/auth/logout", LogoutHandler),
  119. (r"/user/list", UserPageHandler),
  120. (r"/api/user", UserApiHandler),
  121. (r"/menu/list", MenuPageHandler),
  122. (r"/api/menu", MenuApiHandler),
  123. (r"/role/list", RolePageHandler),
  124. (r"/api/role", RoleApiHandler),
  125. (r"/model_engine/list", ModelEnginePageHandler),
  126. (r"/api/model_engine", ModelEngineApiHandler),
  127. (r"/api/model_engine/test", ModelTestApiHandler),
  128. # 瞭望数据源管理路由
  129. (r"/spy_source/list", SpySourcePageHandler),
  130. (r"/api/spy_source", SpySourceApiHandler),
  131. # 数据仓库路由
  132. (r"/data_warehouse/list", DataWarehousePageHandler),
  133. (r"/api/data_warehouse", DataWarehouseApiHandler),
  134. (r"/api/data_warehouse/deep_sse", DataWarehouseDeepCrawlSseHandler),
  135. # 独立瞭望采集界面路由
  136. (r"/spy", SpyPageHandler),
  137. (r"/api/spy/run", SpyRunApiHandler),
  138. (r"/api/spy/commit", SpyCommitApiHandler),
  139. # 接口管理
  140. (r"/api_manage/list", ApiManagePageHandler),
  141. (r"/api/api_manage", ApiManageApiHandler),
  142. (r"/api/interface_proxy", ApiInterfaceProxyHandler),
  143. # 数字员工
  144. (r"/digital_employee/list", DigitalEmployeePageHandler),
  145. (r"/api/digital_employee", DigitalEmployeeApiHandler),
  146. (r"/api/digital_employee/chat", DigitalEmployeeChatApiHandler),
  147. # 技能管理
  148. (r"/skill_manage/list", SkillManagePageHandler),
  149. (r"/api/skill_manage", SkillManageApiHandler),
  150. # 用户侧会话/对话管理(后台)
  151. (r"/chat_admin/sessions", ChatSessionsAdminPageHandler),
  152. (r"/api/chat_admin/sessions", ChatSessionsAdminApiHandler),
  153. (r"/chat_admin/messages", ChatMessagesAdminPageHandler),
  154. (r"/api/chat_admin/messages", ChatMessagesAdminApiHandler),
  155. ],
  156. **settings
  157. )
  158. if __name__ == "__main__":
  159. # 启动服务之前,检查并初始化数据库表
  160. init_db()
  161. # 注入默认超管角色
  162. roles = RoleRepository.get_all_roles()
  163. super_role_id = None
  164. for r in roles:
  165. if r['code'] == 'super_admin':
  166. super_role_id = r['id']
  167. break
  168. if not super_role_id:
  169. super_role_id = RoleRepository.create_role("超级管理员", "super_admin", is_system=1)
  170. user_role = RoleRepository.get_by_code("user")
  171. user_role_id = user_role["id"] if user_role else RoleRepository.create_role("普通用户", "user", is_system=1)
  172. member_role = RoleRepository.get_by_code("member")
  173. if not member_role:
  174. RoleRepository.create_role("会员用户", "member", is_system=1)
  175. # 注入默认菜单
  176. menus = MenuRepository.get_all_menus()
  177. if not menus:
  178. sys_id = MenuRepository.create_menu("系统管理", "", "layui-icon-set", 0, 1)
  179. MenuRepository.create_menu("用户管理", "/user/list", "layui-icon-user", sys_id, 1)
  180. MenuRepository.create_menu("角色管理", "/role/list", "layui-icon-group", sys_id, 2)
  181. MenuRepository.create_menu("功能管理", "/menu/list", "layui-icon-app", sys_id, 3)
  182. # 给超管赋权
  183. all_menus = MenuRepository.get_all_menus()
  184. RoleRepository.assign_role_menus(super_role_id, [m['id'] for m in all_menus])
  185. # 注入默认管理员
  186. admin_user = UserRepository.get_user_by_username("admin")
  187. if not admin_user:
  188. UserRepository.create_user("admin", "admin888", super_role_id)
  189. else:
  190. # 确保已存在的 admin 绑定了超管角色
  191. with get_connection() as conn:
  192. conn.execute("INSERT OR IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)", (admin_user['id'], super_role_id))
  193. with get_connection() as conn:
  194. # 插入默认菜单
  195. conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (5, 0, '模型引擎', 'layui-icon-engine', '/model_engine/list', 5))
  196. conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (6, 0, '瞭望管理', 'layui-icon-find-fill', '', 6))
  197. conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (7, 6, '数据源管理', '', '/spy_source/list', 1))
  198. conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (8, 6, '瞭望采集', '', '/spy', 2))
  199. conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (9, 0, '数据仓库', 'layui-icon-table', '/data_warehouse/list', 7))
  200. api_parent_row = conn.execute("SELECT id FROM menus WHERE parent_id=0 AND name=?", ("接口服务",)).fetchone()
  201. if not api_parent_row:
  202. api_parent_row = conn.execute("SELECT id FROM menus WHERE parent_id=0 AND name=?", ("系统管理",)).fetchone()
  203. api_parent_id = api_parent_row["id"] if api_parent_row else 0
  204. api_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/api_manage/list",)).fetchone()
  205. if not api_row:
  206. conn.execute(
  207. "INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)",
  208. (10, api_parent_id, '接口管理', 'layui-icon-link', '/api_manage/list', 8)
  209. )
  210. api_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/api_manage/list",)).fetchone()
  211. if not api_row:
  212. cursor = conn.execute(
  213. "INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
  214. (api_parent_id, '接口管理', 'layui-icon-link', '/api_manage/list', 8)
  215. )
  216. api_menu_id = cursor.lastrowid
  217. else:
  218. api_menu_id = api_row["id"]
  219. api_current = conn.execute("SELECT name, icon FROM menus WHERE id=?", (api_menu_id,)).fetchone()
  220. if api_current:
  221. if not (api_current["name"] or "").strip():
  222. conn.execute("UPDATE menus SET name=? WHERE id=?", ('接口管理', api_menu_id))
  223. if not (api_current["icon"] or "").strip():
  224. conn.execute("UPDATE menus SET icon=? WHERE id=?", ('layui-icon-link', api_menu_id))
  225. # 确保超级管理员拥有上述菜单权限
  226. for mid in [5, 6, 7, 8, 9, api_menu_id]:
  227. conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, mid))
  228. conn.execute(
  229. """
  230. INSERT OR IGNORE INTO api_endpoints
  231. (name, url, method, response_format, sample_request, qps_window, qps_max, bypass_token, remark, is_active, update_at)
  232. VALUES
  233. (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
  234. """,
  235. (
  236. "网易云随机音乐",
  237. "https://api.52vmy.cn/api/music/wy/rand",
  238. "GET",
  239. "JSON",
  240. "https://api.52vmy.cn/api/music/wy/rand",
  241. 2,
  242. 4,
  243. "",
  244. "",
  245. 1,
  246. ),
  247. )
  248. conn.execute(
  249. """
  250. INSERT OR IGNORE INTO api_endpoints
  251. (name, url, method, response_format, sample_request, qps_window, qps_max, bypass_token, remark, is_active, update_at)
  252. VALUES
  253. (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
  254. """,
  255. (
  256. "三日天气查询",
  257. "https://api.52vmy.cn/api/query/tian",
  258. "GET",
  259. "JSON",
  260. "https://api.52vmy.cn/api/query/tian?city=北京市",
  261. 2,
  262. 4,
  263. "",
  264. "点击前往三日天气API",
  265. 1,
  266. ),
  267. )
  268. service_row = conn.execute("SELECT id FROM menus WHERE parent_id=0 AND name=?", ("智能服务",)).fetchone()
  269. if not service_row:
  270. cursor = conn.execute(
  271. "INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
  272. (0, "智能服务", "layui-icon-component", "", 4)
  273. )
  274. service_menu_id = cursor.lastrowid
  275. else:
  276. service_menu_id = service_row["id"]
  277. de_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/digital_employee/list",)).fetchone()
  278. if not de_row:
  279. cursor = conn.execute(
  280. "INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
  281. (service_menu_id, "数字员工", "layui-icon-username", "/digital_employee/list", 1)
  282. )
  283. de_menu_id = cursor.lastrowid
  284. else:
  285. de_menu_id = de_row["id"]
  286. conn.execute(
  287. "UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
  288. ("数字员工", "layui-icon-username", service_menu_id, 1, de_menu_id)
  289. )
  290. sess_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/chat_admin/sessions",)).fetchone()
  291. if not sess_row:
  292. cursor = conn.execute(
  293. "INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
  294. (service_menu_id, "会话管理", "layui-icon-dialogue", "/chat_admin/sessions", 2)
  295. )
  296. sess_menu_id = cursor.lastrowid
  297. else:
  298. sess_menu_id = sess_row["id"]
  299. conn.execute(
  300. "UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
  301. ("会话管理", "layui-icon-dialogue", service_menu_id, 2, sess_menu_id)
  302. )
  303. msg_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/chat_admin/messages",)).fetchone()
  304. if not msg_row:
  305. cursor = conn.execute(
  306. "INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
  307. (service_menu_id, "对话管理", "layui-icon-chat", "/chat_admin/messages", 3)
  308. )
  309. msg_menu_id = cursor.lastrowid
  310. else:
  311. msg_menu_id = msg_row["id"]
  312. conn.execute(
  313. "UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
  314. ("对话管理", "layui-icon-chat", service_menu_id, 3, msg_menu_id)
  315. )
  316. skill_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/skill_manage/list",)).fetchone()
  317. if not skill_row:
  318. cursor = conn.execute(
  319. "INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
  320. (service_menu_id, "技能管理", "layui-icon-template-1", "/skill_manage/list", 4)
  321. )
  322. skill_menu_id = cursor.lastrowid
  323. else:
  324. skill_menu_id = skill_row["id"]
  325. conn.execute(
  326. "UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
  327. ("技能管理", "layui-icon-template-1", service_menu_id, 4, skill_menu_id)
  328. )
  329. conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, service_menu_id))
  330. conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, de_menu_id))
  331. conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, sess_menu_id))
  332. conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, msg_menu_id))
  333. conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, skill_menu_id))
  334. music_api = conn.execute("SELECT id FROM api_endpoints WHERE url=? AND method='GET'", ("https://api.52vmy.cn/api/music/wy/rand",)).fetchone()
  335. weather_api = conn.execute("SELECT id FROM api_endpoints WHERE url=? AND method='GET'", ("https://api.52vmy.cn/api/query/tian",)).fetchone()
  336. conn.execute(
  337. """
  338. INSERT OR IGNORE INTO digital_employees
  339. (name, alias, category, description, ai_prompt, api_endpoint_id, api_param_name, is_active, update_at)
  340. VALUES
  341. (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
  342. """,
  343. (
  344. "川小农",
  345. "川小农",
  346. "AI",
  347. "基于默认模型与 Prompt 的智能对话数字员工",
  348. "你是数字员工“川小农”。你以专业、克制、可执行的方式回答用户问题。你必须用中文输出。你会先给出结论,再给出清晰步骤与注意事项。遇到信息不足时,先提出最少的澄清点,然后给出可行的默认方案。",
  349. None,
  350. "",
  351. 1,
  352. ),
  353. )
  354. if weather_api:
  355. conn.execute(
  356. """
  357. INSERT OR IGNORE INTO digital_employees
  358. (name, alias, category, description, ai_prompt, api_endpoint_id, api_param_name, is_active, update_at)
  359. VALUES
  360. (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
  361. """,
  362. (
  363. "天气",
  364. "天气",
  365. "NORMAL",
  366. "通过接口服务查询天气:@天气 北京市",
  367. "",
  368. int(weather_api["id"]),
  369. "city",
  370. 1,
  371. ),
  372. )
  373. if music_api:
  374. conn.execute(
  375. """
  376. INSERT OR IGNORE INTO digital_employees
  377. (name, alias, category, description, ai_prompt, api_endpoint_id, api_param_name, is_active, update_at)
  378. VALUES
  379. (?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
  380. """,
  381. (
  382. "音乐",
  383. "音乐",
  384. "NORMAL",
  385. "随机音乐卡片:@音乐",
  386. "",
  387. int(music_api["id"]),
  388. "",
  389. 1,
  390. ),
  391. )
  392. # 注入默认的百度新闻采集源
  393. baidu_source = conn.execute("SELECT id FROM spy_sources WHERE name='百度新闻'").fetchone()
  394. if not baidu_source:
  395. headers_str = """Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
  396. Accept-Encoding: gzip, deflate, br, zstd
  397. Accept-Language: zh-CN,zh;q=0.9
  398. Cache-Control: no-cache
  399. Connection: keep-alive
  400. Cookie: BIDUPSID=5CFCC8D701BF7571598CE9F66EE8E6B5; PSTM=1775407070; BD_UPN=1a314753; BAIDUID=5CFCC8D701BF75717E6B8B51D00D380F:SL=0:NR=10:FG=1;
  401. Host: www.baidu.com
  402. Pragma: no-cache
  403. Referer: https://news.baidu.com/
  404. Upgrade-Insecure-Requests: 1
  405. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 QQBrowser/21.1.8743.400"""
  406. conn.execute(
  407. "INSERT INTO spy_sources (name, entry_url, request_headers, is_active) VALUES (?, ?, ?, ?)",
  408. ("百度新闻", "https://www.baidu.com/s?rtt=1&bsst=1&cl=2&tn=news&rsv_dl=ns_pc&word={关键字}&pn={分页步进}", headers_str, 1)
  409. )
  410. app = make_app()
  411. server = HTTPServer(app)
  412. server.bind(10088)
  413. # 自动CPU核心数
  414. server.start()
  415. try:
  416. print("====== Server 启动成功 ======== 端口:10088 ======", flush=True)
  417. except OSError:
  418. pass
  419. tornado.ioloop.IOLoop.current().start()