# 程序的主入口
# 承担服务器容器+程序作用
# 服务器容器:提供http容器服务,程序放置于该容器中运行
# 程序:本体-智能瞭望与智能问数系统 B/s架构
import os
import sys
import tornado.ioloop
import tornado.web
from tornado.httpserver import HTTPServer
# from app.controllers.base import BaseHandler
# 引入auth - controller层
from app.controllers.auth import LoginHandler,LogoutHandler
from app.controllers.home import IndexHandler, DashboardHandler, DashboardApiHandler
from app.controllers.user import UserPageHandler, UserApiHandler
from app.controllers.menu import MenuPageHandler, MenuApiHandler
from app.controllers.role import RolePageHandler, RoleApiHandler
from app.controllers.model_engine import ModelEnginePageHandler, ModelEngineApiHandler, ModelTestApiHandler
from app.controllers.spy_source import SpySourcePageHandler, SpySourceApiHandler
from app.controllers.data_warehouse import DataWarehousePageHandler, DataWarehouseApiHandler, DataWarehouseDeepCrawlSseHandler
from app.controllers.spy import SpyPageHandler, SpyRunApiHandler, SpyCommitApiHandler
from app.controllers.api_manage import ApiManagePageHandler, ApiManageApiHandler, ApiInterfaceProxyHandler
from app.controllers.digital_employee import DigitalEmployeePageHandler, DigitalEmployeeApiHandler, DigitalEmployeeChatApiHandler
from app.controllers.chat_admin import ChatSessionsAdminPageHandler, ChatSessionsAdminApiHandler, ChatMessagesAdminPageHandler, ChatMessagesAdminApiHandler
from app.controllers.skill_manage import SkillManagePageHandler, SkillManageApiHandler
from app.controllers.portal import PortalRootHandler, PortalLoginHandler, PortalRegisterHandler, PortalChatPageHandler, PortalLogoutHandler, PortalModelsApiHandler, PortalEmployeesApiHandler, PortalSessionsApiHandler, PortalMessagesApiHandler, PortalChatStreamApiHandler, PortalAudioProxyHandler
# 引入db - model层
from app.models.db import init_db, get_connection
from app.models.user import UserRepository
from app.models.role import RoleRepository
from app.models.menu import MenuRepository
try:
if hasattr(sys.stdout, "reconfigure"):
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
if hasattr(sys.stderr, "reconfigure"):
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
# class HealthHandler(tornado.web.RequestHandler):
# def get(self):
# self.write({"status":"ok"})
# class LoginHandler(tornado.web.RequestHandler):
# def get(self):
# self.write(f"""
模拟登录验证测试BaesHandler
#
# """)
# def post(self):
# next_url = self.get_argument("next","/private")
# self.set_secure_cookie("username","admin")
# # 写完安全的cookie以后,跳转到目标地址
# self.redirect(next_url)
# class PrivateHandler(BaseHandler):
# @tornado.web.authenticated
# def get(self):
# self.write(self.current_user)
def make_app():
# return tornado.web.Application([
# ("/abc",HealthHandler),
# ("/login.jsp",HealthHandler),
# ("/",HealthHandler),
# ("/login.php",HealthHandler)
# ],debug=True)
# return tornado.web.Application([
# (r"/",LoginHandler),
# (r"/login",LoginHandler),
# (r"/abc",HealthHandler),
# (r"/private",PrivateHandler)
# ],
# cookie_secret="demo-cookie-secret-change-me",
# login_url="/",
# xsrf_cookies=True,
# debug=True
# )
base_url = os.path.dirname(os.path.abspath(__file__))
settings = dict(
# 预留view 层的内容配置
template_path=os.path.join(base_url,"app","templates"),
static_path=os.path.join(base_url,"app","static"),
cookie_secret="demo-cookie-secret-change-me",
login_url="/admin/login",
xsrf_cookies=True,
debug=True,
autoreload=True
)
return tornado.web.Application([
(r"/", PortalRootHandler),
(r"/login", PortalLoginHandler),
(r"/register", PortalRegisterHandler),
(r"/chat", PortalChatPageHandler),
(r"/logout", PortalLogoutHandler),
(r"/api/models", PortalModelsApiHandler),
(r"/api/employees", PortalEmployeesApiHandler),
(r"/api/sessions", PortalSessionsApiHandler),
(r"/api/messages", PortalMessagesApiHandler),
(r"/api/chat/stream", PortalChatStreamApiHandler),
(r"/api/audio_proxy", PortalAudioProxyHandler),
(r"/u/?", tornado.web.RedirectHandler, {"url": "/"}),
(r"/u/login", tornado.web.RedirectHandler, {"url": "/login"}),
(r"/u/register", tornado.web.RedirectHandler, {"url": "/register"}),
(r"/u/chat", tornado.web.RedirectHandler, {"url": "/chat"}),
(r"/u/logout", tornado.web.RedirectHandler, {"url": "/logout"}),
(r"/api/u/models", tornado.web.RedirectHandler, {"url": "/api/models"}),
(r"/api/u/employees", tornado.web.RedirectHandler, {"url": "/api/employees"}),
(r"/api/u/sessions", tornado.web.RedirectHandler, {"url": "/api/sessions"}),
(r"/api/u/messages", tornado.web.RedirectHandler, {"url": "/api/messages"}),
(r"/api/u/chat/stream", tornado.web.RedirectHandler, {"url": "/api/chat/stream"}),
(r"/admin/?", IndexHandler),
(r"/admin/login", LoginHandler),
(r"/admin/logout", LogoutHandler),
(r"/admin/dashboard", DashboardHandler),
(r"/dashboard", DashboardHandler),
(r"/api/dashboard", DashboardApiHandler),
(r"/auth/login", LoginHandler),
(r"/auth/logout", LogoutHandler),
(r"/user/list", UserPageHandler),
(r"/api/user", UserApiHandler),
(r"/menu/list", MenuPageHandler),
(r"/api/menu", MenuApiHandler),
(r"/role/list", RolePageHandler),
(r"/api/role", RoleApiHandler),
(r"/model_engine/list", ModelEnginePageHandler),
(r"/api/model_engine", ModelEngineApiHandler),
(r"/api/model_engine/test", ModelTestApiHandler),
# 瞭望数据源管理路由
(r"/spy_source/list", SpySourcePageHandler),
(r"/api/spy_source", SpySourceApiHandler),
# 数据仓库路由
(r"/data_warehouse/list", DataWarehousePageHandler),
(r"/api/data_warehouse", DataWarehouseApiHandler),
(r"/api/data_warehouse/deep_sse", DataWarehouseDeepCrawlSseHandler),
# 独立瞭望采集界面路由
(r"/spy", SpyPageHandler),
(r"/api/spy/run", SpyRunApiHandler),
(r"/api/spy/commit", SpyCommitApiHandler),
# 接口管理
(r"/api_manage/list", ApiManagePageHandler),
(r"/api/api_manage", ApiManageApiHandler),
(r"/api/interface_proxy", ApiInterfaceProxyHandler),
# 数字员工
(r"/digital_employee/list", DigitalEmployeePageHandler),
(r"/api/digital_employee", DigitalEmployeeApiHandler),
(r"/api/digital_employee/chat", DigitalEmployeeChatApiHandler),
# 技能管理
(r"/skill_manage/list", SkillManagePageHandler),
(r"/api/skill_manage", SkillManageApiHandler),
# 用户侧会话/对话管理(后台)
(r"/chat_admin/sessions", ChatSessionsAdminPageHandler),
(r"/api/chat_admin/sessions", ChatSessionsAdminApiHandler),
(r"/chat_admin/messages", ChatMessagesAdminPageHandler),
(r"/api/chat_admin/messages", ChatMessagesAdminApiHandler),
],
**settings
)
if __name__ == "__main__":
# 启动服务之前,检查并初始化数据库表
init_db()
# 注入默认超管角色
roles = RoleRepository.get_all_roles()
super_role_id = None
for r in roles:
if r['code'] == 'super_admin':
super_role_id = r['id']
break
if not super_role_id:
super_role_id = RoleRepository.create_role("超级管理员", "super_admin", is_system=1)
user_role = RoleRepository.get_by_code("user")
user_role_id = user_role["id"] if user_role else RoleRepository.create_role("普通用户", "user", is_system=1)
member_role = RoleRepository.get_by_code("member")
if not member_role:
RoleRepository.create_role("会员用户", "member", is_system=1)
# 注入默认菜单
menus = MenuRepository.get_all_menus()
if not menus:
sys_id = MenuRepository.create_menu("系统管理", "", "layui-icon-set", 0, 1)
MenuRepository.create_menu("用户管理", "/user/list", "layui-icon-user", sys_id, 1)
MenuRepository.create_menu("角色管理", "/role/list", "layui-icon-group", sys_id, 2)
MenuRepository.create_menu("功能管理", "/menu/list", "layui-icon-app", sys_id, 3)
# 给超管赋权
all_menus = MenuRepository.get_all_menus()
RoleRepository.assign_role_menus(super_role_id, [m['id'] for m in all_menus])
# 注入默认管理员
admin_user = UserRepository.get_user_by_username("admin")
if not admin_user:
UserRepository.create_user("admin", "admin888", super_role_id)
else:
# 确保已存在的 admin 绑定了超管角色
with get_connection() as conn:
conn.execute("INSERT OR IGNORE INTO user_roles (user_id, role_id) VALUES (?, ?)", (admin_user['id'], super_role_id))
with get_connection() as conn:
# 插入默认菜单
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))
conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (6, 0, '瞭望管理', 'layui-icon-find-fill', '', 6))
conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (7, 6, '数据源管理', '', '/spy_source/list', 1))
conn.execute("INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)", (8, 6, '瞭望采集', '', '/spy', 2))
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))
api_parent_row = conn.execute("SELECT id FROM menus WHERE parent_id=0 AND name=?", ("接口服务",)).fetchone()
if not api_parent_row:
api_parent_row = conn.execute("SELECT id FROM menus WHERE parent_id=0 AND name=?", ("系统管理",)).fetchone()
api_parent_id = api_parent_row["id"] if api_parent_row else 0
api_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/api_manage/list",)).fetchone()
if not api_row:
conn.execute(
"INSERT OR IGNORE INTO menus (id, parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?, ?)",
(10, api_parent_id, '接口管理', 'layui-icon-link', '/api_manage/list', 8)
)
api_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/api_manage/list",)).fetchone()
if not api_row:
cursor = conn.execute(
"INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
(api_parent_id, '接口管理', 'layui-icon-link', '/api_manage/list', 8)
)
api_menu_id = cursor.lastrowid
else:
api_menu_id = api_row["id"]
api_current = conn.execute("SELECT name, icon FROM menus WHERE id=?", (api_menu_id,)).fetchone()
if api_current:
if not (api_current["name"] or "").strip():
conn.execute("UPDATE menus SET name=? WHERE id=?", ('接口管理', api_menu_id))
if not (api_current["icon"] or "").strip():
conn.execute("UPDATE menus SET icon=? WHERE id=?", ('layui-icon-link', api_menu_id))
# 确保超级管理员拥有上述菜单权限
for mid in [5, 6, 7, 8, 9, api_menu_id]:
conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, mid))
conn.execute(
"""
INSERT OR IGNORE INTO api_endpoints
(name, url, method, response_format, sample_request, qps_window, qps_max, bypass_token, remark, is_active, update_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
""",
(
"网易云随机音乐",
"https://api.52vmy.cn/api/music/wy/rand",
"GET",
"JSON",
"https://api.52vmy.cn/api/music/wy/rand",
2,
4,
"",
"",
1,
),
)
conn.execute(
"""
INSERT OR IGNORE INTO api_endpoints
(name, url, method, response_format, sample_request, qps_window, qps_max, bypass_token, remark, is_active, update_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
""",
(
"三日天气查询",
"https://api.52vmy.cn/api/query/tian",
"GET",
"JSON",
"https://api.52vmy.cn/api/query/tian?city=北京市",
2,
4,
"",
"点击前往三日天气API",
1,
),
)
service_row = conn.execute("SELECT id FROM menus WHERE parent_id=0 AND name=?", ("智能服务",)).fetchone()
if not service_row:
cursor = conn.execute(
"INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
(0, "智能服务", "layui-icon-component", "", 4)
)
service_menu_id = cursor.lastrowid
else:
service_menu_id = service_row["id"]
de_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/digital_employee/list",)).fetchone()
if not de_row:
cursor = conn.execute(
"INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
(service_menu_id, "数字员工", "layui-icon-username", "/digital_employee/list", 1)
)
de_menu_id = cursor.lastrowid
else:
de_menu_id = de_row["id"]
conn.execute(
"UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
("数字员工", "layui-icon-username", service_menu_id, 1, de_menu_id)
)
sess_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/chat_admin/sessions",)).fetchone()
if not sess_row:
cursor = conn.execute(
"INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
(service_menu_id, "会话管理", "layui-icon-dialogue", "/chat_admin/sessions", 2)
)
sess_menu_id = cursor.lastrowid
else:
sess_menu_id = sess_row["id"]
conn.execute(
"UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
("会话管理", "layui-icon-dialogue", service_menu_id, 2, sess_menu_id)
)
msg_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/chat_admin/messages",)).fetchone()
if not msg_row:
cursor = conn.execute(
"INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
(service_menu_id, "对话管理", "layui-icon-chat", "/chat_admin/messages", 3)
)
msg_menu_id = cursor.lastrowid
else:
msg_menu_id = msg_row["id"]
conn.execute(
"UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
("对话管理", "layui-icon-chat", service_menu_id, 3, msg_menu_id)
)
skill_row = conn.execute("SELECT id FROM menus WHERE url=?", ("/skill_manage/list",)).fetchone()
if not skill_row:
cursor = conn.execute(
"INSERT INTO menus (parent_id, name, icon, url, order_num) VALUES (?, ?, ?, ?, ?)",
(service_menu_id, "技能管理", "layui-icon-template-1", "/skill_manage/list", 4)
)
skill_menu_id = cursor.lastrowid
else:
skill_menu_id = skill_row["id"]
conn.execute(
"UPDATE menus SET name=?, icon=?, parent_id=?, order_num=? WHERE id=?",
("技能管理", "layui-icon-template-1", service_menu_id, 4, skill_menu_id)
)
conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, service_menu_id))
conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, de_menu_id))
conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, sess_menu_id))
conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, msg_menu_id))
conn.execute("INSERT OR IGNORE INTO role_menus (role_id, menu_id) VALUES (?, ?)", (super_role_id, skill_menu_id))
music_api = conn.execute("SELECT id FROM api_endpoints WHERE url=? AND method='GET'", ("https://api.52vmy.cn/api/music/wy/rand",)).fetchone()
weather_api = conn.execute("SELECT id FROM api_endpoints WHERE url=? AND method='GET'", ("https://api.52vmy.cn/api/query/tian",)).fetchone()
conn.execute(
"""
INSERT OR IGNORE INTO digital_employees
(name, alias, category, description, ai_prompt, api_endpoint_id, api_param_name, is_active, update_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
""",
(
"川小农",
"川小农",
"AI",
"基于默认模型与 Prompt 的智能对话数字员工",
"你是数字员工“川小农”。你以专业、克制、可执行的方式回答用户问题。你必须用中文输出。你会先给出结论,再给出清晰步骤与注意事项。遇到信息不足时,先提出最少的澄清点,然后给出可行的默认方案。",
None,
"",
1,
),
)
if weather_api:
conn.execute(
"""
INSERT OR IGNORE INTO digital_employees
(name, alias, category, description, ai_prompt, api_endpoint_id, api_param_name, is_active, update_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
""",
(
"天气",
"天气",
"NORMAL",
"通过接口服务查询天气:@天气 北京市",
"",
int(weather_api["id"]),
"city",
1,
),
)
if music_api:
conn.execute(
"""
INSERT OR IGNORE INTO digital_employees
(name, alias, category, description, ai_prompt, api_endpoint_id, api_param_name, is_active, update_at)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, current_timestamp)
""",
(
"音乐",
"音乐",
"NORMAL",
"随机音乐卡片:@音乐",
"",
int(music_api["id"]),
"",
1,
),
)
# 注入默认的百度新闻采集源
baidu_source = conn.execute("SELECT id FROM spy_sources WHERE name='百度新闻'").fetchone()
if not baidu_source:
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
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Cookie: BIDUPSID=5CFCC8D701BF7571598CE9F66EE8E6B5; PSTM=1775407070; BD_UPN=1a314753; BAIDUID=5CFCC8D701BF75717E6B8B51D00D380F:SL=0:NR=10:FG=1;
Host: www.baidu.com
Pragma: no-cache
Referer: https://news.baidu.com/
Upgrade-Insecure-Requests: 1
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"""
conn.execute(
"INSERT INTO spy_sources (name, entry_url, request_headers, is_active) VALUES (?, ?, ?, ?)",
("百度新闻", "https://www.baidu.com/s?rtt=1&bsst=1&cl=2&tn=news&rsv_dl=ns_pc&word={关键字}&pn={分页步进}", headers_str, 1)
)
app = make_app()
server = HTTPServer(app)
server.bind(10088)
# 自动CPU核心数
server.start()
try:
print("====== Server 启动成功 ======== 端口:10088 ======", flush=True)
except OSError:
pass
tornado.ioloop.IOLoop.current().start()