From 3fb43c09f372c69dbb82874439ac305ec1e3da52 Mon Sep 17 00:00:00 2001 From: BBIT-Kai <2911862937@qq.com> Date: Tue, 30 Sep 2025 13:48:59 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E4=BB=A3=E4=BB=AA=E8=AF=84?= =?UTF-8?q?=E6=8C=87=E6=A0=87=E8=81=94=E8=AF=86=E5=88=AB=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bbit_ai/app/db/postgres.py | 9 +-- bbit_ai/app/llm/ticketLLMv2.py | 67 +++++++++++++++++ bbit_ai/app/models/F8ImageRequestV2.py | 7 ++ bbit_ai/app/routers/F8.py | 24 +++++++ bbit_ai/app/routers/Vision.py | 2 +- bbit_ai/app/service/vision.py | 23 +++--- ktor/build.gradle.kts | 5 +- readme.md | 60 ++++++++++------ server/{ => docker}/docker-compose.yaml | 72 ++++++++++++++++++- server/docker/rocketmq_config/broker1.conf | 9 +++ server/docker/rocketmq_config/broker2.conf | 9 +++ server/docker/rocketmq_config/rmq-proxy.json | 3 + .../web-antd/src/router/routes/modules/cv.ts | 2 +- .../web-antd/src/router/routes/modules/set.ts | 67 +++++++++++++++++ 14 files changed, 323 insertions(+), 36 deletions(-) create mode 100644 bbit_ai/app/llm/ticketLLMv2.py create mode 100644 bbit_ai/app/models/F8ImageRequestV2.py rename server/{ => docker}/docker-compose.yaml (75%) create mode 100644 server/docker/rocketmq_config/broker1.conf create mode 100644 server/docker/rocketmq_config/broker2.conf create mode 100644 server/docker/rocketmq_config/rmq-proxy.json create mode 100644 vue/apps/web-antd/src/router/routes/modules/set.ts diff --git a/bbit_ai/app/db/postgres.py b/bbit_ai/app/db/postgres.py index 314f825..7b71ae8 100644 --- a/bbit_ai/app/db/postgres.py +++ b/bbit_ai/app/db/postgres.py @@ -485,7 +485,7 @@ def get_ticket_image_list(user_id): "created_at": MyUtils.format_datetime(row[0]), "file_name": row[1], "resolution": row[2], - "size": round(row[3], 2), + "size": round(row[3] / 1024, 2), "name": row[4], "moisture_content": row[5], "cocoon_weight": row[6], @@ -493,7 +493,6 @@ def get_ticket_image_list(user_id): "fresh_shell_weight": row[8], "sample_count": row[9], "barcode": row[10], - # "oss_url": f"http://{SERVER_PATH_OSS}:9000/image-ticket/{row[11]}", "oss_url": get_temp_url("image-ticket", row[11]), "net_weight_total": row[12], "evaluator": row[13], @@ -513,6 +512,7 @@ def insert_ticket_image( moisture_content, cocoon_weight, defective_pupa_count, + dead_pupa_count, fresh_shell_weight, sample_count, barcode, @@ -527,11 +527,11 @@ def insert_ticket_image( """ INSERT INTO ticket_images ( created_by, file_name, resolution, size, name, - moisture_content, cocoon_weight, defective_pupa_count, + moisture_content, cocoon_weight, defective_pupa_count, dead_pupa_count, fresh_shell_weight, sample_count, barcode, oss, net_weight_total, evaluator, reviewer, created_at ) - VALUES (%s, %s, %s, %s, %s, + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, NOW()) RETURNING id @@ -545,6 +545,7 @@ def insert_ticket_image( moisture_content, cocoon_weight, defective_pupa_count, + dead_pupa_count, fresh_shell_weight, sample_count, barcode, diff --git a/bbit_ai/app/llm/ticketLLMv2.py b/bbit_ai/app/llm/ticketLLMv2.py new file mode 100644 index 0000000..d18f5ba --- /dev/null +++ b/bbit_ai/app/llm/ticketLLMv2.py @@ -0,0 +1,67 @@ +import json +import re + +from langchain.schema import HumanMessage + +from config.llm import * +from llm.ticketLLM import decode_barcode + + +def get_ticket_response_v2(image_url: str, needBarcode: bool = False): + # 构建 prompt + prompt_text = f""" +你是一位专业的图像分析AI。你的任务是仔细分析提供的图片内容,并按JSON格式输出结果。 + +## JSON输出结构及字段说明: +# 蚕茧检测信息数据模型 + +该模型用于描述蚕茧检测信息,每条记录包含以下字段: + +## 1. 下足茧重量 (`cocoon_weight`) +- **类型**:浮点数 +- **描述**:下足茧的重量,单位为克(可带小数) + +## 2. 非蛹粒数 (`defective_pupa_count`) +- **类型**:整数 +- **描述**:不合格蛹的数量,即非好蛹的个数 + +## 3. 僵蛹粒数 (`dead_pupa_count`) +- **类型**:整数 +- **描述**:僵蚕蛹的数量 + +## 4. 鲜壳量 (`fresh_shell_weight`) +- **类型**:浮点数 +- **描述**:鲜壳重量,单位为克(可带小数) + +## 5. 小样粒数 (`sample_count`) +- **类型**:整数 +- **描述**:检测使用的小样数量,即用于检测的茧粒数 + +--- + +### 注意事项 +- 所有字段都是必填的(required),在 JSON 实例中必须提供值 +- 浮点数字段可以包含小数,整数字段只能是整数 +- `evaluator` 和 `reviewer` 可以为空字符串,但字段必须存在 +- 如果图片与上述内容无关,则字段值为默认值,例如0、0.0、"" + +最后,只输出严格的 JSON 格式,不要包含其他文字、markdown等内容。 +""" + messages = [ + HumanMessage( + content=[ + {"type": "text", "text": prompt_text}, + {"type": "image_url", "image_url": {"url": image_url}}, + ] + ) + ] + # 直接调用模型 + llmRes = llmVision.invoke(messages).content + # 去掉 ```json 和 ``` 包裹 + cleaned = re.sub(r"^```json\s*|\s*```$", "", llmRes.strip()) + # 解析 JSON + jsonRes = json.loads(cleaned) + print("needBarcode:", needBarcode) + if needBarcode: + jsonRes["barcode"] = decode_barcode(image_url) + return jsonRes diff --git a/bbit_ai/app/models/F8ImageRequestV2.py b/bbit_ai/app/models/F8ImageRequestV2.py new file mode 100644 index 0000000..f2f7f4b --- /dev/null +++ b/bbit_ai/app/models/F8ImageRequestV2.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class F8ImageRequestV2(BaseModel): + title: str + image: str + needBarcode: bool diff --git a/bbit_ai/app/routers/F8.py b/bbit_ai/app/routers/F8.py index b62da27..1a9eb0a 100644 --- a/bbit_ai/app/routers/F8.py +++ b/bbit_ai/app/routers/F8.py @@ -5,6 +5,7 @@ from fastapi import APIRouter from config.app import F8_SERVER_USER_ID from models.BaseResponse import BaseResponse from models.F8ImageRequest import F8ImageRequest +from models.F8ImageRequestV2 import F8ImageRequestV2 from service.vision import process_ticket_image from utils import MyUtils @@ -20,6 +21,29 @@ async def cocoonTicket(data: F8ImageRequest): img_bytes = base64.b64decode(input_data) json_data = await MyUtils.async_task( process_ticket_image, + 1, + True, + img_bytes, + f"{data.title}.jpg", + data.title, + F8_SERVER_USER_ID, + ) + return BaseResponse(data=json_data) + except Exception as e: + return BaseResponse(status=False, message=f"解析失败: {str(e)}", data=None) + + +@f8Router.post("/createTicketImageTaskV2") +async def cocoonTicket(data: F8ImageRequestV2): + input_data = data.image + if "," in input_data: + input_data = input_data.split(",")[1] + try: + img_bytes = base64.b64decode(input_data) + json_data = await MyUtils.async_task( + process_ticket_image, + 2, + data.needBarcode, img_bytes, f"{data.title}.jpg", data.title, diff --git a/bbit_ai/app/routers/Vision.py b/bbit_ai/app/routers/Vision.py index 5d18d81..9aab3a4 100644 --- a/bbit_ai/app/routers/Vision.py +++ b/bbit_ai/app/routers/Vision.py @@ -36,7 +36,7 @@ async def createTicketImageTask( try: contents = await file.read() json_data = await MyUtils.async_task( - process_ticket_image, contents, file.filename, projectName, user_id + process_ticket_image, 1, True, contents, file.filename, projectName, user_id ) return BaseResponse(data=json_data) except Exception as e: diff --git a/bbit_ai/app/service/vision.py b/bbit_ai/app/service/vision.py index abed7aa..714dc40 100644 --- a/bbit_ai/app/service/vision.py +++ b/bbit_ai/app/service/vision.py @@ -5,10 +5,13 @@ import config.minIO as minIO import db.postgres as pg from config.minIO import minio_client from llm.ticketLLM import * +from llm.ticketLLMv2 import get_ticket_response_v2 def process_ticket_image( - img_bytes: bytes, + version: int, + needBarcode: bool = False, + img_bytes=None, file_name: str = None, project_name: str = None, user_id: UUID = None, @@ -18,6 +21,8 @@ def process_ticket_image( """ # 上传到 OSS,使用 UUID 做对象名 + if img_bytes is None: + img_bytes = [] object_name = str(uuid.uuid4()) file_bytes = BytesIO(img_bytes) bucket_name = "image-ticket" @@ -28,15 +33,16 @@ def process_ticket_image( oss_url = minIO.get_temp_url(bucket_name, object_name) # 调用分析方法获取 JSON - json_data = get_ticket_response(oss_url) - # 解析条码 - barcode = decode_barcode(BytesIO(img_bytes)) - json_data["barcode"] = barcode - + if version == 1: + json_data = get_ticket_response(oss_url) + elif version == 2: + json_data = get_ticket_response_v2(oss_url, needBarcode) + else: + json_data = get_ticket_response(oss_url) # 获取图片分辨率和大小 img = Image.open(BytesIO(img_bytes)) resolution = f"{img.width}x{img.height}" - size_kb = len(img_bytes) / 1024 + size_kb = round(len(img_bytes) / 1024, 2) # 插入数据库 pg.insert_ticket_image( @@ -45,12 +51,13 @@ def process_ticket_image( resolution=resolution, size=size_kb, name=project_name if project_name else object_name[:8], + dead_pupa_count=json_data.get("moisture_content") if version == 2 else 0, moisture_content=json_data.get("moisture_content"), cocoon_weight=json_data.get("cocoon_weight"), defective_pupa_count=json_data.get("defective_pupa_count"), fresh_shell_weight=json_data.get("fresh_shell_weight"), sample_count=json_data.get("sample_count"), - barcode=barcode, + barcode=json_data.get("barcode"), oss=object_name, net_weight_total=json_data.get("net_weight_total"), evaluator=json_data.get("evaluator"), diff --git a/ktor/build.gradle.kts b/ktor/build.gradle.kts index 7da7107..9994bb0 100644 --- a/ktor/build.gradle.kts +++ b/ktor/build.gradle.kts @@ -100,7 +100,10 @@ dependencies { implementation("io.ktor:ktor-client-serialization:$ktor_version") // 数据库迁移 // implementation("org.flywaydb:flyway-core:10.13.0") - + // Kong AI // implementation("ai.koog:koog-agents:0.3.0") + // MinIO OSS implementation("io.minio:minio:8.5.17") + // RocketMQ + implementation("org.apache.rocketmq:rocketmq-client-java:5.0.8") } diff --git a/readme.md b/readme.md index bda671c..33fc90f 100644 --- a/readme.md +++ b/readme.md @@ -41,27 +41,47 @@ 公网域名:s1.ronsunny.cn -| 公网端口 | 内网端口 | 框架 | docker服务/Host | docker端口 | 功能作用 | -| -------- | -------- | ---------- | --------------- | ---------- | --------------------------------------- | -| | | Vue | ce_vue | 8090 | vue前端服务 | -| | | FastAPI | ce_pybackend | 13011 | python后端程序 | -| | 5432 | PostgreSQL | ce_postgres | 5432 | PostgreSQL数据库 | -| | 6379 | Redis | ce_redis | 6379 | Radis数据库 | -| | | Etcd | ce_etcd | 2379 | Etcd,Key-Value 存储,给ce_milvus使用 | -| **9000** | 9000 | MinIO | ce_minio | 9000 | MinIO数据访问 | -| | 9001 | | ce_minio | 9001 | MinIOWeb控制端 | -| | 19530 | Milvus | ce_milvus | 19530 | Minvus数据访问 | -| | 9091 | | ce_milvus | 9091 | MinvusWeb控制端,需要加/webui | -| | 3000 | Attu | ce_attu | 3000 | Attu,Minvus的**可视化**控制 | -| **8090** | 8090 | Kong | ce_kong | 8090 | Kong网关 | -| | 8001 | | ce_kong | 8001 | Kong Admin API | -| | 8002 | | ce_kong | 8002 | Kong Admin API 的 HTTPS 端口 | -| | 8444 | | ce_kong | 8444 | Kong **可视化**管理界面 | -| | 8088 | ws-scrcpy | | | Android远程框架 | -| 8088 | | | | | 已废弃,原Android远程框架,已由网关控制 | -| 8089 | | | | | 已废弃,原Ktor后端服务,已由网关控制 | -| 13011 | | | | | 已废弃:原FastAPI后端服务,已由网关控制 | +| 公网端口 | 内网端口 | docker端口 | docker服务/Host | 框架 | 功能作用 | +| -------- | -------- | ---------- | ---------------- | ---------- | ------------------------------------------------------------ | +| | | 8090 | ce_vue | Vue | vue前端服务 | +| | 8089 | | | Ktor | 实验室业务后端 | +| | | 13011 | ce_pybackend | FastAPI | 实验室AI后端 | +| | 5432 | 5432 | ce_postgres | PostgreSQL | PostgreSQL数据库 | +| | 6379 | 6379 | ce_redis | Redis | Radis数据库 | +| | | 2379 | ce_etcd | Etcd | Etcd,Key-Value 存储,给ce_milvus使用 | +| **9000** | 9000 | 9000 | ce_minio | MinIO | MinIO数据访问 | +| | 9001 | 9001 | ce_minio | MinIO | MinIOWeb控制端 | +| | 19530 | 19530 | ce_milvus | Milvus | Minvus数据访问 | +| | 9091 | 9091 | ce_milvus | Milvus | MinvusWeb控制端,需要加/webui | +| | 3000 | 3000 | ce_attu | Attu | Attu,Minvus的**可视化**控制 | +| **8090** | 8090 | 8090 | ce_kong | Kong | Kong网关 | +| | 8001 | 8001 | ce_kong | Kong | Kong Admin API | +| | 8002 | 8002 | ce_kong | Kong | Kong **可视化**管理界面 | +| | 8444 | 8444 | ce_kong | Kong | Kong Admin API 的 HTTPS 端口 | +| | 8088 | | | ws-scrcpy | Android远程框架 | +| | 9876 | 9876 | ce_rmq_name_srv | RocketMQ | NameServer | +| | 8080 | 8080 | ce_rmq_proxy | RocketMQ | RocketMQ Proxy 的 **消息发送/消费 REST API 或 TCP 代理端口** | +| | 8081 | 8081 | ce_rmq_proxy | RocketMQ | RocketMQ Proxy 的 **管理/监控接口** | +| | 8082 | 8082 | ce_rmq_dashboard | RocketMQ | RocketMQ Dashboard **Web UI 端口** | +| | 10909 | 10909 | ce_rmq_broker_a | RocketMQ | broker 内部默认端口 | +| | 10911 | 10911 | ce_rmq_broker_a | RocketMQ | Consumer 拉取消息的端口 | +| | 10912 | 10912 | ce_rmq_broker_a | RocketMQ | Broker 之间的集群复制/同步 | +| | 10929 | 10909 | ce_rmq_broker_b | RocketMQ | broker 内部默认端口 | +| | 10931 | 10911 | ce_rmq_broker_b | RocketMQ | Consumer 拉取消息的端口 | +| | 10932 | 10912 | ce_rmq_broker_b | RocketMQ | Broker 之间的集群复制/同步 | +| 8088 | | | | | 建议后续关闭,原Android远程框架,已由网关控制 | +| 8089 | | | | | 建议后续关闭,原Ktor后端服务,已由网关控制 | +| 13011 | | | | | 已空闲:原FastAPI后端服务,已由网关控制 | +| 服务 | 端口 | 用途 | +| ---------- | ----- | ----------------------------------- | +| NameServer | 9876 | 注册/查询 Broker 地址,服务发现 | +| Broker | 10909 | 内部集群通信(心跳、同步) | +| | 10911 | 外部客户端通信(Producer/Consumer) | +| | 10912 | HA 主从同步(可选) | +| Proxy | 8080 | HTTP/REST 客户端访问 Broker | +| | 8081 | Proxy 管理/状态监控 | +| Dashboard | 8082 | Web UI 可视化监控 | ## 三、部署 diff --git a/server/docker-compose.yaml b/server/docker/docker-compose.yaml similarity index 75% rename from server/docker-compose.yaml rename to server/docker/docker-compose.yaml index 0d70ad1..b4a1457 100644 --- a/server/docker-compose.yaml +++ b/server/docker/docker-compose.yaml @@ -192,6 +192,77 @@ services: - ce_network restart: unless-stopped +# ---------- RocketMQ ---------- + namesrv: + image: apache/rocketmq:5.3.3 + container_name: ce_rmq_name_srv + ports: + - 9876:9876 + networks: + - ce_network + command: sh mqnamesrv + broker1: + image: apache/rocketmq:5.3.3 + container_name: ce_rmq_broker_a + ports: + - 10909:10909 + - 10911:10911 + - 10912:10912 + environment: + - NAMESRV_ADDR=ce_rmq_name_srv:9876 + depends_on: + - namesrv + networks: + - ce_network + volumes: + - ./rocketmq_config/broker1.conf:/opt/broker.conf + command: sh mqbroker -c /opt/broker.conf + broker2: + image: apache/rocketmq:5.3.3 + container_name: ce_rmq_broker_b + links: + - namesrv + ports: + - 10929:10909 + - 10931:10911 + - 10932:10912 + environment: + - NAMESRV_ADDR=ce_rmq_name_srv:9876 # NameServer 地址 + volumes: + - ./rocketmq_config/broker2.conf:/opt/broker.conf + command: sh mqbroker -c /opt/broker.conf + networks: + - ce_network + proxy: + image: apache/rocketmq:5.3.3 + container_name: ce_rmq_proxy + networks: + - ce_network + depends_on: + - broker1 + - broker2 + - namesrv + ports: + - 8080:8080 + - 8081:8081 + restart: on-failure + environment: + - NAMESRV_ADDR=ce_rmq_broker:9876 + command: sh mqproxy + rocketmq-dashboard: + image: apacherocketmq/rocketmq-dashboard:2.1.0 + container_name: ce_rmq_dashboard + environment: + - JAVA_OPTS=-Drocketmq.namesrv.addr=ce_rmq_name_srv:9876 + ports: + - "8082:8082" + restart: unless-stopped + depends_on: + - broker1 + - broker2 + - namesrv + networks: + - ce_network # ---------- 数据卷 ---------- volumes: postgres_data: @@ -207,7 +278,6 @@ volumes: driver_opts: type: tmpfs device: tmpfs - # ---------- 网络 ---------- networks: ce_network: diff --git a/server/docker/rocketmq_config/broker1.conf b/server/docker/rocketmq_config/broker1.conf new file mode 100644 index 0000000..0bfaa85 --- /dev/null +++ b/server/docker/rocketmq_config/broker1.conf @@ -0,0 +1,9 @@ +brokerClusterName = AICluster +brokerName = broker-a +brokerId = 0 +deleteWhen = 04 +fileReservedTime = 48 +brokerRole = ASYNC_MASTER +flushDiskType = ASYNC_FLUSH +storePathRootDir=/home/rocketmq/store +storePathCommitLog=/home/rocketmq/store/commitlog \ No newline at end of file diff --git a/server/docker/rocketmq_config/broker2.conf b/server/docker/rocketmq_config/broker2.conf new file mode 100644 index 0000000..4b1eb38 --- /dev/null +++ b/server/docker/rocketmq_config/broker2.conf @@ -0,0 +1,9 @@ +brokerClusterName = AICluster +brokerName = broker-b +brokerId = 1 +deleteWhen = 04 +fileReservedTime = 48 +brokerRole = SLAVE +flushDiskType = ASYNC_FLUSH +storePathRootDir=/home/rocketmq/store +storePathCommitLog=/home/rocketmq/store/commitlog \ No newline at end of file diff --git a/server/docker/rocketmq_config/rmq-proxy.json b/server/docker/rocketmq_config/rmq-proxy.json new file mode 100644 index 0000000..aed622f --- /dev/null +++ b/server/docker/rocketmq_config/rmq-proxy.json @@ -0,0 +1,3 @@ +{ + "rocketMQClusterName": "AICluster" +} \ No newline at end of file diff --git a/vue/apps/web-antd/src/router/routes/modules/cv.ts b/vue/apps/web-antd/src/router/routes/modules/cv.ts index 961c7ff..6c1fceb 100644 --- a/vue/apps/web-antd/src/router/routes/modules/cv.ts +++ b/vue/apps/web-antd/src/router/routes/modules/cv.ts @@ -7,7 +7,7 @@ const routes: RouteRecordRaw[] = [ { meta: { icon: 'ic:round-remove-red-eye', - authority: ['iva', 'sca', 'ysa'], + authority: ['iva', 'sca', 'ysa', 'ticket'], keepAlive: true, order: 2, title: $t('计算机视觉'), diff --git a/vue/apps/web-antd/src/router/routes/modules/set.ts b/vue/apps/web-antd/src/router/routes/modules/set.ts new file mode 100644 index 0000000..0c79c18 --- /dev/null +++ b/vue/apps/web-antd/src/router/routes/modules/set.ts @@ -0,0 +1,67 @@ +import type { RouteRecordRaw } from 'vue-router'; + +import { $t } from '#/locales'; + +const IFrameView = () => import('@vben/layouts').then((m) => m.IFrameView); + +const routes: RouteRecordRaw[] = [ + { + meta: { + icon: 'ic:round-handyman', + keepAlive: false, + authority: ['set'], + order: 2, + title: $t('控制后台'), + }, + name: 'Set', + path: '/set', + children: [ + { + name: 'Kong-set', + path: '/set/kong', + component: IFrameView, + meta: { + icon: 'mdi:server-network', + link: 'http://10.10.10.9:8002/', + keepAlive: true, + title: '网关控制后台', + }, + }, + { + name: 'Milvus-set', + path: '/set/kong', + component: IFrameView, + meta: { + icon: 'mdi:database', + link: 'http://10.10.10.9:3000/', + keepAlive: true, + title: 'Milvus Attu后台', + }, + }, + { + name: 'Milvus-webui', + path: '/set/kong', + component: IFrameView, + meta: { + icon: 'mdi:monitor-dashboard', + link: 'http://10.10.10.9:9091/webui', + keepAlive: true, + title: 'Milvus 自带后台', + }, + }, + { + name: 'RocketMQ-webui', + path: '/set/rocketMQ', + component: IFrameView, + meta: { + icon: 'mdi:server-network', + link: 'http://10.10.10.9:8082', + keepAlive: true, + title: 'RocketMQ可视化后台', + }, + }, + ], + }, +]; + +export default routes;