diff --git a/README.md b/README.md index 4874d0e..c835ca2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ -# Ragflow-Plus + +
+ Ragflow-Plus +
## 项目介绍 @@ -11,7 +14,7 @@ Ragflow-Plus 是一个基于 Ragflow 的开源项目,主旨是在不影响 Rag 移除原登陆页用户注册的通道,搭建用户后台管理系统,可对用户进行管理,包括用户管理、团队管理、用户模型配置管理等功能。 ### 二. 文档撰写功能 -新增文档撰写全新的交互方式,支持直接导出为 Word 文档 +新增文档撰写全新的交互方式,支持直接导出为 Word 文档。 ## 使用方式 @@ -104,7 +107,7 @@ docker cp dist ragflow-server:/ragflow/web/ ## 交流群 -如果有其它需求或问题建议,可加入交流群进行讨论 +如果有其它需求或问题建议,可加入交流群进行讨论。 由于群聊超过200人,无法通过扫码加入,如需加群,加我微信zstar1003,备注"加群"即可。 diff --git a/assets/management.png b/assets/management.png index 34f42f8..ce90957 100644 Binary files a/assets/management.png and b/assets/management.png differ diff --git a/assets/ragflow-plus.png b/assets/ragflow-plus.png new file mode 100644 index 0000000..be50cb7 Binary files /dev/null and b/assets/ragflow-plus.png differ diff --git a/docker/.env b/docker/.env index fc3b47d..bb92ae5 100644 --- a/docker/.env +++ b/docker/.env @@ -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 \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 22c3ee4..81cba49 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -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: diff --git a/management/.env b/management/.env new file mode 100644 index 0000000..9e893b0 --- /dev/null +++ b/management/.env @@ -0,0 +1,5 @@ +# 管理系统用户名和密码 +MANAGEMENT_ADMIN_USERNAME=admin +MANAGEMENT_ADMIN_PASSWORD=12345678 +# 用来加密生成token的密钥(用来加密token,提高token的安全性,防止token被破解和篡改) +MANAGEMENT_JWT_SECRET=20250409 \ No newline at end of file diff --git a/management/docker-compose.yml b/management/docker-compose.yml index 773a28e..2568397 100644 --- a/management/docker-compose.yml +++ b/management/docker-compose.yml @@ -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" \ No newline at end of file + - "host.docker.internal:host-gateway" + networks: + - management_network + +networks: + management_network: + driver: bridge \ No newline at end of file diff --git a/management/nginx.conf b/management/nginx.conf index e0db9a3..8593259 100644 --- a/management/nginx.conf +++ b/management/nginx.conf @@ -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; # 代理链路追踪 + } } \ No newline at end of file diff --git a/management/server/app.py b/management/server/app.py index 206d875..00e8632 100644 --- a/management/server/app.py +++ b/management/server/app.py @@ -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) \ No newline at end of file + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/management/server/database.py b/management/server/database.py index 7663e15..01ea2f9 100644 --- a/management/server/database.py +++ b/management/server/database.py @@ -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", diff --git a/management/server/requirements.txt b/management/server/requirements.txt index d7dc9c1..1de9102 100644 --- a/management/server/requirements.txt +++ b/management/server/requirements.txt @@ -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 \ No newline at end of file +Werkzeug==3.1.3 +PyJWT==2.10.1 +dotenv==0.9.9 \ No newline at end of file diff --git a/management/server/services/users/service.py b/management/server/services/users/service.py index 4ec9480..f3a72b0 100644 --- a/management/server/services/users/service.py +++ b/management/server/services/users/service.py @@ -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() diff --git a/management/web/.env b/management/web/.env index 4d97a03..f06f271 100644 --- a/management/web/.env +++ b/management/web/.env @@ -1,7 +1,9 @@ -# 所有环境的环境变量(命名必须以 VITE_ 开头) - ## 项目标题 VITE_APP_TITLE = Ragflow Plus ## 路由模式 hash 或 html5 VITE_ROUTER_HISTORY = hash + +# 默认登录凭据 +VITE_DEFAULT_USERNAME= +VITE_DEFAULT_PASSWORD= diff --git a/management/web/.env.production b/management/web/.env.production index 7e59a62..ffed50e 100644 --- a/management/web/.env.production +++ b/management/web/.env.production @@ -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/ diff --git a/management/web/package.json b/management/web/package.json index 86b5289..50f8091 100644 --- a/management/web/package.json +++ b/management/web/package.json @@ -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", diff --git a/management/web/src/http/axios.ts b/management/web/src/http/axios.ts index a186324..8970ba5 100644 --- a/management/web/src/http/axios.ts +++ b/management/web/src/http/axios.ts @@ -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 过期时 diff --git a/management/web/src/pages/login/index.vue b/management/web/src/pages/login/index.vue index c73bbbf..c572c4a 100644 --- a/management/web/src/pages/login/index.vue +++ b/management/web/src/pages/login/index.vue @@ -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: "" }) diff --git a/management/web/src/pages/user-config/index.vue b/management/web/src/pages/user-config/index.vue index 2577949..97cd423 100644 --- a/management/web/src/pages/user-config/index.vue +++ b/management/web/src/pages/user-config/index.vue @@ -99,6 +99,16 @@ function handleSelectionChange(selection: TableData[]) { // 监听分页参数的变化 watch([() => paginationData.currentPage, () => paginationData.pageSize], getTableData, { immediate: true }) + +// 确保页面挂载和激活时获取数据 +onMounted(() => { + getTableData() +}) + +// 当从其他页面切换回来时刷新数据 +onActivated(() => { + getTableData() +})