feat(management): 管理系统更新至v0.1.2版本,修复若干问题: (#19)

1.新建用户模型配置不全问题
2.用户为空时,添加用户异常问题
3.用户配置界面点击未刷新问题
4.管理员账号密码无法修改问题
5.前后端API调用访问不畅问题
This commit is contained in:
zstar 2025-04-09 23:58:17 +08:00 committed by GitHub
parent 5c59cbdf9c
commit 4101a46ce3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 236 additions and 155 deletions

View File

@ -1,4 +1,7 @@
# Ragflow-Plus
<div align="center">
<img src="assets/ragflow-plus.png" width="250" alt="Ragflow-Plus">
</div>
## 项目介绍
@ -11,7 +14,7 @@ Ragflow-Plus 是一个基于 Ragflow 的开源项目,主旨是在不影响 Rag
移除原登陆页用户注册的通道,搭建用户后台管理系统,可对用户进行管理,包括用户管理、团队管理、用户模型配置管理等功能。
### 二. 文档撰写功能
新增文档撰写全新的交互方式,支持直接导出为 Word 文档
新增文档撰写全新的交互方式,支持直接导出为 Word 文档
## 使用方式
@ -104,7 +107,7 @@ docker cp dist ragflow-server:/ragflow/web/
## 交流群
如果有其它需求或问题建议,可加入交流群进行讨论
如果有其它需求或问题建议,可加入交流群进行讨论
由于群聊超过200人无法通过扫码加入如需加群加我微信zstar1003备注"加群"即可。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 47 KiB

BIN
assets/ragflow-plus.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,7 +1,3 @@
# The type of doc engine to use.
# Available options:
# - `elasticsearch` (default)
# - `infinity` (https://github.com/infiniflow/infinity)
DOC_ENGINE=${DOC_ENGINE:-elasticsearch}
# ------------------------------
@ -76,73 +72,15 @@ REDIS_PORT=6379
REDIS_PASSWORD=infini_rag_flow
# The port used to expose RAGFlow's HTTP API service to the host machine,
# allowing EXTERNAL access to the service running inside the Docker container.
SVR_HTTP_PORT=9380
# The RAGFlow Docker image to download.
# Defaults to the v0.17.2-slim edition, which is the RAGFlow Docker image without embedding models.
# RAGFLOW_IMAGE=infiniflow/ragflow:v0.17.2-slim
#
# To download the RAGFlow Docker image with embedding models, uncomment the following line instead:
RAGFLOW_IMAGE=infiniflow/ragflow:v0.17.2
#
# The Docker image of the v0.17.2 edition includes:
# - Built-in embedding models:
# - BAAI/bge-large-zh-v1.5
# - BAAI/bge-reranker-v2-m3
# - maidalun1020/bce-embedding-base_v1
# - maidalun1020/bce-reranker-base_v1
# - Embedding models that will be downloaded once you select them in the RAGFlow UI:
# - BAAI/bge-base-en-v1.5
# - BAAI/bge-large-en-v1.5
# - BAAI/bge-small-en-v1.5
# - BAAI/bge-small-zh-v1.5
# - jinaai/jina-embeddings-v2-base-en
# - jinaai/jina-embeddings-v2-small-en
# - nomic-ai/nomic-embed-text-v1.5
# - sentence-transformers/all-MiniLM-L6-v2
#
#
# If you cannot download the RAGFlow Docker image:
#
# - For the `nightly-slim` edition, uncomment either of the following:
# RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:nightly-slim
# RAGFLOW_IMAGE=registry.cn-hangzhou.aliyuncs.com/infiniflow/ragflow:nightly-slim
#
# - For the `nightly` edition, uncomment either of the following:
# RAGFLOW_IMAGE=swr.cn-north-4.myhuaweicloud.com/infiniflow/ragflow:nightly
# RAGFLOW_IMAGE=registry.cn-hangzhou.aliyuncs.com/infiniflow/ragflow:nightly
# The local time zone.
TIMEZONE='Asia/Shanghai'
# Uncomment the following line if you have limited access to huggingface.co:
# HF_ENDPOINT=https://hf-mirror.com
# Optimizations for MacOS
# Uncomment the following line if your operating system is MacOS:
# MACOS=1
# The maximum file size for each uploaded file, in bytes.
# You can uncomment this line and update the value if you wish to change the 128M file size limit
# MAX_CONTENT_LENGTH=134217728
# After making the change, ensure you update `client_max_body_size` in nginx/nginx.conf correspondingly.
# The log level for the RAGFlow's owned packages and imported packages.
# Available level:
# - `DEBUG`
# - `INFO` (default)
# - `WARNING`
# - `ERROR`
# For example, following line changes the log level of `ragflow.es_conn` to `DEBUG`:
# LOG_LEVELS=ragflow.es_conn=DEBUG
# aliyun OSS configuration
# STORAGE_IMPL=OSS
# ACCESS_KEY=xxx
# SECRET_KEY=eee
# ENDPOINT=http://oss-cn-hangzhou.aliyuncs.com
# REGION=cn-hangzhou
# BUCKET=ragflow65536
# 管理系统用户名和密码
MANAGEMENT_ADMIN_USERNAME=admin
MANAGEMENT_ADMIN_PASSWORD=12345678
# 用来加密生成token的密钥(用来加密token提高token的安全性防止token被破解和篡改)
MANAGEMENT_JWT_SECRET=20250409

View File

@ -30,26 +30,36 @@ services:
extra_hosts:
- "host.docker.internal:host-gateway"
# 新增加的用户后台信息管理系统
management-frontend:
image: zstar1003/ragflowplus-management-web:v0.1.1
frontend:
image: zstar1003/ragflowplus-management-web:v0.1.2
build:
context: .
dockerfile: Dockerfile
target: frontend
ports:
- "8888:80"
depends_on:
- management-backend
- backend
environment:
- API_URL=http://management-backend:5000
- API_BASE_URL=/api
networks:
- ragflow
management-backend:
image: zstar1003/ragflowplus-management-server:v0.1.1
backend:
image: zstar1003/ragflowplus-management-server:v0.1.2
build:
context: .
dockerfile: Dockerfile
target: backend
ports:
- "5000:5000"
environment:
- FLASK_ENV=development
- CORS_ALLOWED_ORIGINS=http://localhost:8888,http://management-frontend
- CORS_ALLOWED_ORIGINS=http://frontend
- MANAGEMENT_ADMIN_USERNAME=${MANAGEMENT_ADMIN_USERNAME:-admin}
- MANAGEMENT_ADMIN_PASSWORD=${MANAGEMENT_ADMIN_PASSWORD:-12345678}
- MANAGEMENT_JWT_SECRET=${MANAGEMENT_JWT_SECRET:-12345678}
extra_hosts:
- "host.docker.internal:host-gateway"
networks:

5
management/.env Normal file
View File

@ -0,0 +1,5 @@
# 管理系统用户名和密码
MANAGEMENT_ADMIN_USERNAME=admin
MANAGEMENT_ADMIN_PASSWORD=12345678
# 用来加密生成token的密钥(用来加密token提高token的安全性防止token被破解和篡改)
MANAGEMENT_JWT_SECRET=20250409

View File

@ -1,6 +1,6 @@
services:
frontend:
image: zstar1003/ragflowplus-management-web:v0.1.1
image: zstar1003/ragflowplus-management-web:v0.1.2
build:
context: .
dockerfile: Dockerfile
@ -10,10 +10,12 @@ services:
depends_on:
- backend
environment:
- API_URL=http://backend:5000
- API_BASE_URL=/api
networks:
- management_network
backend:
image: zstar1003/ragflowplus-management-server:v0.1.1
image: zstar1003/ragflowplus-management-server:v0.1.2
build:
context: .
dockerfile: Dockerfile
@ -22,6 +24,15 @@ services:
- "5000:5000"
environment:
- FLASK_ENV=development
- CORS_ALLOWED_ORIGINS=http://localhost:8888,http://frontend
- CORS_ALLOWED_ORIGINS=http://frontend
- MANAGEMENT_ADMIN_USERNAME=${MANAGEMENT_ADMIN_USERNAME:-admin}
- MANAGEMENT_ADMIN_PASSWORD=${MANAGEMENT_ADMIN_PASSWORD:-12345678}
- MANAGEMENT_JWT_SECRET=${MANAGEMENT_JWT_SECRET:-12345678}
extra_hosts:
- "host.docker.internal:host-gateway"
- "host.docker.internal:host-gateway"
networks:
- management_network
networks:
management_network:
driver: bridge

View File

@ -10,4 +10,15 @@ server {
alias /usr/share/nginx/html/;
try_files $uri $uri/ /index.html;
}
location /api/ {
# 将所有以/api/开头的请求转发到后端服务(backend容器的5000端口)
proxy_pass http://backend:5000/api/;
# 设置代理请求头
proxy_set_header Host $host; # 保留原始请求的Host头
# 传递客户端真实IP
proxy_set_header X-Real-IP $remote_addr; # 记录客户端IP
# 添加X-Forwarded-For头
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链路追踪
}
}

