diff --git a/.gitignore b/.gitignore index 8c4797d..8b2e10a 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ Thumbs.db # Claude .claude/ + +# PPTist 源码树(pptist-deploy 期间 git clone 下来的 323M 依赖,含 node_modules + .git) +scripts/pptist-deploy/PPTist/ diff --git a/backend/start_local.sh b/backend/start_local.sh deleted file mode 100755 index ad79170..0000000 --- a/backend/start_local.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/bin/bash -# 启动 Java 后端。profile=yj。 -# MySQL 33306 / Redis 等统一见同目录 application-local.yml(与 scripts/backend-restart.sh 一致,勿再写 -D 数据源以免和 yml 冲突)。 - -set -euo pipefail - -export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 -export PATH="$JAVA_HOME/bin:$PATH" - -PROJECT_DIR=/opt/download/oss_files/gangyan-deploy/gangyan/backend -JAR_FILE="$PROJECT_DIR/chat_web_yj.jar" -LOCAL_CFG="$PROJECT_DIR/application-local.yml" - -if [[ ! -f "$JAR_FILE" ]]; then - echo "Error: JAR file not found at $JAR_FILE" - exit 1 -fi - -pkill -f "chat_web_yj.jar" 2>/dev/null || true -sleep 2 - -# 日志目录(logback 写 /opt/apps/...);非 root 启动时需可写 -mkdir -p "$PROJECT_DIR/logs" /opt/apps/logs/chat-server-backend-cast 2>/dev/null || true - -LOG_OUT="$PROJECT_DIR/nohup.out" -if ! ( umask 022; : >>"$LOG_OUT" ) 2>/dev/null; then - LOG_OUT="/tmp/chat_web_yj_nohup.log" -fi - -EXTRA_JAVA=() -[[ -f "$LOCAL_CFG" ]] && EXTRA_JAVA=(-Dspring.config.additional-location="file:${LOCAL_CFG}") - -nohup java \ - -Djava.net.useSystemProxies=false \ - -Dhttp.nonProxyHosts="localhost|127.*|[::1]|*.local" \ - -Xms512m \ - -Xmx2048m \ - "${EXTRA_JAVA[@]}" \ - -Dspring.profiles.active=yj \ - -jar "$JAR_FILE" >>"$LOG_OUT" 2>&1 & - -sleep 10 - -if pgrep -f "chat_web_yj.jar" > /dev/null; then - echo "OK PID $(pgrep -n -f 'java .*chat_web_yj\.jar' | head -1) log:$LOG_OUT" -else - tail -50 "$LOG_OUT" 2>/dev/null || true - exit 1 -fi diff --git a/backend/startup.sh b/backend/startup.sh deleted file mode 100755 index 46bd184..0000000 --- a/backend/startup.sh +++ /dev/null @@ -1 +0,0 @@ -nohup /home/gc/gangyan/backend/jdk1.8.0_161/bin/java -jar -Xms8g -Xmx8g -DserverUrlPrefix=http://localhost:3000 -DserverFrontUrlPrefix=/chat_web -Dspring.profiles.active=yj -Dspring.redis.database=3 -Dspring.redis.password=Redis_897653 -Dchat.modelName=zhipu-api /home/gc/gangyan/backend/chat_web_yj.jar > /home/gc/gangyan/backend/nohup.out 2>&1 & diff --git a/backend/stop.sh b/backend/stop.sh deleted file mode 100755 index f15e1aa..0000000 --- a/backend/stop.sh +++ /dev/null @@ -1 +0,0 @@ -ps -ef|grep chat_web_yj | grep -v grep | awk '{print $2}' | xargs kill -9 diff --git a/backend/application-local.yml b/chat_web_backend/application-local.yml.archived similarity index 85% rename from backend/application-local.yml rename to chat_web_backend/application-local.yml.archived index ac29373..967fc25 100644 --- a/backend/application-local.yml +++ b/chat_web_backend/application-local.yml.archived @@ -1,3 +1,7 @@ +# 原始位置:/opt/download/oss_files/gangyan-deploy/gangyan/backend/application-local.yml +# 归档时间:2026-04-20 +# 用途:yj profile 的本地覆盖(mysql 33306 Docker 映射),启动时通过 -Dspring.config.additional-location 叠加 + # 覆盖 jar 内过期的 application-yj.yml(当前 jar 仍含远程库 123.57.146.97,不配本会长时间卡 Druid)。 # 完整 spring.redis + spring.datasource 与源码 application-yj.yml 对齐(本地 Docker 33306)。 diff --git a/chat_web_backend/start.sh b/chat_web_backend/start.sh deleted file mode 100755 index 14a5490..0000000 --- a/chat_web_backend/start.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -# Java后端启动脚本 - -# 使用 Java 11 运行(兼容性更好) -export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 -export PATH=$JAVA_HOME/bin:$PATH - -cd "$(dirname "$0")" - -echo "==========================================" -echo "启动 Chat Web Backend" -echo "==========================================" -echo "" -echo "Java版本:" -java -version -echo "" -echo "启动端口: 8099" -echo "访问地址: http://localhost:8099/chat_web_backend" -echo "" -echo "按 Ctrl+C 停止服务" -echo "==========================================" -echo "" - -# 启动应用 -nohup java -jar target/chat_web_backend.jar --spring.profiles.active=dev > ./nohup.out 2>&1 & diff --git a/chat_web_backend/test_mysql.sh b/chat_web_backend/test_mysql.sh deleted file mode 100755 index 5e601d1..0000000 --- a/chat_web_backend/test_mysql.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -echo "测试MySQL连接..." -echo "密码: 1234567890" -mysql -uroot -p1234567890 -h127.0.0.1 -e "SHOW DATABASES;" 2>&1 | head -20 diff --git a/langchain-chat/restart.sh b/langchain-chat/restart.sh deleted file mode 100755 index 018103b..0000000 --- a/langchain-chat/restart.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash - -# 确保使用 bash 运行 -if [ -z "$BASH_VERSION" ]; then - exec bash "$0" "$@" -fi - -# 颜色定义 -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# 打印带颜色的消息 -print_yellow() { printf "${YELLOW}%s${NC}\n" "$1"; } -print_green() { printf "${GREEN}%s${NC}\n" "$1"; } -print_red() { printf "${RED}%s${NC}\n" "$1"; } - -print_yellow "=== 停止 7861 和 8501 端口服务 ===" - -for port in 7861 8501; do - pids=$(lsof -t -i:"$port" 2>/dev/null) - if [ -n "$pids" ]; then - print_yellow "正在停止端口 $port 的进程: $pids" - kill -9 $pids 2>/dev/null - print_green "端口 $port 已停止" - else - echo "端口 $port 无运行中的服务" - fi -done - -# 也停止所有 startup.py 进程 -pids=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}') -if [ -n "$pids" ]; then - print_yellow "正在停止 startup.py 进程: $pids" - kill -9 $pids 2>/dev/null - print_green "startup.py 进程已停止" -fi - -echo "" -print_yellow "=== 启动服务 ===" - -cd /home/gc/gangyan/langchain-chat - -# 初始化 conda(确保 PATH 中包含 conda 路径) -CONDA_INIT="/root/miniconda3/etc/profile.d/conda.sh" -if [ -f "$CONDA_INIT" ]; then - # 初始化 conda,将 conda 路径添加到 PATH - . "$CONDA_INIT" 2>/dev/null || source "$CONDA_INIT" 2>/dev/null -fi - -# 查找 conda 可执行文件(按优先级) -if command -v conda &> /dev/null; then - CONDA_EXE="conda" -elif [ -f "/root/miniconda3/bin/conda" ]; then - CONDA_EXE="/root/miniconda3/bin/conda" -elif [ -f "/root/miniconda3/condabin/conda" ]; then - CONDA_EXE="/root/miniconda3/condabin/conda" -else - print_red "错误: 未找到 conda 命令" - print_red "请检查 conda 是否已安装: /root/miniconda3" - exit 1 -fi - -# 使用 conda 环境启动 -print_yellow "使用环境: gangyan" -print_yellow "日志文件: nohup.out" -print_yellow "Conda路径: $CONDA_EXE" - -# 获取 python 的完整路径 -PYTHON_EXE="/root/miniconda3/envs/gangyan/bin/python" -if [ ! -f "$PYTHON_EXE" ]; then - # 尝试通过 conda run 获取路径 - PYTHON_EXE="$($CONDA_EXE run -n gangyan which python 2>/dev/null)" - if [ -z "$PYTHON_EXE" ] || [ ! -f "$PYTHON_EXE" ]; then - print_red "错误: 无法找到 python 可执行文件" - print_red "请检查 conda 环境 gangyan 是否已安装" - exit 1 - fi -fi - -print_yellow "Python路径: $PYTHON_EXE" - -# 直接使用 python 完整路径启动(不依赖 conda activate,更可靠) -# 设置 PYTHONPATH 确保能正确导入模块 -export PYTHONPATH="/home/gc/gangyan/langchain-chat:$PYTHONPATH" -cd /home/gc/gangyan/langchain-chat -nohup "$PYTHON_EXE" startup.py -a >> nohup.out 2>&1 & -PID=$! - -print_green "服务已启动,PID: $PID" -print_yellow "日志文件: /home/gc/gangyan/langchain-chat/nohup.out" -print_yellow "查看日志: tail -f nohup.out" - -# 等待几秒后显示日志 -sleep 2 -echo "" -print_yellow "=== 最近日志 ===" -tail -20 nohup.out diff --git a/langchain-chat/server/agent/tools/duckduckgo_search.py b/langchain-chat/server/agent/tools/duckduckgo_search.py index a5c2914..cabb36a 100644 --- a/langchain-chat/server/agent/tools/duckduckgo_search.py +++ b/langchain-chat/server/agent/tools/duckduckgo_search.py @@ -1,186 +1,148 @@ import asyncio import re -import aiohttp import json import logging +import requests from pydantic import BaseModel, Field from server.chat import utils -# 配置日志记录器 logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s') logger = logging.getLogger(__name__) -async def duckduckgo_search_iter(query: str, uuid: str = "",time: str = "", resource_type: str = None, limit: int = 3): - # 定义三个API的URL - text_url = 'http://43.251.225.121/inspur/search_text' - video_url = 'http://43.251.225.121/inspur/search_video' - news_url = 'http://43.251.225.121/inspur/search_new' +# 新接口:内网 searxng 服务(原 43.251.225.121 已下线) +# aiohttp 与该 searxng 配合会 30s 超时(疑似 header/UA 被拦),所以改用 requests。 +SEARXNG_URL = 'http://118.196.92.255/searxng/search' +SEARXNG_HEADERS = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) gangyan-langchain'} - payload = { - "query": query, - "time": time - } - async def fetch(session, url, json_payload,limit): - logger.info(f"从 {url} 获取数据,请求参数: {json_payload}") - try: - json_payload["limit"] = limit - async with session.post(url, json=json_payload) as response: - if response.status != 200: - logger.error(f"向 {url} 请求失败,状态码 {response.status}") - data = await response.json() - logger.info(f"从 {url} 获取的资料数: {len(data) if isinstance(data, list) else '未知'}") - return data - except Exception as e: - logger.error(f"获取 {url} 数据时发生错误: {e}") +def _searxng_results_to_items(results, mapping, limit): + """把 searxng 统一的 {url,title,content} 映射成老接口期望的字段格式""" + out = [] + for r in results[:limit]: + title = r.get('title', '') or '' + url = r.get('url', '') or '' + content = r.get('content', '') or '' + item = {} + for dst_key, src in mapping.items(): + if src == 'title': + item[dst_key] = title + elif src == 'url': + item[dst_key] = url + elif src == 'content': + item[dst_key] = content + out.append(item) + return out + + +def _sync_fetch(params, limit_n, kind): + logger.info(f"searxng 请求 {kind}: params={params}") + try: + r = requests.get(SEARXNG_URL, params=params, headers=SEARXNG_HEADERS, timeout=15) + if r.status_code != 200: + logger.error(f"searxng {kind} HTTP {r.status_code}") return [] + data = r.json() + results = data.get('results', []) if isinstance(data, dict) else [] + logger.info(f"searxng {kind} 条数: {len(results)}") + if kind == 'text': + return _searxng_results_to_items(results, {'title': 'title', 'href': 'url', 'body': 'content'}, limit_n) + if kind == 'video': + return _searxng_results_to_items(results, {'title': 'title', 'content': 'url', 'description': 'content'}, limit_n) + if kind == 'news': + return _searxng_results_to_items(results, {'title': 'title', 'url': 'url', 'body': 'content'}, limit_n) + return [] + except Exception as e: + logger.error(f"searxng {kind} 请求异常: {type(e).__name__}: {e}") + return [] - # 根据 resource_type 确定要请求的 API - # 默认并发请求三个API - # 视频只请求 video_url - # 新闻只请求 news_url - # 其他类型只请求 text_url - async with aiohttp.ClientSession() as session: - logger.info("发起请求duckduckgo...") - n = limit % 3 - limit1 = 0 - limit2 = 0 - limit3 = 0 - match n: - case 0: - limit1 = limit//3 - limit2 = limit1 - limit3 = limit1 - case 1: - limit1 = limit//3 +1 - limit2 = limit//3 - limit3 = limit2 - case 2: - limit1 = limit//3 +1 - limit2 = limit1 - limit2 = limit +async def duckduckgo_search_iter(query: str, uuid: str = "", time: str = "", resource_type: str = None, limit: int = 3): + logger.info("发起 searxng 搜索请求...") - if resource_type is None or not resource_type == 'video': - text_task = asyncio.create_task(fetch(session, text_url, payload,limit1)) - video_task = asyncio.create_task(fetch(session, video_url, payload, limit3)) - news_task = asyncio.create_task(fetch(session, news_url, payload, limit2)) - text_result, video_result, news_result = await asyncio.gather(text_task, video_task, news_task) - logger.info("合并结果...") - - logger.info("合并结果完成") - combined_result = { - "text": text_result, - "video": video_result, - "news": news_result - } + # 三类按 limit 平均分配 + n = limit % 3 + if n == 0: + limit1 = limit2 = limit3 = limit // 3 + elif n == 1: + limit1 = limit // 3 + 1 + limit2 = limit3 = limit // 3 + else: + limit1 = limit2 = limit // 3 + 1 + limit3 = limit // 3 - else: - video_result = await fetch(session, video_url, payload, limit) - combined_result = { - "video": video_result - } - del limit1,limit2,limit3 - # elif resource_type == 'news': - # news_result = await fetch(session, news_url, payload) - # combined_result = { - # "news": news_result - # } + if resource_type is None or resource_type != 'video': + text_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'general'}, limit1, 'text') + news_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'news'}, limit2, 'news') + video_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'videos'}, limit3, 'video') + text_result, news_result, video_result = await asyncio.gather(text_task, news_task, video_task) + combined_result = { + "text": text_result, + "video": video_result, + "news": news_result, + } + else: + video_result = await asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'videos'}, limit, 'video') + combined_result = {"video": video_result} - # else: # 其他类型 - # text_result = await fetch(session, text_url, payload) - # combined_result = { - # "text": text_result - # } + logger.info("searxng 请求已完成") - logger.info("请求已完成") - res = [] - source = [] - info = utils.get_shared_variable(uuid) - index = info["num"] - if "text" in combined_result: - for item in combined_result["text"]: - index += 1 - res.append(f'资料[{index}] 资料标题{item["title"]}({item["href"]}) 资料内容为: {item["body"]}') - source.append(f'资料[{index}] [{item["title"]}]({item["href"]})') - if "video" in combined_result: - for item in combined_result["video"]: - index += 1 - res.append(f'资料[{index}] 视频标题[{item["title"]}]({item["content"]}) 视频内容为: {item["description"]}') - source.append(f'视频资料[{index}] [{item["title"]}]({item["content"]})') - if "news" in combined_result: - for item in combined_result["news"]: - index += 1 - res.append(f'资料[{index}] 新闻标题[{item["title"]}]({item["url"]}) 新闻内容为: {item["body"]}') - source.append(f'资料[{index}] [{item["title"]}]({item["url"]})') - info["source_docs"].extend(source) - utils.set_shared_variable(uuid, info) - return res,source + res = [] + source = [] + info = utils.get_shared_variable(uuid) + index = info["num"] + if "text" in combined_result: + for item in combined_result["text"]: + index += 1 + res.append(f'资料[{index}] 资料标题{item["title"]}({item["href"]}) 资料内容为: {item["body"]}') + source.append(f'资料[{index}] [{item["title"]}]({item["href"]})') + if "video" in combined_result: + for item in combined_result["video"]: + index += 1 + res.append(f'资料[{index}] 视频标题[{item["title"]}]({item["content"]}) 视频内容为: {item["description"]}') + source.append(f'视频资料[{index}] [{item["title"]}]({item["content"]})') + if "news" in combined_result: + for item in combined_result["news"]: + index += 1 + res.append(f'资料[{index}] 新闻标题[{item["title"]}]({item["url"]}) 新闻内容为: {item["body"]}') + source.append(f'资料[{index}] [{item["title"]}]({item["url"]})') + info["source_docs"].extend(source) + utils.set_shared_variable(uuid, info) + return res, source def duckduckgo_search(query: str, time: str = "", resource_type: str = None): - logger.info(f"模型输入: {query}") - # 对传入的 query 字段进行解析 - # 判断 query 是否包含 "}{" - # if "}{" in query: - # # 将 query 分割为两个JSON字符串 - # split_index = query.find("}{") - # json_part1 = query[:split_index+1] - # json_part2 = query[split_index+1:] - - # try: - # obj1 = json.loads(json_part1) - # obj2 = json.loads(json_part2) - - # # 提取 query, resource_type, time, uuid - # parsed_query = obj1.get("query", "") - # parsed_resource_type = obj1.get("resource_type", None) - # parsed_time = obj1.get("time", time) # 如obj1未包含time则使用传入的默认值 - # parsed_uuid = obj2.get("uuid", "") + logger.info(f"模型输入: {query}") matches = re.findall(r'\{.*?\}', query) - if len(matches)>=2: + if len(matches) >= 2: query = matches[0] else: return "<关键指令>不需要再调用该工具了" + parsed_uuid = "" + parsed_limit = 3 try: - obj1= json.loads(query) + obj1 = json.loads(query) parsed_query = obj1.get("query", "") parsed_limit = obj1.get("limit", 3) parsed_resource_type = obj1.get("resource_type", None) - parsed_time = obj1.get("time", time) # 如obj1未包含time则使用传入的默认值 + parsed_time = obj1.get("time", time) parsed_uuid = json.loads(matches[1])["uuid"] - # 将解析到的值覆盖原有的参数 query = parsed_query if parsed_query else query resource_type = parsed_resource_type if parsed_resource_type else resource_type time = parsed_time if parsed_time else time - logger.info(f"解析完成,query: {query}, uuid: {parsed_uuid}, time: {time}, resource_type: {resource_type}, parsed_limit: {parsed_limit}") except json.JSONDecodeError as e: logger.error(f"解析JSON出错: {e}") - # 在同步环境中运行异步函数 combined_result = asyncio.run(duckduckgo_search_iter(query, parsed_uuid, time, resource_type, parsed_limit)) - # 以标准json格式输出 logger.info("返回JSON格式的结果给到模型...") return combined_result + + class DuckduckgoInput(BaseModel): location: str = Field(description="网络搜索查询") + if __name__ == "__main__": - # 测试调用 - # 1. 默认请求三个API - # result_default = duckduckgo_search("粉末冶金", "m", "default") - # print("duckduckgo输出(默认):\n", result_default) - - # # 2. 只请求视频 - # result_video = duckduckgo_search("粉末冶金", "m", "video") - # print("duckduckgo输出(视频):\n", result_video) - - # # 3. 只请求新闻 - # result_news = duckduckgo_search("粉末冶金", "m", "news") - # print("duckduckgo输出(新闻):\n", result_news) - - # 4. 其它类型只请求文本 - result_other = duckduckgo_search("粉末冶金", "m", "other") - print("duckduckgo输出(其他):\n", result_other) + result_other = duckduckgo_search('{"query":"粉末冶金","limit":3}{"uuid":"test-uuid"}', "m", "other") + print("searxng输出(其他):\n", result_other) diff --git a/langchain-chat/server/chat/ZhipuSearchAPI.py b/langchain-chat/server/chat/ZhipuSearchAPI.py index 6c5e7f0..d9218af 100644 --- a/langchain-chat/server/chat/ZhipuSearchAPI.py +++ b/langchain-chat/server/chat/ZhipuSearchAPI.py @@ -70,7 +70,7 @@ class ZhipuSearchAPIWrapper: ) logging.info(f"Zhipu检索内容:{search_query}") - url = "http://ywk3hvt4d:01Jp2V1tR9PdTsYSz919779Rb9_@134.122.191.214/search" + url = "http://118.196.92.255/searxng/search" engines = "duckduckgo,bing" data = { "format":"json", diff --git a/langchain-chat/shutdown_all.sh b/langchain-chat/shutdown_all.sh deleted file mode 100644 index 8617669..0000000 --- a/langchain-chat/shutdown_all.sh +++ /dev/null @@ -1,2 +0,0 @@ -# mac设备上的grep命令可能不支持grep -P选项,请使用Homebrew安装;或使用ggrep命令 -ps -eo pid,user,cmd|grep -P 'server/api.py|webui.py|fastchat.serve|langchain_chat'|grep -v grep|awk '{print $1}'|xargs kill -9 \ No newline at end of file diff --git a/langchain-chat/start.sh b/langchain-chat/start.sh deleted file mode 100755 index aeddb97..0000000 --- a/langchain-chat/start.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -# 启动 Python 后端服务脚本(使用 nohup) - -# 颜色定义 -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# 检查是否已有进程在运行 -EXISTING=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}') -if [ -n "$EXISTING" ]; then - echo -e "${YELLOW}警告: 检测到已有 Python 后端进程在运行 (PID: $EXISTING)${NC}" - read -p "是否先停止现有进程? (y/n): " -n 1 -r - echo - if [[ $REPLY =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}正在停止现有进程...${NC}" - kill $EXISTING 2>/dev/null - sleep 2 - # 如果还在运行,强制终止 - if ps -p $EXISTING > /dev/null 2>&1; then - kill -9 $EXISTING 2>/dev/null - fi - echo -e "${GREEN}已停止现有进程${NC}" - else - echo -e "${RED}请先停止现有进程或使用 stop.sh${NC}" - exit 1 - fi -fi - -# 获取脚本所在目录 -SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -cd "$SCRIPT_DIR" - -# 设置 PDF 转换服务环境变量(确保与 pdf-convert-service 一致) -export PDF_CONVERT_KB_ROOT="${PDF_CONVERT_KB_ROOT:-$SCRIPT_DIR/knowledge_base}" - -# 检查 conda 环境 -if ! command -v conda &> /dev/null; then - echo -e "${RED}错误: 未找到 conda 命令${NC}" - exit 1 -fi - -# 激活 conda 环境并启动 -echo -e "${YELLOW}正在启动 Python 后端...${NC}" -echo -e "${YELLOW}使用环境: gangyan${NC}" -echo -e "${YELLOW}日志文件: nohup.out${NC}" - -# 使用 nohup 启动 -nohup conda run -n gangyan python startup.py -a > nohup.out 2>&1 & -PID=$! - -echo -e "${GREEN}后端已启动,PID: $PID${NC}" -echo -e "${YELLOW}查看日志: tail -f nohup.out${NC}" -echo -e "${YELLOW}停止服务: ./stop.sh${NC}" - -# 等待几秒后显示日志 -sleep 2 -echo -e "\n${YELLOW}=== 最近日志 ===${NC}" -tail -20 nohup.out diff --git a/langchain-chat/stop.sh b/langchain-chat/stop.sh deleted file mode 100755 index 43eda5e..0000000 --- a/langchain-chat/stop.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/bash -# 停止 Python 后端服务脚本 - -# 默认端口(从 server_config.py 中获取) -DEFAULT_PORT=7861 - -# 颜色定义 -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -NC='\033[0m' # No Color - -echo -e "${YELLOW}正在查找运行中的 Python 后端进程...${NC}" - -# 方法1: 通过进程名查找 -PIDS=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}') - -# 方法2: 如果方法1没找到,通过端口查找 -if [ -z "$PIDS" ]; then - echo -e "${YELLOW}未找到 startup.py 进程,尝试通过端口 ${DEFAULT_PORT} 查找...${NC}" - PIDS=$(lsof -ti:${DEFAULT_PORT} 2>/dev/null) -fi - -if [ -z "$PIDS" ]; then - echo -e "${RED}未找到运行中的 Python 后端进程${NC}" - exit 0 -fi - -echo -e "${GREEN}找到以下进程:${NC}" -ps aux | grep "[p]ython.*startup.py -a" | grep -v grep -if [ -n "$PIDS" ]; then - echo -e "${YELLOW}进程 PID: $PIDS${NC}" -fi - -# 询问是否确认停止 -read -p "是否停止这些进程? (y/n): " -n 1 -r -echo -if [[ ! $REPLY =~ ^[Yy]$ ]]; then - echo -e "${YELLOW}已取消${NC}" - exit 0 -fi - -# 停止进程 -for PID in $PIDS; do - if [ -n "$PID" ]; then - echo -e "${YELLOW}正在停止进程 $PID...${NC}" - kill $PID 2>/dev/null - if [ $? -eq 0 ]; then - echo -e "${GREEN}进程 $PID 已发送停止信号${NC}" - else - echo -e "${RED}无法停止进程 $PID${NC}" - fi - fi -done - -# 等待进程结束 -echo -e "${YELLOW}等待进程结束...${NC}" -sleep 3 - -# 检查是否还有进程在运行 -REMAINING=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}') -if [ -n "$REMAINING" ]; then - echo -e "${YELLOW}仍有进程在运行,强制终止...${NC}" - for PID in $REMAINING; do - kill -9 $PID 2>/dev/null - echo -e "${GREEN}已强制终止进程 $PID${NC}" - done -fi - -echo -e "${GREEN}所有进程已停止${NC}" diff --git a/langchain-chat/stop_quick.sh b/langchain-chat/stop_quick.sh deleted file mode 100755 index 033024b..0000000 --- a/langchain-chat/stop_quick.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -# 快速停止 Python 后端服务脚本(无需确认) - -# 默认端口 -DEFAULT_PORT=7861 - -# 颜色定义 -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -RED='\033[0;31m' -NC='\033[0m' - -echo -e "${YELLOW}正在查找并停止 Python 后端进程...${NC}" - -# 方法1: 通过进程名查找 -PIDS=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}') - -# 方法2: 如果方法1没找到,通过端口查找 -if [ -z "$PIDS" ]; then - PIDS=$(lsof -ti:${DEFAULT_PORT} 2>/dev/null) -fi - -if [ -z "$PIDS" ]; then - echo -e "${RED}未找到运行中的 Python 后端进程${NC}" - exit 0 -fi - -# 显示找到的进程 -echo -e "${GREEN}找到以下进程:${NC}" -ps aux | grep "[p]ython.*startup.py -a" | grep -v grep - -# 停止进程 -for PID in $PIDS; do - if [ -n "$PID" ]; then - echo -e "${YELLOW}正在停止进程 $PID...${NC}" - kill $PID 2>/dev/null - fi -done - -# 等待进程结束 -sleep 2 - -# 检查是否还有进程在运行,如果有则强制终止 -REMAINING=$(ps aux | grep "[p]ython.*startup.py -a" | awk '{print $2}') -if [ -n "$REMAINING" ]; then - echo -e "${YELLOW}强制终止剩余进程...${NC}" - for PID in $REMAINING; do - kill -9 $PID 2>/dev/null - echo -e "${GREEN}已强制终止进程 $PID${NC}" - done -fi - -echo -e "${GREEN}✓ 所有进程已停止${NC}" diff --git a/scripts/backend-restart.sh b/scripts/backend-restart.sh index ee57c74..eb81ad6 100755 --- a/scripts/backend-restart.sh +++ b/scripts/backend-restart.sh @@ -1,13 +1,12 @@ #!/usr/bin/env bash # 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍 [ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"} -# 重启 Java 后端 chat_web_yj.jar(与 start_all.sh 一致) +# 重启 Java 后端 chat_web_backend.jar(源码 mvn 编译产物,profile=dev) # 日志:gangyan/logs/backend.log -# 勿在此加 -Dspring.datasource*:与 application-local.yml 打架易连错库。 -# 若 jar 未重打、内嵌 yml 仍是远程库,必须依赖 backend/application-local.yml(含完整 spring 数据源)。 +# 注:backend/chat_web_yj.jar(同一 jar 的改名副本)及其 application-local.yml 覆盖配置已于 2026-04-20 归档删除。 set -u source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh" -# 默认 backend.log;若曾被 root 创建导致当前用户不可写,则改用同目录 backend-<用户>.log(仍在 gangyan/logs 下) +# 默认 backend.log;若曾被 root 创建导致当前用户不可写,则改用同目录 backend-<用户>.log LOG_FILE="$LOG_DIR/backend.log" if ! ( umask 022; : >>"$LOG_FILE" ) 2>/dev/null; then ALT="$LOG_DIR/backend-$(id -un).log" @@ -25,34 +24,41 @@ fi export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH="$JAVA_HOME/bin:$PATH" -log_tee "======== 停止 chat_web_yj.jar ========" -pkill -f "chat_web_yj.jar" 2>/dev/null && log_tee "已发送停止信号" || log_tee "未找到运行中的进程" -sleep 2 +BACKEND_DIR="$GANGYAN_ROOT/chat_web_backend" +JAR_REL="target/chat_web_backend.jar" -log_tee "======== 启动 chat_web_yj.jar ========" -cd "$GANGYAN_ROOT/backend" -LOCAL_CFG="$GANGYAN_ROOT/backend/application-local.yml" -EXTRA_JAVA_ARGS=() -if [ -f "$LOCAL_CFG" ]; then - log_tee "加载本地配置: $LOCAL_CFG" - EXTRA_JAVA_ARGS=(-Dspring.config.additional-location="file:${LOCAL_CFG}") +if [ ! -f "$BACKEND_DIR/$JAR_REL" ]; then + log_tee "错误: 未找到 $BACKEND_DIR/$JAR_REL。先执行 chat_web_backend/compile.sh 编译。" + exit 1 fi + +log_tee "======== 停止 chat_web_backend.jar ========" +pkill -f "target/chat_web_backend.jar" 2>/dev/null && log_tee "已发送停止信号" || log_tee "未找到运行中的进程" +sleep 2 +# 端口兜底(非 hawei 启的旧进程 lsof 可能看不到,用 ss 探测) +if ss -tln 2>/dev/null | grep -qE ':8099[[:space:]]'; then + log_tee "8099 仍被占用,再等 3 秒..." + sleep 3 +fi + +log_tee "======== 启动 chat_web_backend.jar (profile=dev) ========" +cd "$BACKEND_DIR" nohup java -jar \ -Xms512m -Xmx2048m \ - "${EXTRA_JAVA_ARGS[@]}" \ - -Dspring.profiles.active=yj \ - chat_web_yj.jar >> "$LOG_FILE" 2>&1 & + "$JAR_REL" \ + --spring.profiles.active=dev >> "$LOG_FILE" 2>&1 & STARTED_PID=$! log_tee "已后台启动,PID=$STARTED_PID" log_tee "Java 标准输出/错误写入: $LOG_FILE" sleep 3 if kill -0 "$STARTED_PID" 2>/dev/null; then - if command -v ss >/dev/null 2>&1 && ss -tln 2>/dev/null | grep -q ':8099'; then - log_tee "自检: 进程存活且 8099 已监听(后端已就绪或即将就绪)" + if ss -tln 2>/dev/null | grep -qE ':8099[[:space:]]'; then + log_tee "自检: 进程存活且 8099 已监听" else - log_tee "自检: 进程存活,但 8099 尚未监听(可能仍在启动;若 10 秒后仍无,请 tail 上面日志路径)" + log_tee "自检: 进程存活,但 8099 尚未监听(可能仍在启动;10 秒后若仍无请 tail $LOG_FILE)" fi else - log_tee "自检失败: 进程已退出。请查看日志末尾:" + log_tee "自检失败: 进程已退出。最近日志:" tail -n 40 "$LOG_FILE" 2>/dev/null | while IFS= read -r line || [ -n "$line" ]; do log_line "$line"; done + exit 1 fi diff --git a/scripts/log-trim-daemon.sh b/scripts/log-trim-daemon.sh index d4757e1..4b0790f 100755 --- a/scripts/log-trim-daemon.sh +++ b/scripts/log-trim-daemon.sh @@ -31,7 +31,8 @@ fi MAX_BYTES=$((MAX_MB * 1024 * 1024)) TMP="${FILE}.trimtmp.$$" -LOCK="${FILE}.trimlock" +# LOCK 放 /tmp 并按用户命名,避免不同用户启动时 .trimlock owner 错配导致 Permission denied +LOCK="/tmp/gangyan-trim-$(basename "$FILE").$(id -un).lock" mkdir -p "$(dirname "$FILE")" touch "$FILE" 2>/dev/null || true diff --git a/scripts/start-all.sh b/scripts/start-all.sh new file mode 100755 index 0000000..17f0692 --- /dev/null +++ b/scripts/start-all.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍 +[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"} +# 一键启动全部 gangyan 服务:基础设施 -> 后端 -> 前端。 +# 每一步直接 source 已有的 *-restart.sh,保持单一事实来源。 +# 日志:gangyan/logs/start-all.log +set -u +source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh" +LOG_FILE="$LOG_DIR/start-all.log" + +run_step() { + local label="$1" script="$2" + log_tee "━━━━━━━━ ${label} ━━━━━━━━" + if [ ! -x "$SCRIPT_DIR/$script" ]; then + log_tee "跳过: $script 不存在或不可执行" + return 0 + fi + bash "$SCRIPT_DIR/$script" 2>&1 | tee -a "$LOG_FILE" + local rc=${PIPESTATUS[0]} + [ "$rc" -eq 0 ] || log_tee "警告: $script 返回 $rc" + return 0 +} + +port_check() { + local port="$1" name="$2" + if ss -tln 2>/dev/null | grep -qE ":${port}[[:space:]]"; then + log_tee " [OK] ${name} :${port}" + else + log_tee " [!!] ${name} :${port} 未监听" + fi +} + +log_tee "╔══════════════════════════════════════╗" +log_tee "║ gangyan 全量启动 $(date '+%F %H:%M:%S') ║" +log_tee "╚══════════════════════════════════════╝" + +# 1) 基础设施(Docker 容器:MySQL / Redis / Milvus) +run_step "[1/5] MySQL" mysql-restart.sh +run_step "[2/5] Redis" redis-restart.sh +run_step "[3/5] Milvus" milvus-restart.sh + +# 2) 应用层(langchain-chat 会顺带起 pdf-convert-service + log-trim daemon) +run_step "[4/6] langchain-chat + pdf-convert + log-trim" langchain-restart.sh +run_step "[5/6] Java 后端" backend-restart.sh +run_step "[6/6] 前端 vite" frontend-restart.sh + +# 3) 辅助进程状态(excalidraw-ai / pptist-ai 当前以 screen -dmS 管理,非脚本化;本脚本仅做健康检查) +log_tee "━━━━━━━━ 辅助进程检查 ━━━━━━━━" +if pgrep -f 'excalidraw-ai-proxy\.py' >/dev/null; then + log_tee " [OK] excalidraw-ai-proxy.py (screen -dmS aiproxy)" +else + log_tee " [!!] excalidraw-ai-proxy.py 未运行。手动启: screen -dmS aiproxy bash -c 'cd $GANGYAN_ROOT/scripts && /opt/software/miniconda3/envs/langchain-chat/bin/python excalidraw-ai-proxy.py'" +fi +if pgrep -f 'pptist-ai-backend\.py' >/dev/null; then + log_tee " [OK] pptist-ai-backend.py (screen -dmS pptist-ai)" +else + log_tee " [!!] pptist-ai-backend.py 未运行。手动启: screen -dmS pptist-ai bash -c 'cd $GANGYAN_ROOT/scripts && /opt/software/miniconda3/envs/langchain-chat/bin/python pptist-ai-backend.py'" +fi + +# 4) 系统级 tools-nginx(systemd 管理,不在本脚本中重启) +if systemctl is-active --quiet nginx 2>/dev/null; then + log_tee " [OK] nginx (systemd, tools-nginx.conf -> :18000)" +else + log_tee " [!!] nginx 未运行。sudo systemctl start nginx" +fi + +# 5) 端口自检 +log_tee "━━━━━━━━ 端口自检(等 5 秒) ━━━━━━━━" +sleep 5 +port_check 3306 "MySQL (3306 映射)" +port_check 33306 "MySQL (33306 映射)" +port_check 6379 "Redis" +port_check 19530 "Milvus gRPC" +port_check 9091 "Milvus HTTP" +port_check 7861 "langchain-chat API" +port_check 6006 "pdf-convert-service" +port_check 8099 "Java 后端" +port_check 3000 "vite 前端" +port_check 18000 "tools-nginx (工具集合)" + +log_tee "" +log_tee "完成。完整日志: $LOG_FILE" +log_tee "访问: http://:3000/metalinfo" diff --git a/start_all.sh b/start_all.sh deleted file mode 100644 index 735bddb..0000000 --- a/start_all.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash -# 启动 gangyan 项目所有服务(与 /opt/start_all.sh 同步维护时可覆盖过去) - -echo "启动 gangyan 项目服务..." - -# 1. 启动 Docker 服务 -echo "[1/4] 启动 Docker 服务..." -cd /opt/download/oss_files/gangyan-deploy/gangyan/milvus && docker compose up -d -cd /opt/download/oss_files/gangyan-deploy/gangyan/mysql/mysql-8.4.4 && docker compose up -d -docker start redis-server 2>/dev/null || docker run -d --name redis-server -p 6379:6379 redis:7-alpine - -# 2. 启动 Java 后端(MySQL 33306 / Redis 见 jar 内 application-yj.yml;勿在此加 -Dspring.datasource* 以免覆盖端口) -echo "[2/4] 启动 Java 后端..." -export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 -export PATH=$JAVA_HOME/bin:$PATH -pkill -f "chat_web_yj.jar" 2>/dev/null -sleep 2 -cd /opt/download/oss_files/gangyan-deploy/gangyan/backend -LOCAL_CFG="/opt/download/oss_files/gangyan-deploy/gangyan/backend/application-local.yml" -EXTRA_JAVA=() -[ -f "$LOCAL_CFG" ] && EXTRA_JAVA=(-Dspring.config.additional-location="file:${LOCAL_CFG}") -nohup java -jar \ - -Xms512m -Xmx2048m \ - "${EXTRA_JAVA[@]}" \ - -Dspring.profiles.active=yj \ - chat_web_yj.jar > nohup.out 2>&1 & - -# 3. 启动前端 -echo "[3/4] 启动前端..." -if ! pgrep -f "vite" > /dev/null; then - cd /opt/download/oss_files/gangyan-deploy/gangyan/chat_web_front - nohup npm run dev > nohup.out 2>&1 & -fi - -# 4. 启动 langchain-chat(可选) -# --all-api:仅 API(7861)+依赖进程,不启 Streamlit WebUI;-a 需要 pip install streamlit -echo "[4/4] 启动 langchain-chat(可选)..." -read -p "是否启动 langchain-chat? (y/n) " -n 1 -r -echo -if [[ $REPLY =~ ^[Yy]$ ]]; then - source /opt/software/miniconda3/etc/profile.d/conda.sh - conda activate langchain-chat - cd /opt/download/oss_files/gangyan-deploy/gangyan/langchain-chat - export PYTHONPATH="/opt/download/oss_files/gangyan-deploy/gangyan/langchain-chat" - nohup python startup.py --all-api > langchain.log 2>&1 & - echo "langchain-chat 启动中(API :7861),查看日志: tail -f langchain.log" - # PDF 预览依赖的本地转换微服务(:6006,PyMuPDF 抽文本→Markdown) - bash /opt/download/oss_files/gangyan-deploy/gangyan/scripts/pdf-convert-service.sh - echo "pdf-convert-service 已尝试启动(:6006),日志: tail -f /opt/download/oss_files/gangyan-deploy/gangyan/logs/pdf-convert-service.log" -fi - -echo "" -echo "服务启动完成!" -echo " 前端: http://localhost:3000/metalinfo" -echo " 后端: http://localhost:8099/chat_web_backend" -echo " MySQL: localhost:33306 (Docker 映射,勿用 3306 连宿主)" -echo " Redis: localhost:6379" -echo " Milvus: localhost:19530"