[运维] 接入内网 searxng + 清理启动脚本 + 修 log-trim 权限

搜索接口:

- duckduckgo_search.py / ZhipuSearchAPI.py 切换到内网 searxng (原 43.251.225.121 / 134.122.191.214 已失效)

启动脚本清理:

- 删除废弃 backend/ 目录 (与 chat_web_backend/ 编译产物 jar MD5 相同,仅是改名副本)

- 删除 start_all.sh 与 langchain-chat/{start,stop,stop_quick,shutdown_all,restart}.sh (被 scripts/*-restart.sh 覆盖)

- 删除 chat_web_backend/{start,test_mysql}.sh

修复:

- scripts/backend-restart.sh 对齐当前实际在跑的 chat_web_backend.jar (profile=dev)

- scripts/log-trim-daemon.sh 把 LOCK 移到 /tmp 按用户命名,修复非首次用户跑时的 Permission denied

新增:

- scripts/start-all.sh:一键启动入口,串联 mysql/redis/milvus/langchain/backend/frontend,含端口自检

- chat_web_backend/application-local.yml.archived:原 backend/ 下 yj profile 覆盖配置的归档备份

其他:

- .gitignore 忽略 scripts/pptist-deploy/PPTist/ (323M 第三方源码树)
This commit is contained in:
2026-04-20 15:59:11 +08:00
parent 279b104434
commit 0c3a393d04
18 changed files with 222 additions and 585 deletions

3
.gitignore vendored
View File

@@ -57,3 +57,6 @@ Thumbs.db
# Claude # Claude
.claude/ .claude/
# PPTist 源码树pptist-deploy 期间 git clone 下来的 323M 依赖,含 node_modules + .git
scripts/pptist-deploy/PPTist/

View File

@@ -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

View File

@@ -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 &

View File

@@ -1 +0,0 @@
ps -ef|grep chat_web_yj | grep -v grep | awk '{print $2}' | xargs kill -9

View File

@@ -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 # 覆盖 jar 内过期的 application-yj.yml当前 jar 仍含远程库 123.57.146.97,不配本会长时间卡 Druid
# 完整 spring.redis + spring.datasource 与源码 application-yj.yml 对齐(本地 Docker 33306 # 完整 spring.redis + spring.datasource 与源码 application-yj.yml 对齐(本地 Docker 33306

View File

@@ -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 &

View File

@@ -1,4 +0,0 @@
#!/bin/bash
echo "测试MySQL连接..."
echo "密码: 1234567890"
mysql -uroot -p1234567890 -h127.0.0.1 -e "SHOW DATABASES;" 2>&1 | head -20

View File

@@ -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

View File

@@ -1,186 +1,148 @@
import asyncio import asyncio
import re import re
import aiohttp
import json import json
import logging import logging
import requests
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from server.chat import utils from server.chat import utils
# 配置日志记录器
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s') logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def duckduckgo_search_iter(query: str, uuid: str = "",time: str = "", resource_type: str = None, limit: int = 3): # 新接口:内网 searxng 服务(原 43.251.225.121 已下线)
# 定义三个API的URL # aiohttp 与该 searxng 配合会 30s 超时(疑似 header/UA 被拦),所以改用 requests。
text_url = 'http://43.251.225.121/inspur/search_text' SEARXNG_URL = 'http://118.196.92.255/searxng/search'
video_url = 'http://43.251.225.121/inspur/search_video' SEARXNG_HEADERS = {'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) gangyan-langchain'}
news_url = 'http://43.251.225.121/inspur/search_new'
payload = {
"query": query,
"time": time
}
async def fetch(session, url, json_payload,limit): def _searxng_results_to_items(results, mapping, limit):
logger.info(f"{url} 获取数据,请求参数: {json_payload}") """把 searxng 统一的 {url,title,content} 映射成老接口期望的字段格式"""
try: out = []
json_payload["limit"] = limit for r in results[:limit]:
async with session.post(url, json=json_payload) as response: title = r.get('title', '') or ''
if response.status != 200: url = r.get('url', '') or ''
logger.error(f"{url} 请求失败,状态码 {response.status}") content = r.get('content', '') or ''
data = await response.json() item = {}
logger.info(f"{url} 获取的资料数: {len(data) if isinstance(data, list) else '未知'}") for dst_key, src in mapping.items():
return data if src == 'title':
except Exception as e: item[dst_key] = title
logger.error(f"获取 {url} 数据时发生错误: {e}") 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 [] 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 async def duckduckgo_search_iter(query: str, uuid: str = "", time: str = "", resource_type: str = None, limit: int = 3):
limit1 = 0 logger.info("发起 searxng 搜索请求...")
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
if resource_type is None or not resource_type == 'video': # 三类按 limit 平均分配
text_task = asyncio.create_task(fetch(session, text_url, payload,limit1)) n = limit % 3
video_task = asyncio.create_task(fetch(session, video_url, payload, limit3)) if n == 0:
news_task = asyncio.create_task(fetch(session, news_url, payload, limit2)) limit1 = limit2 = limit3 = limit // 3
text_result, video_result, news_result = await asyncio.gather(text_task, video_task, news_task) elif n == 1:
logger.info("合并结果...") limit1 = limit // 3 + 1
limit2 = limit3 = limit // 3
logger.info("合并结果完成") else:
combined_result = { limit1 = limit2 = limit // 3 + 1
"text": text_result, limit3 = limit // 3
"video": video_result,
"news": news_result
}
else: if resource_type is None or resource_type != 'video':
video_result = await fetch(session, video_url, payload, limit) text_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'general'}, limit1, 'text')
combined_result = { news_task = asyncio.to_thread(_sync_fetch, {'q': query, 'format': 'json', 'categories': 'news'}, limit2, 'news')
"video": video_result 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)
del limit1,limit2,limit3 combined_result = {
# elif resource_type == 'news': "text": text_result,
# news_result = await fetch(session, news_url, payload) "video": video_result,
# combined_result = { "news": news_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: # 其他类型 logger.info("searxng 请求已完成")
# text_result = await fetch(session, text_url, payload)
# combined_result = {
# "text": text_result
# }
logger.info("请求已完成") res = []
res = [] source = []
source = [] info = utils.get_shared_variable(uuid)
info = utils.get_shared_variable(uuid) index = info["num"]
index = info["num"] if "text" in combined_result:
if "text" in combined_result: for item in combined_result["text"]:
for item in combined_result["text"]: index += 1
index += 1 res.append(f'资料[{index}] 资料标题{item["title"]}({item["href"]}) 资料内容为: {item["body"]}')
res.append(f'资料[{index}] 资料标题{item["title"]}({item["href"]}) 资料内容为: {item["body"]}') source.append(f'资料[{index}] [{item["title"]}]({item["href"]})')
source.append(f'资料[{index}] [{item["title"]}]({item["href"]})') if "video" in combined_result:
if "video" in combined_result: for item in combined_result["video"]:
for item in combined_result["video"]: index += 1
index += 1 res.append(f'资料[{index}] 视频标题[{item["title"]}]({item["content"]}) 视频内容为: {item["description"]}')
res.append(f'资料[{index}] 视频标题[{item["title"]}]({item["content"]}) 视频内容为: {item["description"]}') source.append(f'视频资料[{index}] [{item["title"]}]({item["content"]})')
source.append(f'视频资料[{index}] [{item["title"]}]({item["content"]})') if "news" in combined_result:
if "news" in combined_result: for item in combined_result["news"]:
for item in combined_result["news"]: index += 1
index += 1 res.append(f'资料[{index}] 新闻标题[{item["title"]}]({item["url"]}) 新闻内容为: {item["body"]}')
res.append(f'资料[{index}] 新闻标题[{item["title"]}]({item["url"]}) 新闻内容为: {item["body"]}') source.append(f'资料[{index}] [{item["title"]}]({item["url"]})')
source.append(f'资料[{index}] [{item["title"]}]({item["url"]})') info["source_docs"].extend(source)
info["source_docs"].extend(source) utils.set_shared_variable(uuid, info)
utils.set_shared_variable(uuid, info) return res, source
return res,source
def duckduckgo_search(query: str, time: str = "", resource_type: str = None): def duckduckgo_search(query: str, time: str = "", resource_type: str = None):
logger.info(f"模型输入: {query}") 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", "")
matches = re.findall(r'\{.*?\}', query) matches = re.findall(r'\{.*?\}', query)
if len(matches)>=2: if len(matches) >= 2:
query = matches[0] query = matches[0]
else: else:
return "<关键指令>不需要再调用该工具了</关键指令>" return "<关键指令>不需要再调用该工具了</关键指令>"
parsed_uuid = ""
parsed_limit = 3
try: try:
obj1= json.loads(query) obj1 = json.loads(query)
parsed_query = obj1.get("query", "") parsed_query = obj1.get("query", "")
parsed_limit = obj1.get("limit", 3) parsed_limit = obj1.get("limit", 3)
parsed_resource_type = obj1.get("resource_type", None) 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"] parsed_uuid = json.loads(matches[1])["uuid"]
# 将解析到的值覆盖原有的参数
query = parsed_query if parsed_query else query query = parsed_query if parsed_query else query
resource_type = parsed_resource_type if parsed_resource_type else resource_type resource_type = parsed_resource_type if parsed_resource_type else resource_type
time = parsed_time if parsed_time else time 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}") logger.info(f"解析完成query: {query}, uuid: {parsed_uuid}, time: {time}, resource_type: {resource_type}, parsed_limit: {parsed_limit}")
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
logger.error(f"解析JSON出错: {e}") logger.error(f"解析JSON出错: {e}")
# 在同步环境中运行异步函数
combined_result = asyncio.run(duckduckgo_search_iter(query, parsed_uuid, time, resource_type, parsed_limit)) combined_result = asyncio.run(duckduckgo_search_iter(query, parsed_uuid, time, resource_type, parsed_limit))
# 以标准json格式输出
logger.info("返回JSON格式的结果给到模型...") logger.info("返回JSON格式的结果给到模型...")
return combined_result return combined_result
class DuckduckgoInput(BaseModel): class DuckduckgoInput(BaseModel):
location: str = Field(description="网络搜索查询") location: str = Field(description="网络搜索查询")
if __name__ == "__main__": if __name__ == "__main__":
# 测试调用 result_other = duckduckgo_search('{"query":"粉末冶金","limit":3}{"uuid":"test-uuid"}', "m", "other")
# 1. 默认请求三个API print("searxng输出(其他):\n", result_other)
# 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)

View File

@@ -70,7 +70,7 @@ class ZhipuSearchAPIWrapper:
) )
logging.info(f"Zhipu检索内容:{search_query}") 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" engines = "duckduckgo,bing"
data = { data = {
"format":"json", "format":"json",

View File

@@ -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

View File

@@ -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

View File

@@ -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}"

View File

@@ -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}"

View File

@@ -1,13 +1,12 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍 # 须用 bash 执行;若误用 sh/dash 会自动改用 bash 再跑一遍
[ -n "${BASH_VERSION:-}" ] || exec /usr/bin/env bash "$0" ${1+"$@"} [ -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 # 日志gangyan/logs/backend.log
# 勿在此加 -Dspring.datasource*:与 application-local.yml 打架易连错库 # backend/chat_web_yj.jar同一 jar 的改名副本)及其 application-local.yml 覆盖配置已于 2026-04-20 归档删除
# 若 jar 未重打、内嵌 yml 仍是远程库,必须依赖 backend/application-local.yml含完整 spring 数据源)。
set -u set -u
source "$(cd "$(dirname "$0")" && pwd)/common-restart.sh" 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" LOG_FILE="$LOG_DIR/backend.log"
if ! ( umask 022; : >>"$LOG_FILE" ) 2>/dev/null; then if ! ( umask 022; : >>"$LOG_FILE" ) 2>/dev/null; then
ALT="$LOG_DIR/backend-$(id -un).log" ALT="$LOG_DIR/backend-$(id -un).log"
@@ -25,34 +24,41 @@ fi
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export PATH="$JAVA_HOME/bin:$PATH" export PATH="$JAVA_HOME/bin:$PATH"
log_tee "======== 停止 chat_web_yj.jar ========" BACKEND_DIR="$GANGYAN_ROOT/chat_web_backend"
pkill -f "chat_web_yj.jar" 2>/dev/null && log_tee "已发送停止信号" || log_tee "未找到运行中的进程" JAR_REL="target/chat_web_backend.jar"
sleep 2
log_tee "======== 启动 chat_web_yj.jar ========" if [ ! -f "$BACKEND_DIR/$JAR_REL" ]; then
cd "$GANGYAN_ROOT/backend" log_tee "错误: 未找到 $BACKEND_DIR/$JAR_REL。先执行 chat_web_backend/compile.sh 编译。"
LOCAL_CFG="$GANGYAN_ROOT/backend/application-local.yml" exit 1
EXTRA_JAVA_ARGS=()
if [ -f "$LOCAL_CFG" ]; then
log_tee "加载本地配置: $LOCAL_CFG"
EXTRA_JAVA_ARGS=(-Dspring.config.additional-location="file:${LOCAL_CFG}")
fi 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 \ nohup java -jar \
-Xms512m -Xmx2048m \ -Xms512m -Xmx2048m \
"${EXTRA_JAVA_ARGS[@]}" \ "$JAR_REL" \
-Dspring.profiles.active=yj \ --spring.profiles.active=dev >> "$LOG_FILE" 2>&1 &
chat_web_yj.jar >> "$LOG_FILE" 2>&1 &
STARTED_PID=$! STARTED_PID=$!
log_tee "已后台启动PID=$STARTED_PID" log_tee "已后台启动PID=$STARTED_PID"
log_tee "Java 标准输出/错误写入: $LOG_FILE" log_tee "Java 标准输出/错误写入: $LOG_FILE"
sleep 3 sleep 3
if kill -0 "$STARTED_PID" 2>/dev/null; then 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 if ss -tln 2>/dev/null | grep -qE ':8099[[:space:]]'; then
log_tee "自检: 进程存活且 8099 已监听(后端已就绪或即将就绪)" log_tee "自检: 进程存活且 8099 已监听"
else else
log_tee "自检: 进程存活,但 8099 尚未监听(可能仍在启动;10 秒后仍无请 tail 上面日志路径)" log_tee "自检: 进程存活,但 8099 尚未监听可能仍在启动10 秒后仍无请 tail $LOG_FILE"
fi fi
else 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 tail -n 40 "$LOG_FILE" 2>/dev/null | while IFS= read -r line || [ -n "$line" ]; do log_line "$line"; done
exit 1
fi fi

View File

@@ -31,7 +31,8 @@ fi
MAX_BYTES=$((MAX_MB * 1024 * 1024)) MAX_BYTES=$((MAX_MB * 1024 * 1024))
TMP="${FILE}.trimtmp.$$" 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")" mkdir -p "$(dirname "$FILE")"
touch "$FILE" 2>/dev/null || true touch "$FILE" 2>/dev/null || true

83
scripts/start-all.sh Executable file
View File

@@ -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-nginxsystemd 管理,不在本脚本中重启)
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://<server-ip>:3000/metalinfo"

View File

@@ -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 预览依赖的本地转换微服务(:6006PyMuPDF 抽文本→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"