View File

@ -1,7 +1,14 @@
import database
import jwt
import os
from flask import Flask, jsonify, request
from flask_cors import CORS
import database
from datetime import datetime, timedelta
from routes import register_routes
from dotenv import load_dotenv
# 加载环境变量
load_dotenv(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'docker', '.env'))
app = Flask(__name__)
# 启用CORS允许前端访问
@ -10,11 +17,49 @@ CORS(app, resources={r"/api/*": {"origins": "*"}}, supports_credentials=True)
# 注册所有路由
register_routes(app)
# 从环境变量获取配置
ADMIN_USERNAME = os.getenv('MANAGEMENT_ADMIN_USERNAME', 'admin')
ADMIN_PASSWORD = os.getenv('MANAGEMENT_ADMIN_PASSWORD', '12345678')
JWT_SECRET = os.getenv('MANAGEMENT_JWT_SECRET', 'your-secret-key')
# 生成token
def generate_token(username):
# 设置令牌过期时间例如1小时后过期
expire_time = datetime.utcnow() + timedelta(hours=1)
# 生成令牌
token = jwt.encode({
'username': username,
'exp': expire_time
}, JWT_SECRET, algorithm='HS256')
return token
# 登录路由保留在主文件中
@app.route('/api/v1/auth/login', methods=['POST'])
def login():
# 实现登录逻辑
return {"code": 0, "data": {"token": "your-token"}, "message": "登录成功"}
data = request.get_json()
username = data.get('username')
password = data.get('password')
# 创建用户名和密码的映射
valid_users = {
ADMIN_USERNAME: ADMIN_PASSWORD
}
# 验证用户名是否存在
if not username or username not in valid_users:
return {"code": 1, "message": "用户名不存在"}, 400
# 验证密码是否正确
if not password or password != valid_users[username]:
return {"code": 1, "message": "密码错误"}, 400
# 生成token
token = generate_token(username)
return {"code": 0, "data": {"token": token}, "message": "登录成功"}
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
app.run(host='0.0.0.0', port=5000)

View File

@ -1,11 +1,25 @@
import mysql.connector
import os
from utils import generate_uuid, encrypt_password
from datetime import datetime
# 检测是否在Docker容器中运行
def is_running_in_docker():
# 检查是否存在/.dockerenv文件
docker_env = os.path.exists('/.dockerenv')
# 或者检查cgroup中是否包含docker字符串
try:
with open('/proc/self/cgroup', 'r') as f:
return docker_env or 'docker' in f.read()
except:
return docker_env
# 根据运行环境选择合适的主机地址
DB_HOST = 'host.docker.internal' if is_running_in_docker() else 'localhost'
# 数据库连接配置
db_config = {
# "host": "host.docker.internal", # 如果是在Docke容器内部访问数据库
"host": "localhost",
"host": DB_HOST,
"port": 5455,
"user": "root",
"password": "infini_rag_flow",

View File

@ -3,4 +3,6 @@ flask_cors==5.0.1
mysql-connector-python==9.2.0
pycryptodomex==3.20.0
tabulate==0.9.0
Werkzeug==3.1.3
Werkzeug==3.1.3
PyJWT==2.10.1
dotenv==0.9.9

View File

@ -102,25 +102,40 @@ def create_user(user_data):
conn = mysql.connector.connect(**db_config)
cursor = conn.cursor(dictionary=True)
# 查询最早创建的tenant配置
query_earliest_tenant = """
SELECT id, llm_id, embd_id, asr_id, img2txt_id, rerank_id, tts_id, parser_ids, credit
FROM tenant
WHERE create_time = (SELECT MIN(create_time) FROM tenant)
LIMIT 1
"""
cursor.execute(query_earliest_tenant)
earliest_tenant = cursor.fetchone()
# 查询最早创建的tenant配置
query_earliest_tenant_llm = """
SELECT llm_factory, model_type, llm_name, api_key, api_base, max_tokens, used_tokens
FROM tenant_llm
WHERE create_time = (SELECT MIN(create_time) FROM tenant_llm)
LIMIT 1
"""
cursor.execute(query_earliest_tenant_llm)
earliest_tenant_llm = cursor.fetchone()
# 检查用户表是否为空
check_users_query = "SELECT COUNT(*) as user_count FROM user"
cursor.execute(check_users_query)
user_count = cursor.fetchone()['user_count']
# 如果有用户则查询最早的tenant和用户配置
if user_count > 0:
# 查询最早创建的tenant配置
query_earliest_tenant = """
SELECT id, llm_id, embd_id, asr_id, img2txt_id, rerank_id, tts_id, parser_ids, credit
FROM tenant
WHERE create_time = (SELECT MIN(create_time) FROM tenant)
LIMIT 1
"""
cursor.execute(query_earliest_tenant)
earliest_tenant = cursor.fetchone()
# 查询最早创建的用户ID
query_earliest_user = """
SELECT id FROM user
WHERE create_time = (SELECT MIN(create_time) FROM user)
LIMIT 1
"""
cursor.execute(query_earliest_user)
earliest_user = cursor.fetchone()
# 查询最早用户的所有tenant_llm配置
query_earliest_user_tenant_llms = """
SELECT llm_factory, model_type, llm_name, api_key, api_base, max_tokens, used_tokens
FROM tenant_llm
WHERE tenant_id = %s
"""
cursor.execute(query_earliest_user_tenant_llms, (earliest_user['id'],))
earliest_user_tenant_llms = cursor.fetchall()
# 开始插入
user_id = generate_uuid()
@ -169,13 +184,23 @@ def create_user(user_data):
%s, %s, %s
)
"""
tenant_data = (
user_id, create_time, current_date, create_time, current_date, username + "'s Kingdom",
None, str(earliest_tenant['llm_id']), str(earliest_tenant['embd_id']),
str(earliest_tenant['asr_id']), str(earliest_tenant['img2txt_id']),
str(earliest_tenant['rerank_id']), str(earliest_tenant['tts_id']),
str(earliest_tenant['parser_ids']), str(earliest_tenant['credit']), 1
)
if user_count > 0:
# 如果有现有用户,复制其模型配置
tenant_data = (
user_id, create_time, current_date, create_time, current_date, username + "'s Kingdom",
None, str(earliest_tenant['llm_id']), str(earliest_tenant['embd_id']),
str(earliest_tenant['asr_id']), str(earliest_tenant['img2txt_id']),
str(earliest_tenant['rerank_id']), str(earliest_tenant['tts_id']),
str(earliest_tenant['parser_ids']), str(earliest_tenant['credit']), 1
)
else:
# 如果是第一个用户模型ID使用空字符串
tenant_data = (
user_id, create_time, current_date, create_time, current_date, username + "'s Kingdom",
None, '', '', '', '', '', '',
'', "1000", 1
)
cursor.execute(tenant_insert_query, tenant_data)
# 插入用户租户关系表owner角色
@ -194,39 +219,44 @@ def create_user(user_data):
)
cursor.execute(user_tenant_insert_owner_query, user_tenant_data_owner)
# 插入用户租户关系表normal角色
user_tenant_insert_normal_query = """
INSERT INTO user_tenant (
id, create_time, create_date, update_time, update_date, user_id,
tenant_id, role, invited_by, status
) VALUES (
%s, %s, %s, %s, %s, %s,
%s, %s, %s, %s
)
"""
user_tenant_data_normal = (
generate_uuid(), create_time, current_date, create_time, current_date, user_id,
earliest_tenant['id'], "normal", earliest_tenant['id'], 1
)
cursor.execute(user_tenant_insert_normal_query, user_tenant_data_normal)
# 只有在存在其他用户时,才加入最早用户的团队
if user_count > 0:
# 插入用户租户关系表normal角色
user_tenant_insert_normal_query = """
INSERT INTO user_tenant (
id, create_time, create_date, update_time, update_date, user_id,
tenant_id, role, invited_by, status
) VALUES (
%s, %s, %s, %s, %s, %s,
%s, %s, %s, %s
)
"""
user_tenant_data_normal = (
generate_uuid(), create_time, current_date, create_time, current_date, user_id,
earliest_tenant['id'], "normal", earliest_tenant['id'], 1
)
cursor.execute(user_tenant_insert_normal_query, user_tenant_data_normal)
# 插入租户LLM配置表
tenant_llm_insert_query = """
INSERT INTO tenant_llm (
create_time, create_date, update_time, update_date, tenant_id,
llm_factory, model_type, llm_name, api_key, api_base, max_tokens, used_tokens
) VALUES (
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s
)
"""
tenant_llm_data = (
create_time, current_date, create_time, current_date, user_id,
str(earliest_tenant_llm['llm_factory']), str(earliest_tenant_llm['model_type']), str(earliest_tenant_llm['llm_name']),
str(earliest_tenant_llm['api_key']), str(earliest_tenant_llm['api_base']), str(earliest_tenant_llm['max_tokens']), 0
)
cursor.execute(tenant_llm_insert_query, tenant_llm_data)
# 为新用户复制最早用户的所有tenant_llm配置
tenant_llm_insert_query = """
INSERT INTO tenant_llm (
create_time, create_date, update_time, update_date, tenant_id,
llm_factory, model_type, llm_name, api_key, api_base, max_tokens, used_tokens
) VALUES (
%s, %s, %s, %s, %s,
%s, %s, %s, %s, %s, %s, %s
)
"""
# 遍历最早用户的所有tenant_llm配置并复制给新用户
for tenant_llm in earliest_user_tenant_llms:
tenant_llm_data = (
create_time, current_date, create_time, current_date, user_id,
str(tenant_llm['llm_factory']), str(tenant_llm['model_type']), str(tenant_llm['llm_name']),
str(tenant_llm['api_key']), str(tenant_llm['api_base']), str(tenant_llm['max_tokens']), 0
)
cursor.execute(tenant_llm_insert_query, tenant_llm_data)
conn.commit()
cursor.close()
conn.close()

View File

@ -1,7 +1,9 @@
# 所有环境的环境变量(命名必须以 VITE_ 开头)
## 项目标题
VITE_APP_TITLE = Ragflow Plus
## 路由模式 hash 或 html5
VITE_ROUTER_HISTORY = hash
# 默认登录凭据
VITE_DEFAULT_USERNAME=
VITE_DEFAULT_PASSWORD=

View File

@ -1,7 +1,7 @@
# 生产环境的环境变量(命名必须以 VITE_ 开头)
## 后端接口地址(如果解决跨域问题采用 CORS 就需要写绝对路径)
VITE_BASE_URL = http://localhost:5000
VITE_BASE_URL = ""
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/v3-admin-vite/ 域名下就需要填写 /v3-admin-vite/
VITE_PUBLIC_PATH = /v3-admin-vite/

View File

@ -7,7 +7,7 @@
"repository": "https://github.com/un-pany/v3-admin-vite",
"scripts": {
"dev": "vite",
"build:staging": "vue-tsc && vite build --mode staging",
"build:staging": "vue-tsc && vite build --mode production",
"build": "vue-tsc && vite build",
"preview": "vite preview",
"lint": "eslint . --fix",

View File

@ -55,7 +55,7 @@ function createInstance() {
const message = get(error, "response.data.message")
switch (status) {
case 400:
error.message = "请求错误"
error.message = "账号密码不正确"
break
case 401:
// Token 过期时

View File

@ -28,8 +28,8 @@ const loading = ref(false)
/** 登录表单数据 */
const loginFormData: LoginRequestData = reactive({
username: "admin",
password: "12345678",
username: import.meta.env.VITE_DEFAULT_USERNAME || "admin",
password: import.meta.env.VITE_DEFAULT_PASSWORD || "12345678",
code: ""
})

View File

@ -99,6 +99,16 @@ function handleSelectionChange(selection: TableData[]) {
//
watch([() => paginationData.currentPage, () => paginationData.pageSize], getTableData, { immediate: true })
//
onMounted(() => {
getTableData()
})
//
onActivated(() => {
getTableData()
})
</script>
<template>