From 13b4f626aa01eb800c0ca84059e5e8c708a91c5d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com>
Date: Sat, 4 Apr 2026 10:31:32 +0800
Subject: [PATCH] =?UTF-8?q?remove(AI):=20=E7=A7=BB=E9=99=A4=E6=89=80?=
=?UTF-8?q?=E6=9C=89AI=E7=9B=B8=E5=85=B3=E6=A8=A1=E5=9D=97=E4=BB=A3?=
=?UTF-8?q?=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 删除Ollama客户端及DTO类定义
- 移除AI聊天、分析、知识库控制器
- 清理AI相关数据传输对象
- 删除知识库实体类和映射器
- 移除AI分析服务和提示词模板
- 清理AI相关的XML映射文件
- 移除AI配置属性类定义
- 删除知识库服务实现类
---
.../com/gxwebsoft/ai/client/OllamaClient.java | 194 ------
.../ai/client/dto/OllamaChatRequest.java | 19 -
.../ai/client/dto/OllamaChatResponse.java | 28 -
.../ai/client/dto/OllamaEmbeddingRequest.java | 14 -
.../client/dto/OllamaEmbeddingResponse.java | 13 -
.../ai/client/dto/OllamaMessage.java | 14 -
.../ai/client/dto/OllamaTagsResponse.java | 22 -
.../ai/config/AiOllamaProperties.java | 68 --
.../ai/controller/AiAnalyticsController.java | 37 -
.../ai/controller/AiChatController.java | 95 ---
.../ai/controller/AiKbController.java | 54 --
.../ai/dto/AiAnalyticsAskRequest.java | 13 -
.../ai/dto/AiAnalyticsAskResult.java | 12 -
.../com/gxwebsoft/ai/dto/AiChatRequest.java | 35 -
.../com/gxwebsoft/ai/dto/AiChatResult.java | 15 -
.../com/gxwebsoft/ai/dto/AiKbAskRequest.java | 12 -
.../com/gxwebsoft/ai/dto/AiKbAskResult.java | 12 -
.../java/com/gxwebsoft/ai/dto/AiKbHit.java | 15 -
.../gxwebsoft/ai/dto/AiKbIngestResult.java | 15 -
.../gxwebsoft/ai/dto/AiKbQueryRequest.java | 12 -
.../com/gxwebsoft/ai/dto/AiKbQueryResult.java | 13 -
.../java/com/gxwebsoft/ai/dto/AiMessage.java | 14 -
.../ai/dto/AiShopMetricsQueryRequest.java | 18 -
.../ai/dto/AiShopMetricsQueryResult.java | 16 -
.../gxwebsoft/ai/dto/AiShopMetricsRow.java | 23 -
.../com/gxwebsoft/ai/entity/AiKbChunk.java | 57 --
.../com/gxwebsoft/ai/entity/AiKbDocument.java | 59 --
.../gxwebsoft/ai/mapper/AiKbChunkMapper.java | 10 -
.../ai/mapper/AiKbDocumentMapper.java | 10 -
.../ai/mapper/AiShopAnalyticsMapper.java | 16 -
.../ai/mapper/xml/AiShopAnalyticsMapper.xml | 24 -
.../com/gxwebsoft/ai/prompt/AiPrompts.java | 24 -
.../ai/service/AiAnalyticsService.java | 82 ---
.../gxwebsoft/ai/service/AiChatService.java | 94 ---
.../ai/service/AiKbChunkService.java | 8 -
.../ai/service/AiKbDocumentService.java | 8 -
.../gxwebsoft/ai/service/AiKbRagService.java | 426 ------------
.../ai/service/AiShopAnalyticsService.java | 46 --
.../ai/service/impl/AiKbChunkServiceImpl.java | 12 -
.../service/impl/AiKbDocumentServiceImpl.java | 12 -
.../com/gxwebsoft/ai/util/AiTextUtil.java | 100 ---
.../auto/controller/QrLoginController.java | 104 ---
.../auto/dto/QrLoginConfirmRequest.java | 50 --
.../com/gxwebsoft/auto/dto/QrLoginData.java | 55 --
.../auto/dto/QrLoginGenerateResponse.java | 29 -
.../auto/dto/QrLoginStatusResponse.java | 32 -
.../auto/service/QrLoginService.java | 46 --
.../auto/service/impl/QrLoginServiceImpl.java | 239 -------
.../ClinicAppointmentController.java | 122 ----
.../ClinicDoctorApplyController.java | 128 ----
.../ClinicDoctorUserController.java | 128 ----
.../controller/ClinicMedicineController.java | 127 ----
.../ClinicMedicineInoutController.java | 127 ----
.../ClinicMedicineStockController.java | 127 ----
.../ClinicPatientUserController.java | 129 ----
.../ClinicPrescriptionController.java | 191 ------
.../ClinicPrescriptionItemController.java | 121 ----
...务中台-排班信息接口对接文档20251114(2).docx | Bin 115163 -> 0 bytes
.../clinic/dto/PrescriptionOrderRequest.java | 24 -
.../clinic/entity/ClinicAppointment.java | 81 ---
.../clinic/entity/ClinicDoctorApply.java | 125 ----
.../clinic/entity/ClinicDoctorUser.java | 99 ---
.../clinic/entity/ClinicMedicine.java | 71 --
.../clinic/entity/ClinicMedicineInout.java | 99 ---
.../clinic/entity/ClinicMedicineStock.java | 59 --
.../clinic/entity/ClinicPatientUser.java | 85 ---
.../clinic/entity/ClinicPrescription.java | 133 ----
.../clinic/entity/ClinicPrescriptionItem.java | 99 ---
.../mapper/ClinicAppointmentMapper.java | 37 -
.../mapper/ClinicDoctorApplyMapper.java | 37 -
.../clinic/mapper/ClinicDoctorUserMapper.java | 37 -
.../mapper/ClinicMedicineInoutMapper.java | 37 -
.../clinic/mapper/ClinicMedicineMapper.java | 37 -
.../mapper/ClinicMedicineStockMapper.java | 37 -
.../mapper/ClinicPatientUserMapper.java | 37 -
.../mapper/ClinicPrescriptionItemMapper.java | 38 --
.../mapper/ClinicPrescriptionMapper.java | 38 --
.../mapper/xml/ClinicAppointmentMapper.xml | 62 --
.../mapper/xml/ClinicDoctorApplyMapper.xml | 114 ----
.../mapper/xml/ClinicDoctorUserMapper.xml | 86 ---
.../mapper/xml/ClinicMedicineInoutMapper.xml | 93 ---
.../mapper/xml/ClinicMedicineMapper.xml | 66 --
.../mapper/xml/ClinicMedicineStockMapper.xml | 54 --
.../mapper/xml/ClinicPatientUserMapper.xml | 71 --
.../xml/ClinicPrescriptionItemMapper.xml | 73 --
.../mapper/xml/ClinicPrescriptionMapper.xml | 132 ----
.../clinic/param/ClinicAppointmentParam.java | 58 --
.../clinic/param/ClinicDoctorApplyParam.java | 114 ----
.../clinic/param/ClinicDoctorUserParam.java | 86 ---
.../param/ClinicMedicineInoutParam.java | 102 ---
.../clinic/param/ClinicMedicineParam.java | 63 --
.../param/ClinicMedicineStockParam.java | 50 --
.../clinic/param/ClinicPatientUserParam.java | 66 --
.../param/ClinicPrescriptionItemParam.java | 81 ---
.../clinic/param/ClinicPrescriptionParam.java | 101 ---
.../service/ClinicAppointmentService.java | 42 --
.../service/ClinicDoctorApplyService.java | 42 --
.../service/ClinicDoctorUserService.java | 42 --
.../service/ClinicMedicineInoutService.java | 42 --
.../clinic/service/ClinicMedicineService.java | 42 --
.../service/ClinicMedicineStockService.java | 42 --
.../service/ClinicPatientUserService.java | 42 --
.../ClinicPrescriptionItemService.java | 43 --
.../service/ClinicPrescriptionService.java | 45 --
.../impl/ClinicAppointmentServiceImpl.java | 47 --
.../impl/ClinicDoctorApplyServiceImpl.java | 47 --
.../impl/ClinicDoctorUserServiceImpl.java | 47 --
.../impl/ClinicMedicineInoutServiceImpl.java | 47 --
.../impl/ClinicMedicineServiceImpl.java | 47 --
.../impl/ClinicMedicineStockServiceImpl.java | 47 --
.../impl/ClinicPatientUserServiceImpl.java | 47 --
.../ClinicPrescriptionItemServiceImpl.java | 48 --
.../impl/ClinicPrescriptionServiceImpl.java | 75 --
.../cms/controller/CmsAppController.java | 646 ++++++++++++++++++
.../cms/controller/CmsAppFieldController.java | 188 +++++
.../controller/CmsAppSettingController.java | 121 ++++
.../java/com/gxwebsoft/cms/entity/CmsApp.java | 368 ++++++++++
.../com/gxwebsoft/cms/entity/CmsAppField.java | 75 ++
.../gxwebsoft/cms/entity/CmsAppSetting.java | 89 +++
.../cms/mapper/CmsAppFieldMapper.java | 43 ++
.../gxwebsoft/cms/mapper/CmsAppMapper.java | 53 ++
.../cms/mapper/CmsAppSettingMapper.java | 37 +
.../cms/mapper/xml/CmsAppFieldMapper.xml | 82 +++
.../gxwebsoft/cms/mapper/xml/CmsAppMapper.xml | 463 +++++++++++++
.../cms/mapper/xml/CmsAppSettingMapper.xml | 81 +++
.../cms/param/CmsAppFieldImportParam.java | 56 ++
.../gxwebsoft/cms/param/CmsAppFieldParam.java | 66 ++
.../com/gxwebsoft/cms/param/CmsAppParam.java | 231 +++++++
.../cms/param/CmsAppSettingParam.java | 86 +++
.../cms/service/CmsAppFieldService.java | 43 ++
.../gxwebsoft/cms/service/CmsAppService.java | 92 +++
.../cms/service/CmsAppSettingService.java | 42 ++
.../service/impl/CmsAppFieldServiceImpl.java | 54 ++
.../cms/service/impl/CmsAppServiceImpl.java | 587 ++++++++++++++++
.../service/impl/CmsAppServiceImplHelper.java | 239 +++++++
.../impl/CmsAppSettingServiceImpl.java | 47 ++
.../com/gxwebsoft/cms/sql/cms_app_publish.sql | 17 +
.../controller/DormitoryApplyController.java | 128 ----
.../controller/DormitoryBedController.java | 126 ----
.../DormitoryBuildingController.java | 126 ----
.../controller/DormitoryFloorController.java | 126 ----
.../controller/DormitoryRecordController.java | 126 ----
.../dormitory/entity/DormitoryApply.java | 89 ---
.../dormitory/entity/DormitoryBed.java | 91 ---
.../dormitory/entity/DormitoryBuilding.java | 48 --
.../dormitory/entity/DormitoryFloor.java | 56 --
.../dormitory/entity/DormitoryRecord.java | 69 --
.../mapper/DormitoryApplyMapper.java | 37 -
.../dormitory/mapper/DormitoryBedMapper.java | 37 -
.../mapper/DormitoryBuildingMapper.java | 37 -
.../mapper/DormitoryFloorMapper.java | 37 -
.../mapper/DormitoryRecordMapper.java | 37 -
.../mapper/xml/DormitoryApplyMapper.xml | 87 ---
.../mapper/xml/DormitoryBedMapper.xml | 73 --
.../mapper/xml/DormitoryBuildingMapper.xml | 51 --
.../mapper/xml/DormitoryFloorMapper.xml | 55 --
.../mapper/xml/DormitoryRecordMapper.xml | 65 --
.../dormitory/param/DormitoryApplyParam.java | 87 ---
.../dormitory/param/DormitoryBedParam.java | 69 --
.../param/DormitoryBuildingParam.java | 46 --
.../dormitory/param/DormitoryFloorParam.java | 50 --
.../dormitory/param/DormitoryRecordParam.java | 62 --
.../service/DormitoryApplyService.java | 42 --
.../service/DormitoryBedService.java | 42 --
.../service/DormitoryBuildingService.java | 42 --
.../service/DormitoryFloorService.java | 42 --
.../service/DormitoryRecordService.java | 42 --
.../impl/DormitoryApplyServiceImpl.java | 47 --
.../service/impl/DormitoryBedServiceImpl.java | 47 --
.../impl/DormitoryBuildingServiceImpl.java | 47 --
.../impl/DormitoryFloorServiceImpl.java | 47 --
.../impl/DormitoryRecordServiceImpl.java | 47 --
.../controller/EnterpriseController.java | 147 ----
.../enterprise/entity/Enterprise.java | 67 --
.../enterprise/mapper/EnterpriseMapper.java | 13 -
.../mapper/xml/EnterpriseMapper.xml | 7 -
.../enterprise/service/EnterpriseService.java | 13 -
.../service/impl/EnterpriseServiceImpl.java | 17 -
.../impl/CmsAppServiceImplCacheTest.java | 66 ++
179 files changed, 3872 insertions(+), 9566 deletions(-)
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/OllamaClient.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatResponse.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingResponse.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/dto/OllamaMessage.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/client/dto/OllamaTagsResponse.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/config/AiOllamaProperties.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/controller/AiAnalyticsController.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/controller/AiChatController.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/controller/AiKbController.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskResult.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiChatRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiChatResult.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiKbAskRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiKbAskResult.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiKbHit.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiKbIngestResult.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiKbQueryRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiKbQueryResult.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiMessage.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryResult.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsRow.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/entity/AiKbChunk.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/entity/AiKbDocument.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AiKbChunkMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AiKbDocumentMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/mapper/AiShopAnalyticsMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/mapper/xml/AiShopAnalyticsMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/ai/prompt/AiPrompts.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/AiAnalyticsService.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/AiChatService.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/AiKbChunkService.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/AiKbDocumentService.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/AiKbRagService.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/AiShopAnalyticsService.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AiKbChunkServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/service/impl/AiKbDocumentServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/ai/util/AiTextUtil.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/service/QrLoginService.java
delete mode 100644 src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicAppointmentController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorApplyController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorUserController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineInoutController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineStockController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicPatientUserController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionItemController.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/controller/业务中台-排班信息接口对接文档20251114(2).docx
delete mode 100644 src/main/java/com/gxwebsoft/clinic/dto/PrescriptionOrderRequest.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicAppointment.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicDoctorApply.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicDoctorUser.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicMedicine.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicMedicineInout.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicMedicineStock.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicPatientUser.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicPrescription.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/entity/ClinicPrescriptionItem.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicAppointmentMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicDoctorApplyMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicDoctorUserMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicMedicineInoutMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicMedicineMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicMedicineStockMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicPatientUserMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicPrescriptionItemMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/ClinicPrescriptionMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicAppointmentMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicDoctorApplyMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicDoctorUserMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicMedicineInoutMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicMedicineMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicMedicineStockMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicPatientUserMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicPrescriptionItemMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/mapper/xml/ClinicPrescriptionMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicAppointmentParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicDoctorApplyParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicDoctorUserParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicMedicineInoutParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicMedicineParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicMedicineStockParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicPatientUserParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicPrescriptionItemParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/param/ClinicPrescriptionParam.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicAppointmentService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicDoctorApplyService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicDoctorUserService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicMedicineInoutService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicMedicineService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicMedicineStockService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicPatientUserService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicPrescriptionItemService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/ClinicPrescriptionService.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicAppointmentServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicDoctorApplyServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicDoctorUserServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicMedicineInoutServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicMedicineServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicMedicineStockServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicPatientUserServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicPrescriptionItemServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/clinic/service/impl/ClinicPrescriptionServiceImpl.java
create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsAppController.java
create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsAppFieldController.java
create mode 100644 src/main/java/com/gxwebsoft/cms/controller/CmsAppSettingController.java
create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsApp.java
create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsAppField.java
create mode 100644 src/main/java/com/gxwebsoft/cms/entity/CmsAppSetting.java
create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsAppFieldMapper.java
create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsAppMapper.java
create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/CmsAppSettingMapper.java
create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAppFieldMapper.xml
create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAppMapper.xml
create mode 100644 src/main/java/com/gxwebsoft/cms/mapper/xml/CmsAppSettingMapper.xml
create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsAppFieldImportParam.java
create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsAppFieldParam.java
create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsAppParam.java
create mode 100644 src/main/java/com/gxwebsoft/cms/param/CmsAppSettingParam.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsAppFieldService.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsAppService.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/CmsAppSettingService.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsAppFieldServiceImpl.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsAppServiceImpl.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsAppServiceImplHelper.java
create mode 100644 src/main/java/com/gxwebsoft/cms/service/impl/CmsAppSettingServiceImpl.java
create mode 100644 src/main/java/com/gxwebsoft/cms/sql/cms_app_publish.sql
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/controller/DormitoryApplyController.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/controller/DormitoryBedController.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/controller/DormitoryBuildingController.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/controller/DormitoryFloorController.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/controller/DormitoryRecordController.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/entity/DormitoryApply.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/entity/DormitoryBed.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/entity/DormitoryBuilding.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/entity/DormitoryFloor.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/entity/DormitoryRecord.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/DormitoryApplyMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/DormitoryBedMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/DormitoryBuildingMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/DormitoryFloorMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/DormitoryRecordMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/xml/DormitoryApplyMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/xml/DormitoryBedMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/xml/DormitoryBuildingMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/xml/DormitoryFloorMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/mapper/xml/DormitoryRecordMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/param/DormitoryApplyParam.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/param/DormitoryBedParam.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/param/DormitoryBuildingParam.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/param/DormitoryFloorParam.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/param/DormitoryRecordParam.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/DormitoryApplyService.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/DormitoryBedService.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/DormitoryBuildingService.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/DormitoryFloorService.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/DormitoryRecordService.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/impl/DormitoryApplyServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/impl/DormitoryBedServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/impl/DormitoryBuildingServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/impl/DormitoryFloorServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/dormitory/service/impl/DormitoryRecordServiceImpl.java
delete mode 100644 src/main/java/com/gxwebsoft/enterprise/controller/EnterpriseController.java
delete mode 100644 src/main/java/com/gxwebsoft/enterprise/entity/Enterprise.java
delete mode 100644 src/main/java/com/gxwebsoft/enterprise/mapper/EnterpriseMapper.java
delete mode 100644 src/main/java/com/gxwebsoft/enterprise/mapper/xml/EnterpriseMapper.xml
delete mode 100644 src/main/java/com/gxwebsoft/enterprise/service/EnterpriseService.java
delete mode 100644 src/main/java/com/gxwebsoft/enterprise/service/impl/EnterpriseServiceImpl.java
create mode 100644 src/test/java/com/gxwebsoft/cms/service/impl/CmsAppServiceImplCacheTest.java
diff --git a/src/main/java/com/gxwebsoft/ai/client/OllamaClient.java b/src/main/java/com/gxwebsoft/ai/client/OllamaClient.java
deleted file mode 100644
index 6e08d1b..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/OllamaClient.java
+++ /dev/null
@@ -1,194 +0,0 @@
-package com.gxwebsoft.ai.client;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.gxwebsoft.ai.client.dto.*;
-import com.gxwebsoft.ai.config.AiOllamaProperties;
-import com.gxwebsoft.common.core.exception.BusinessException;
-import com.gxwebsoft.common.core.utils.JSONUtil;
-import okhttp3.*;
-import okio.BufferedSource;
-import org.springframework.stereotype.Component;
-
-import javax.annotation.Resource;
-import java.io.IOException;
-import java.time.Duration;
-import java.util.Objects;
-import java.util.function.Consumer;
-
-/**
- * 轻量 Ollama HTTP Client(兼容 /api/chat、/api/embeddings、/api/tags)。
- */
-@Component
-public class OllamaClient {
- @Resource
- private AiOllamaProperties props;
-
- @Resource
- private ObjectMapper objectMapper;
-
- private volatile OkHttpClient http;
-
- private OkHttpClient http() {
- OkHttpClient c = http;
- if (c != null) {
- return c;
- }
- synchronized (this) {
- if (http == null) {
- http = new OkHttpClient.Builder()
- .connectTimeout(Duration.ofMillis(props.getConnectTimeoutMs()))
- .readTimeout(Duration.ofMillis(props.getReadTimeoutMs()))
- .writeTimeout(Duration.ofMillis(props.getWriteTimeoutMs()))
- .build();
- }
- return http;
- }
- }
-
- public OllamaTagsResponse tags() {
- return getJson("/api/tags", OllamaTagsResponse.class);
- }
-
- public OllamaChatResponse chat(OllamaChatRequest req) {
- if (req.getStream() == null) {
- req.setStream(false);
- }
- return postJson("/api/chat", req, OllamaChatResponse.class);
- }
-
- /**
- * 流式对话:Ollama 会返回按行分隔的 JSON。
- */
- public void chatStream(OllamaChatRequest req, Consumer onEvent) {
- Objects.requireNonNull(onEvent, "onEvent");
- if (req.getStream() == null) {
- req.setStream(true);
- }
-
- Request request = buildPost(baseUrl(), "/api/chat", JSONUtil.toJSONString(req));
- try (Response resp = http().newCall(request).execute()) {
- if (!resp.isSuccessful()) {
- throw new BusinessException("Ollama chat stream failed: HTTP " + resp.code());
- }
- ResponseBody body = resp.body();
- if (body == null) {
- throw new BusinessException("Ollama chat stream failed: empty body");
- }
-
- BufferedSource source = body.source();
- String line;
- while ((line = source.readUtf8Line()) != null) {
- line = line.trim();
- if (line.isEmpty()) {
- continue;
- }
- OllamaChatResponse event = objectMapper.readValue(line, OllamaChatResponse.class);
- onEvent.accept(event);
- if (Boolean.TRUE.equals(event.getDone())) {
- break;
- }
- }
- } catch (IOException e) {
- throw new BusinessException("Ollama chat stream IO error: " + e.getMessage());
- }
- }
-
- public OllamaEmbeddingResponse embedding(String prompt) {
- OllamaEmbeddingRequest req = new OllamaEmbeddingRequest();
- req.setModel(props.getEmbedModel());
- req.setPrompt(prompt);
- return postJson("/api/embeddings", req, OllamaEmbeddingResponse.class);
- }
-
- private String baseUrl() {
- if (props.getBaseUrl() == null || props.getBaseUrl().trim().isEmpty()) {
- throw new BusinessException("ai.ollama.base-url 未配置");
- }
- return props.getBaseUrl().trim();
- }
-
- private String fallbackUrl() {
- if (props.getFallbackUrl() == null || props.getFallbackUrl().trim().isEmpty()) {
- return null;
- }
- return props.getFallbackUrl().trim();
- }
-
- private T getJson(String path, Class clazz) {
- try {
- return getJsonOnce(baseUrl(), path, clazz);
- } catch (Exception e) {
- String fb = fallbackUrl();
- if (fb == null) {
- throw e;
- }
- return getJsonOnce(fb, path, clazz);
- }
- }
-
- private T getJsonOnce(String base, String path, Class clazz) {
- Request req = new Request.Builder()
- .url(join(base, path))
- .get()
- .build();
- try (Response resp = http().newCall(req).execute()) {
- if (!resp.isSuccessful()) {
- throw new BusinessException("Ollama GET failed: HTTP " + resp.code());
- }
- ResponseBody body = resp.body();
- if (body == null) {
- throw new BusinessException("Ollama GET failed: empty body");
- }
- return objectMapper.readValue(body.string(), clazz);
- } catch (IOException e) {
- throw new BusinessException("Ollama GET IO error: " + e.getMessage());
- }
- }
-
- private T postJson(String path, Object payload, Class clazz) {
- String json = JSONUtil.toJSONString(payload);
- try {
- return postJsonOnce(baseUrl(), path, json, clazz);
- } catch (Exception e) {
- String fb = fallbackUrl();
- if (fb == null) {
- throw e;
- }
- return postJsonOnce(fb, path, json, clazz);
- }
- }
-
- private T postJsonOnce(String base, String path, String json, Class clazz) {
- Request req = buildPost(base, path, json);
- try (Response resp = http().newCall(req).execute()) {
- if (!resp.isSuccessful()) {
- throw new BusinessException("Ollama POST failed: HTTP " + resp.code());
- }
- ResponseBody body = resp.body();
- if (body == null) {
- throw new BusinessException("Ollama POST failed: empty body");
- }
- return objectMapper.readValue(body.string(), clazz);
- } catch (IOException e) {
- throw new BusinessException("Ollama POST IO error: " + e.getMessage());
- }
- }
-
- private Request buildPost(String base, String path, String json) {
- RequestBody body = RequestBody.create(json, MediaType.parse("application/json; charset=utf-8"));
- return new Request.Builder()
- .url(join(base, path))
- .post(body)
- .build();
- }
-
- private static String join(String base, String path) {
- String b = base;
- if (b.endsWith("/")) {
- b = b.substring(0, b.length() - 1);
- }
- String p = path.startsWith("/") ? path : ("/" + path);
- return b + p;
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatRequest.java b/src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatRequest.java
deleted file mode 100644
index 938894a..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatRequest.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.gxwebsoft.ai.client.dto;
-
-import lombok.Data;
-
-import java.util.List;
-import java.util.Map;
-
-@Data
-public class OllamaChatRequest {
- private String model;
- private List messages;
- private Boolean stream;
-
- /**
- * Ollama options,例如:temperature、top_k、top_p、num_predict...
- */
- private Map options;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatResponse.java b/src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatResponse.java
deleted file mode 100644
index ec9291b..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaChatResponse.java
+++ /dev/null
@@ -1,28 +0,0 @@
-package com.gxwebsoft.ai.client.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Data;
-
-@Data
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class OllamaChatResponse {
- private String model;
-
- @JsonProperty("created_at")
- private String createdAt;
-
- private OllamaMessage message;
-
- private Boolean done;
-
- @JsonProperty("total_duration")
- private Long totalDuration;
-
- @JsonProperty("prompt_eval_count")
- private Integer promptEvalCount;
-
- @JsonProperty("eval_count")
- private Integer evalCount;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingRequest.java b/src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingRequest.java
deleted file mode 100644
index 5629eba..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingRequest.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.gxwebsoft.ai.client.dto;
-
-import lombok.Data;
-
-@Data
-public class OllamaEmbeddingRequest {
- private String model;
-
- /**
- * Ollama embeddings 目前常用字段为 prompt。
- */
- private String prompt;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingResponse.java b/src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingResponse.java
deleted file mode 100644
index 233e863..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaEmbeddingResponse.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.gxwebsoft.ai.client.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class OllamaEmbeddingResponse {
- private List embedding;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaMessage.java b/src/main/java/com/gxwebsoft/ai/client/dto/OllamaMessage.java
deleted file mode 100644
index 6606c69..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaMessage.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.gxwebsoft.ai.client.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class OllamaMessage {
- private String role;
- private String content;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaTagsResponse.java b/src/main/java/com/gxwebsoft/ai/client/dto/OllamaTagsResponse.java
deleted file mode 100644
index c1fe7c0..0000000
--- a/src/main/java/com/gxwebsoft/ai/client/dto/OllamaTagsResponse.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.gxwebsoft.ai.client.dto;
-
-import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@JsonIgnoreProperties(ignoreUnknown = true)
-public class OllamaTagsResponse {
- private List models;
-
- @Data
- @JsonIgnoreProperties(ignoreUnknown = true)
- public static class Model {
- private String name;
- private Long size;
- private String digest;
- private String modified_at;
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/config/AiOllamaProperties.java b/src/main/java/com/gxwebsoft/ai/config/AiOllamaProperties.java
deleted file mode 100644
index e0c1732..0000000
--- a/src/main/java/com/gxwebsoft/ai/config/AiOllamaProperties.java
+++ /dev/null
@@ -1,68 +0,0 @@
-package com.gxwebsoft.ai.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-import org.springframework.stereotype.Component;
-
-/**
- * Ollama API 配置。
- *
- * 说明:本项目通过自建 Ollama 网关提供服务,因此这里用 baseUrl + fallbackUrl。
- */
-@Data
-@Component
-@ConfigurationProperties(prefix = "ai.ollama")
-public class AiOllamaProperties {
- /**
- * 主地址,例如:https://ai-api.websoft.top
- */
- private String baseUrl;
-
- /**
- * 备用地址,例如:http://47.119.165.234:11434
- */
- private String fallbackUrl;
-
- /**
- * 对话模型,例如:qwen3.5:cloud
- */
- private String chatModel;
-
- /**
- * 向量模型,例如:qwen3-embedding:4b
- */
- private String embedModel;
-
- /**
- * HTTP 超时(毫秒)。
- */
- private long connectTimeoutMs = 10_000;
- private long readTimeoutMs = 300_000;
- private long writeTimeoutMs = 60_000;
-
- /**
- * 并发上限(用于 embedding/入库等批处理场景)。
- */
- private int maxConcurrency = 4;
-
- /**
- * RAG:检索候选 chunk 最大数量(避免一次性拉取过多数据导致内存/耗时过高)。
- */
- private int ragMaxCandidates = 2000;
-
- /**
- * RAG:检索返回 topK。
- */
- private int ragTopK = 5;
-
- /**
- * RAG:单个 chunk 最大字符数(用于入库切分)。
- */
- private int ragChunkSize = 800;
-
- /**
- * RAG:chunk 重叠字符数(用于减少语义断裂)。
- */
- private int ragChunkOverlap = 120;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiAnalyticsController.java b/src/main/java/com/gxwebsoft/ai/controller/AiAnalyticsController.java
deleted file mode 100644
index fe72b49..0000000
--- a/src/main/java/com/gxwebsoft/ai/controller/AiAnalyticsController.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package com.gxwebsoft.ai.controller;
-
-import com.gxwebsoft.ai.dto.*;
-import com.gxwebsoft.ai.service.AiAnalyticsService;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-
-@Tag(name = "AI - 订单数据分析")
-@RestController
-@RequestMapping("/api/ai/analytics")
-public class AiAnalyticsController extends BaseController {
- @Resource
- private AiAnalyticsService analyticsService;
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "查询商城订单按天指标(当前租户)")
- @PostMapping("/query")
- public ApiResult query(@RequestBody AiShopMetricsQueryRequest request) {
- Integer tenantId = getTenantId();
- return success(analyticsService.queryShopMetrics(tenantId, request));
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "AI 解析并输出订单分析结论(当前租户)")
- @PostMapping("/ask")
- public ApiResult ask(@RequestBody AiAnalyticsAskRequest request) {
- Integer tenantId = getTenantId();
- return success(analyticsService.askShopAnalytics(tenantId, request));
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiChatController.java b/src/main/java/com/gxwebsoft/ai/controller/AiChatController.java
deleted file mode 100644
index 45d5564..0000000
--- a/src/main/java/com/gxwebsoft/ai/controller/AiChatController.java
+++ /dev/null
@@ -1,95 +0,0 @@
-package com.gxwebsoft.ai.controller;
-
-import com.gxwebsoft.ai.client.OllamaClient;
-import com.gxwebsoft.ai.client.dto.OllamaChatResponse;
-import com.gxwebsoft.ai.client.dto.OllamaTagsResponse;
-import com.gxwebsoft.ai.dto.AiChatRequest;
-import com.gxwebsoft.ai.dto.AiChatResult;
-import com.gxwebsoft.ai.dto.AiMessage;
-import com.gxwebsoft.ai.service.AiChatService;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
-
-import javax.annotation.Resource;
-import java.util.Collections;
-import java.util.LinkedHashMap;
-import java.util.Map;
-import java.util.concurrent.CompletableFuture;
-
-@Tag(name = "AI - 对话")
-@RestController
-@RequestMapping("/api/ai")
-public class AiChatController extends BaseController {
- @Resource
- private OllamaClient ollamaClient;
-
- @Resource
- private AiChatService aiChatService;
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "获取 Ollama 模型列表")
- @GetMapping("/models")
- public ApiResult models() {
- return success(ollamaClient.tags());
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "非流式对话")
- @PostMapping("/chat")
- public ApiResult chat(@RequestBody AiChatRequest request) {
- return success(aiChatService.chat(request));
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "流式对话(SSE)")
- @PostMapping("/chat/stream")
- public SseEmitter chatStream(@RequestBody AiChatRequest request) {
- // 10 分钟超时(可根据前端需要调整)
- SseEmitter emitter = new SseEmitter(10 * 60 * 1000L);
-
- CompletableFuture.runAsync(() -> {
- try {
- aiChatService.chatStream(request,
- delta -> {
- try {
- emitter.send(SseEmitter.event().name("delta").data(delta));
- } catch (Exception e) {
- // 客户端断开会触发 send 异常,这里直接结束即可
- emitter.complete();
- }
- },
- (OllamaChatResponse done) -> {
- try {
- Map meta = new LinkedHashMap<>();
- meta.put("model", done.getModel());
- meta.put("prompt_eval_count", done.getPromptEvalCount());
- meta.put("eval_count", done.getEvalCount());
- meta.put("total_duration", done.getTotalDuration());
- emitter.send(SseEmitter.event().name("done").data(meta));
- } catch (Exception ignored) {
- } finally {
- emitter.complete();
- }
- });
- } catch (Exception e) {
- emitter.completeWithError(e);
- }
- });
-
- return emitter;
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "流式对话(SSE, GET 版本,便于 EventSource)")
- @GetMapping("/chat/stream")
- public SseEmitter chatStreamGet(@RequestParam("prompt") String prompt) {
- AiChatRequest req = new AiChatRequest();
- req.setMessages(Collections.singletonList(new AiMessage("user", prompt)));
- return chatStream(req);
- }
-}
diff --git a/src/main/java/com/gxwebsoft/ai/controller/AiKbController.java b/src/main/java/com/gxwebsoft/ai/controller/AiKbController.java
deleted file mode 100644
index afd2078..0000000
--- a/src/main/java/com/gxwebsoft/ai/controller/AiKbController.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package com.gxwebsoft.ai.controller;
-
-import com.gxwebsoft.ai.dto.*;
-import com.gxwebsoft.ai.service.AiKbRagService;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.annotation.Resource;
-
-@Tag(name = "AI - 知识库(RAG)")
-@RestController
-@RequestMapping("/api/ai/kb")
-public class AiKbController extends BaseController {
- @Resource
- private AiKbRagService ragService;
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "上传文档入库(txt/md/html 优先)")
- @PostMapping("/upload")
- public ApiResult upload(@RequestParam("file") MultipartFile file) {
- Integer tenantId = getTenantId();
- return success(ragService.ingestUpload(tenantId, file));
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "同步 CMS 文章到知识库(当前租户)")
- @PostMapping("/sync/cms")
- public ApiResult syncCms() {
- Integer tenantId = getTenantId();
- return success(ragService.syncCms(tenantId));
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "仅检索(返回 topK 命中)")
- @PostMapping("/query")
- public ApiResult query(@RequestBody AiKbQueryRequest request) {
- Integer tenantId = getTenantId();
- return success(ragService.query(tenantId, request));
- }
-
- @PreAuthorize("isAuthenticated()")
- @Operation(summary = "知识库问答(RAG 生成 + 返回检索结果)")
- @PostMapping("/ask")
- public ApiResult ask(@RequestBody AiKbAskRequest request) {
- Integer tenantId = getTenantId();
- return success(ragService.ask(tenantId, request));
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskRequest.java
deleted file mode 100644
index 085472a..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskRequest.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiAnalyticsAskRequest", description = "AI 数据分析提问")
-public class AiAnalyticsAskRequest {
- private String question;
- private String startDate;
- private String endDate;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskResult.java b/src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskResult.java
deleted file mode 100644
index f11b61d..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiAnalyticsAskResult.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiAnalyticsAskResult", description = "AI 数据分析结果")
-public class AiAnalyticsAskResult {
- private String analysis;
- private AiShopMetricsQueryResult data;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiChatRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AiChatRequest.java
deleted file mode 100644
index eaa90ff..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiChatRequest.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@Schema(name = "AiChatRequest", description = "AI 对话请求")
-public class AiChatRequest {
- @Schema(description = "可选:直接传一句话。若 messages 为空则使用该字段构造 user message")
- private String prompt;
-
- @Schema(description = "可选:OpenAI 风格 messages(role: system/user/assistant)")
- private List messages;
-
- @Schema(description = "可选:覆盖默认模型")
- private String model;
-
- @Schema(description = "是否流式输出(/chat/stream 端点通常忽略此字段)")
- private Boolean stream;
-
- @Schema(description = "temperature")
- private Double temperature;
-
- @Schema(description = "top_k")
- private Integer topK;
-
- @Schema(description = "top_p")
- private Double topP;
-
- @Schema(description = "num_predict(类似 max_tokens)")
- private Integer numPredict;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiChatResult.java b/src/main/java/com/gxwebsoft/ai/dto/AiChatResult.java
deleted file mode 100644
index c38dd41..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiChatResult.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Schema(name = "AiChatResult", description = "AI 对话结果")
-public class AiChatResult {
- private String content;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiKbAskRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AiKbAskRequest.java
deleted file mode 100644
index 199134c..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiKbAskRequest.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiKbAskRequest", description = "知识库问答请求")
-public class AiKbAskRequest {
- private String question;
- private Integer topK;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiKbAskResult.java b/src/main/java/com/gxwebsoft/ai/dto/AiKbAskResult.java
deleted file mode 100644
index a23a480..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiKbAskResult.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiKbAskResult", description = "知识库问答结果")
-public class AiKbAskResult {
- private String answer;
- private AiKbQueryResult retrieval;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiKbHit.java b/src/main/java/com/gxwebsoft/ai/dto/AiKbHit.java
deleted file mode 100644
index e204685..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiKbHit.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiKbHit", description = "知识库命中")
-public class AiKbHit {
- private String chunkId;
- private Integer documentId;
- private String title;
- private Double score;
- private String content;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiKbIngestResult.java b/src/main/java/com/gxwebsoft/ai/dto/AiKbIngestResult.java
deleted file mode 100644
index c14778d..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiKbIngestResult.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiKbIngestResult", description = "知识库入库结果")
-public class AiKbIngestResult {
- private Integer documentId;
- private String title;
- private Integer chunks;
- private Integer updatedDocuments;
- private Integer skippedDocuments;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiKbQueryRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AiKbQueryRequest.java
deleted file mode 100644
index c4ad3c6..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiKbQueryRequest.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiKbQueryRequest", description = "知识库检索请求")
-public class AiKbQueryRequest {
- private String query;
- private Integer topK;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiKbQueryResult.java b/src/main/java/com/gxwebsoft/ai/dto/AiKbQueryResult.java
deleted file mode 100644
index 33b082e..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiKbQueryResult.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@Schema(name = "AiKbQueryResult", description = "知识库检索结果")
-public class AiKbQueryResult {
- private List hits;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiMessage.java b/src/main/java/com/gxwebsoft/ai/dto/AiMessage.java
deleted file mode 100644
index 1e9ba61..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiMessage.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class AiMessage {
- private String role;
- private String content;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryRequest.java b/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryRequest.java
deleted file mode 100644
index ba31384..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryRequest.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(name = "AiShopMetricsQueryRequest", description = "商城订单指标查询请求")
-public class AiShopMetricsQueryRequest {
- @Schema(description = "开始日期(YYYY-MM-DD)")
- private String startDate;
-
- @Schema(description = "结束日期(YYYY-MM-DD),包含该天")
- private String endDate;
-
- @Schema(description = "是否按天分组,默认 true")
- private Boolean groupByDay;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryResult.java b/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryResult.java
deleted file mode 100644
index f4086cc..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsQueryResult.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.List;
-
-@Data
-@Schema(name = "AiShopMetricsQueryResult", description = "商城订单指标查询结果")
-public class AiShopMetricsQueryResult {
- private Integer tenantId;
- private String startDate;
- private String endDate;
- private List rows;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsRow.java b/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsRow.java
deleted file mode 100644
index 747ecdf..0000000
--- a/src/main/java/com/gxwebsoft/ai/dto/AiShopMetricsRow.java
+++ /dev/null
@@ -1,23 +0,0 @@
-package com.gxwebsoft.ai.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.math.BigDecimal;
-
-@Data
-@Schema(name = "AiShopMetricsRow", description = "商城订单指标行(按 tenant/day)")
-public class AiShopMetricsRow {
- private Integer tenantId;
- private String day;
-
- private Long orderCnt;
- private Long paidOrderCnt;
- private BigDecimal gmv;
- private BigDecimal refundAmt;
- private Long payUserCnt;
-
- private BigDecimal aov;
- private BigDecimal payRate;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiKbChunk.java b/src/main/java/com/gxwebsoft/ai/entity/AiKbChunk.java
deleted file mode 100644
index 47ffd0c..0000000
--- a/src/main/java/com/gxwebsoft/ai/entity/AiKbChunk.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package com.gxwebsoft.ai.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
-/**
- * 知识库分段(chunk)。
- */
-@Data
-@Schema(name = "AiKbChunk", description = "AI 知识库分段")
-public class AiKbChunk implements Serializable {
- private static final long serialVersionUID = 1L;
-
- @TableId(value = "id", type = IdType.AUTO)
- private Integer id;
-
- private Integer documentId;
-
- /**
- * 外部引用用的唯一 ID(便于在答案里引用)。
- */
- private String chunkId;
-
- private Integer chunkIndex;
-
- private String title;
-
- private String content;
-
- private String contentHash;
-
- /**
- * embedding JSON(数组),存成文本便于快速落库。
- */
- private String embedding;
-
- /**
- * embedding 的 L2 范数,用于余弦相似度。
- */
- private Double embeddingNorm;
-
- @TableLogic
- private Integer deleted;
-
- private Integer tenantId;
-
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime createTime;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/entity/AiKbDocument.java b/src/main/java/com/gxwebsoft/ai/entity/AiKbDocument.java
deleted file mode 100644
index dac0c3b..0000000
--- a/src/main/java/com/gxwebsoft/ai/entity/AiKbDocument.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.gxwebsoft.ai.entity;
-
-import com.baomidou.mybatisplus.annotation.IdType;
-import com.baomidou.mybatisplus.annotation.TableId;
-import com.baomidou.mybatisplus.annotation.TableLogic;
-import com.fasterxml.jackson.annotation.JsonFormat;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.io.Serializable;
-import java.time.LocalDateTime;
-
-/**
- * 知识库文档(来源:上传、CMS 等)。
- */
-@Data
-@Schema(name = "AiKbDocument", description = "AI 知识库文档")
-public class AiKbDocument implements Serializable {
- private static final long serialVersionUID = 1L;
-
- @TableId(value = "document_id", type = IdType.AUTO)
- private Integer documentId;
-
- private String title;
-
- /**
- * upload / cms
- */
- private String sourceType;
-
- /**
- * 例如 CMS article_id
- */
- private Integer sourceId;
-
- /**
- * 例如文件名、路径等
- */
- private String sourceRef;
-
- /**
- * 文档文本内容哈希(用于增量同步/去重)。
- */
- private String contentHash;
-
- private Integer status;
-
- @TableLogic
- private Integer deleted;
-
- private Integer tenantId;
-
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime updateTime;
-
- @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
- private LocalDateTime createTime;
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiKbChunkMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiKbChunkMapper.java
deleted file mode 100644
index 1628de5..0000000
--- a/src/main/java/com/gxwebsoft/ai/mapper/AiKbChunkMapper.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.gxwebsoft.ai.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.gxwebsoft.ai.entity.AiKbChunk;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface AiKbChunkMapper extends BaseMapper {
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiKbDocumentMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiKbDocumentMapper.java
deleted file mode 100644
index 8dc61c6..0000000
--- a/src/main/java/com/gxwebsoft/ai/mapper/AiKbDocumentMapper.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.gxwebsoft.ai.mapper;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import com.gxwebsoft.ai.entity.AiKbDocument;
-import org.apache.ibatis.annotations.Mapper;
-
-@Mapper
-public interface AiKbDocumentMapper extends BaseMapper {
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/mapper/AiShopAnalyticsMapper.java b/src/main/java/com/gxwebsoft/ai/mapper/AiShopAnalyticsMapper.java
deleted file mode 100644
index 27221ac..0000000
--- a/src/main/java/com/gxwebsoft/ai/mapper/AiShopAnalyticsMapper.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.gxwebsoft.ai.mapper;
-
-import com.gxwebsoft.ai.dto.AiShopMetricsRow;
-import org.apache.ibatis.annotations.Mapper;
-import org.apache.ibatis.annotations.Param;
-
-import java.time.LocalDateTime;
-import java.util.List;
-
-@Mapper
-public interface AiShopAnalyticsMapper {
- List queryMetrics(@Param("tenantId") Integer tenantId,
- @Param("start") LocalDateTime start,
- @Param("end") LocalDateTime end);
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiShopAnalyticsMapper.xml b/src/main/java/com/gxwebsoft/ai/mapper/xml/AiShopAnalyticsMapper.xml
deleted file mode 100644
index 0e745b6..0000000
--- a/src/main/java/com/gxwebsoft/ai/mapper/xml/AiShopAnalyticsMapper.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/src/main/java/com/gxwebsoft/ai/prompt/AiPrompts.java b/src/main/java/com/gxwebsoft/ai/prompt/AiPrompts.java
deleted file mode 100644
index 106ae56..0000000
--- a/src/main/java/com/gxwebsoft/ai/prompt/AiPrompts.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.gxwebsoft.ai.prompt;
-
-/**
- * 统一提示词模板(尽量简短、可控)。
- */
-public class AiPrompts {
- private AiPrompts() {
- }
-
- public static final String SYSTEM_SUPPORT =
- "你是 WebSoft 客服AI。规则:\n" +
- "- 只使用给定的“上下文资料”回答,禁止编造。\n" +
- "- 如果资料不足,直接说“资料不足”,并列出需要补充的信息。\n" +
- "- 答案末尾必须给引用,格式:[source:chunk_id]。\n" +
- "- 输出中文,简洁可执行。\n";
-
- public static final String SYSTEM_ANALYTICS =
- "你是商城订单数据分析助手。你将基于提供的按天指标数据给出结论。\n" +
- "要求:\n" +
- "- 只基于数据陈述,不要编造不存在的数字。\n" +
- "- 输出包含:结论、关键指标变化、异常点、建议的下一步核查。\n" +
- "- 输出中文,简洁。\n";
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/AiAnalyticsService.java b/src/main/java/com/gxwebsoft/ai/service/AiAnalyticsService.java
deleted file mode 100644
index 0f37aec..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/AiAnalyticsService.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package com.gxwebsoft.ai.service;
-
-import cn.hutool.core.util.StrUtil;
-import com.gxwebsoft.ai.dto.*;
-import com.gxwebsoft.ai.prompt.AiPrompts;
-import com.gxwebsoft.common.core.exception.BusinessException;
-import com.gxwebsoft.common.core.utils.JSONUtil;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.time.LocalDate;
-import java.time.format.DateTimeParseException;
-import java.util.Arrays;
-
-@Service
-public class AiAnalyticsService {
- @Resource
- private AiShopAnalyticsService shopAnalyticsService;
- @Resource
- private AiChatService aiChatService;
-
- public AiShopMetricsQueryResult queryShopMetrics(Integer tenantId, AiShopMetricsQueryRequest request) {
- if (tenantId == null) {
- throw new BusinessException("tenantId 不能为空");
- }
- if (request == null) {
- throw new BusinessException("请求不能为空");
- }
- LocalDate start = parseDate(request.getStartDate(), "startDate");
- LocalDate end = parseDate(request.getEndDate(), "endDate");
- if (end.isBefore(start)) {
- throw new BusinessException("endDate 不能早于 startDate");
- }
-
- AiShopMetricsQueryResult r = new AiShopMetricsQueryResult();
- r.setTenantId(tenantId);
- r.setStartDate(start.toString());
- r.setEndDate(end.toString());
- r.setRows(shopAnalyticsService.queryTenantDaily(tenantId, start, end));
- return r;
- }
-
- public AiAnalyticsAskResult askShopAnalytics(Integer tenantId, AiAnalyticsAskRequest request) {
- if (request == null || StrUtil.isBlank(request.getQuestion())) {
- throw new BusinessException("question 不能为空");
- }
- AiShopMetricsQueryRequest q = new AiShopMetricsQueryRequest();
- q.setStartDate(request.getStartDate());
- q.setEndDate(request.getEndDate());
- q.setGroupByDay(true);
- AiShopMetricsQueryResult data = queryShopMetrics(tenantId, q);
-
- String userPrompt =
- "用户问题:\n" + request.getQuestion() + "\n\n" +
- "数据(JSON,字段含义:order_cnt=订单数,paid_order_cnt=已支付订单数,gmv=已支付金额,refund_amt=退款金额,pay_user_cnt=支付用户数,aov=客单价,pay_rate=支付率):\n" +
- JSONUtil.toJSONString(data, true);
-
- AiChatRequest chat = new AiChatRequest();
- chat.setMessages(Arrays.asList(
- new AiMessage("system", AiPrompts.SYSTEM_ANALYTICS),
- new AiMessage("user", userPrompt)
- ));
-
- AiChatResult resp = aiChatService.chat(chat);
- AiAnalyticsAskResult r = new AiAnalyticsAskResult();
- r.setAnalysis(resp.getContent());
- r.setData(data);
- return r;
- }
-
- private static LocalDate parseDate(String s, String field) {
- if (StrUtil.isBlank(s)) {
- throw new BusinessException(field + " 不能为空,格式 YYYY-MM-DD");
- }
- try {
- return LocalDate.parse(s.trim());
- } catch (DateTimeParseException e) {
- throw new BusinessException(field + " 格式错误,需 YYYY-MM-DD");
- }
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/AiChatService.java b/src/main/java/com/gxwebsoft/ai/service/AiChatService.java
deleted file mode 100644
index c457607..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/AiChatService.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package com.gxwebsoft.ai.service;
-
-import cn.hutool.core.util.StrUtil;
-import com.gxwebsoft.ai.client.OllamaClient;
-import com.gxwebsoft.ai.client.dto.OllamaChatRequest;
-import com.gxwebsoft.ai.client.dto.OllamaChatResponse;
-import com.gxwebsoft.ai.client.dto.OllamaMessage;
-import com.gxwebsoft.ai.config.AiOllamaProperties;
-import com.gxwebsoft.ai.dto.AiChatRequest;
-import com.gxwebsoft.ai.dto.AiChatResult;
-import com.gxwebsoft.ai.dto.AiMessage;
-import com.gxwebsoft.common.core.exception.BusinessException;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.*;
-import java.util.function.Consumer;
-
-@Service
-public class AiChatService {
- @Resource
- private AiOllamaProperties props;
-
- @Resource
- private OllamaClient ollamaClient;
-
- public AiChatResult chat(AiChatRequest request) {
- OllamaChatRequest req = buildChatRequest(request, false);
- OllamaChatResponse resp = ollamaClient.chat(req);
- String content = resp != null && resp.getMessage() != null ? resp.getMessage().getContent() : null;
- return new AiChatResult(content == null ? "" : content);
- }
-
- public void chatStream(AiChatRequest request, Consumer onDelta, Consumer onFinal) {
- Objects.requireNonNull(onDelta, "onDelta");
- OllamaChatRequest req = buildChatRequest(request, true);
- ollamaClient.chatStream(req, event -> {
- String delta = event != null && event.getMessage() != null ? event.getMessage().getContent() : null;
- if (StrUtil.isNotBlank(delta)) {
- onDelta.accept(delta);
- }
- if (Boolean.TRUE.equals(event.getDone()) && onFinal != null) {
- onFinal.accept(event);
- }
- });
- }
-
- private OllamaChatRequest buildChatRequest(AiChatRequest request, boolean stream) {
- if (request == null) {
- throw new BusinessException("请求不能为空");
- }
-
- List messages = request.getMessages();
- if ((messages == null || messages.isEmpty()) && StrUtil.isBlank(request.getPrompt())) {
- throw new BusinessException("prompt 或 messages 不能为空");
- }
- if (messages == null || messages.isEmpty()) {
- messages = Collections.singletonList(new AiMessage("user", request.getPrompt()));
- }
-
- List ollamaMessages = new ArrayList<>();
- for (AiMessage m : messages) {
- if (m == null || StrUtil.isBlank(m.getRole()) || m.getContent() == null) {
- continue;
- }
- ollamaMessages.add(new OllamaMessage(m.getRole(), m.getContent()));
- }
- if (ollamaMessages.isEmpty()) {
- throw new BusinessException("messages 为空或无有效内容");
- }
-
- Map options = new HashMap<>();
- if (request.getTemperature() != null) {
- options.put("temperature", request.getTemperature());
- }
- if (request.getTopK() != null) {
- options.put("top_k", request.getTopK());
- }
- if (request.getTopP() != null) {
- options.put("top_p", request.getTopP());
- }
- if (request.getNumPredict() != null) {
- options.put("num_predict", request.getNumPredict());
- }
-
- OllamaChatRequest req = new OllamaChatRequest();
- req.setModel(StrUtil.blankToDefault(request.getModel(), props.getChatModel()));
- req.setMessages(ollamaMessages);
- req.setStream(stream);
- req.setOptions(options.isEmpty() ? null : options);
- return req;
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/AiKbChunkService.java b/src/main/java/com/gxwebsoft/ai/service/AiKbChunkService.java
deleted file mode 100644
index 0bcc717..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/AiKbChunkService.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.gxwebsoft.ai.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.gxwebsoft.ai.entity.AiKbChunk;
-
-public interface AiKbChunkService extends IService {
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/AiKbDocumentService.java b/src/main/java/com/gxwebsoft/ai/service/AiKbDocumentService.java
deleted file mode 100644
index 601f778..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/AiKbDocumentService.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.gxwebsoft.ai.service;
-
-import com.baomidou.mybatisplus.extension.service.IService;
-import com.gxwebsoft.ai.entity.AiKbDocument;
-
-public interface AiKbDocumentService extends IService {
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/AiKbRagService.java b/src/main/java/com/gxwebsoft/ai/service/AiKbRagService.java
deleted file mode 100644
index 04c6c1f..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/AiKbRagService.java
+++ /dev/null
@@ -1,426 +0,0 @@
-package com.gxwebsoft.ai.service;
-
-import cn.hutool.core.util.ObjectUtil;
-import cn.hutool.core.util.StrUtil;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.gxwebsoft.ai.client.OllamaClient;
-import com.gxwebsoft.ai.client.dto.OllamaEmbeddingResponse;
-import com.gxwebsoft.ai.config.AiOllamaProperties;
-import com.gxwebsoft.ai.dto.*;
-import com.gxwebsoft.ai.entity.AiKbChunk;
-import com.gxwebsoft.ai.entity.AiKbDocument;
-import com.gxwebsoft.ai.prompt.AiPrompts;
-import com.gxwebsoft.ai.util.AiTextUtil;
-import com.gxwebsoft.cms.entity.CmsArticle;
-import com.gxwebsoft.cms.entity.CmsArticleContent;
-import com.gxwebsoft.cms.service.CmsArticleContentService;
-import com.gxwebsoft.cms.service.CmsArticleService;
-import com.gxwebsoft.common.core.exception.BusinessException;
-import com.gxwebsoft.common.core.utils.JSONUtil;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.tika.Tika;
-import org.springframework.stereotype.Service;
-import org.springframework.web.multipart.MultipartFile;
-
-import javax.annotation.Resource;
-import java.nio.charset.StandardCharsets;
-import java.time.LocalDateTime;
-import java.util.*;
-import java.util.concurrent.*;
-import java.util.stream.Collectors;
-
-@Service
-public class AiKbRagService {
- @Resource
- private AiOllamaProperties props;
- @Resource
- private OllamaClient ollamaClient;
- @Resource
- private AiKbDocumentService documentService;
- @Resource
- private AiKbChunkService chunkService;
- @Resource
- private CmsArticleService cmsArticleService;
- @Resource
- private CmsArticleContentService cmsArticleContentService;
- @Resource
- private AiChatService aiChatService;
- @Resource
- private ObjectMapper objectMapper;
-
- private final Tika tika = new Tika();
- private volatile ExecutorService embedPool;
-
- private ExecutorService pool() {
- ExecutorService p = embedPool;
- if (p != null) {
- return p;
- }
- synchronized (this) {
- if (embedPool == null) {
- embedPool = Executors.newFixedThreadPool(Math.max(1, props.getMaxConcurrency()));
- }
- return embedPool;
- }
- }
-
- public AiKbIngestResult ingestUpload(Integer tenantId, MultipartFile file) {
- if (tenantId == null) {
- throw new BusinessException("tenantId 不能为空");
- }
- if (file == null || file.isEmpty()) {
- throw new BusinessException("文件不能为空");
- }
- String title = StrUtil.blankToDefault(file.getOriginalFilename(), "upload");
- String text = extractText(file);
- if (StrUtil.isBlank(text)) {
- throw new BusinessException("无法解析文件内容,请上传 txt/md/html 等可解析文本");
- }
-
- String contentHash = AiTextUtil.sha256(text);
- AiKbDocument doc = new AiKbDocument();
- doc.setTitle(title);
- doc.setSourceType("upload");
- doc.setSourceRef(title);
- doc.setContentHash(contentHash);
- doc.setStatus(0);
- doc.setTenantId(tenantId);
- doc.setCreateTime(LocalDateTime.now());
- doc.setUpdateTime(LocalDateTime.now());
- documentService.save(doc);
-
- int chunks = ingestChunks(doc, text);
-
- AiKbIngestResult r = new AiKbIngestResult();
- r.setDocumentId(doc.getDocumentId());
- r.setTitle(doc.getTitle());
- r.setChunks(chunks);
- r.setUpdatedDocuments(1);
- r.setSkippedDocuments(0);
- return r;
- }
-
- /**
- * 同步 CMS(仅当前 tenant)。
- */
- public AiKbIngestResult syncCms(Integer tenantId) {
- if (tenantId == null) {
- throw new BusinessException("tenantId 不能为空");
- }
- // 仅同步“已发布且未删除”的文章
- List articles = cmsArticleService.list(new LambdaQueryWrapper()
- .eq(CmsArticle::getTenantId, tenantId)
- .eq(CmsArticle::getDeleted, 0)
- .eq(CmsArticle::getStatus, 0));
- if (articles == null || articles.isEmpty()) {
- AiKbIngestResult r = new AiKbIngestResult();
- r.setUpdatedDocuments(0);
- r.setSkippedDocuments(0);
- r.setChunks(0);
- return r;
- }
-
- Set articleIds = articles.stream().map(CmsArticle::getArticleId).collect(Collectors.toSet());
- List contents = cmsArticleContentService.list(new LambdaQueryWrapper()
- .in(CmsArticleContent::getArticleId, articleIds));
-
- Map contentByArticle = contents.stream()
- .collect(Collectors.toMap(
- CmsArticleContent::getArticleId,
- c -> c,
- (a, b) -> (a.getCreateTime() != null && b.getCreateTime() != null && a.getCreateTime().isAfter(b.getCreateTime())) ? a : b
- ));
-
- int updatedDocs = 0;
- int skippedDocs = 0;
- int totalChunks = 0;
-
- for (CmsArticle a : articles) {
- CmsArticleContent c = contentByArticle.get(a.getArticleId());
- String raw = "";
- if (a.getOverview() != null) {
- raw += a.getOverview() + "\n";
- }
- if (c != null && c.getContent() != null) {
- raw += c.getContent();
- }
- String text = a.getTitle() + "\n" + AiTextUtil.stripHtml(raw);
- text = AiTextUtil.normalizeWhitespace(text);
- if (StrUtil.isBlank(text)) {
- continue;
- }
- String hash = AiTextUtil.sha256(text);
-
- AiKbDocument existing = documentService.getOne(new LambdaQueryWrapper()
- .eq(AiKbDocument::getTenantId, tenantId)
- .eq(AiKbDocument::getSourceType, "cms")
- .eq(AiKbDocument::getSourceId, a.getArticleId())
- .last("limit 1"));
-
- if (existing != null && StrUtil.equals(existing.getContentHash(), hash)) {
- skippedDocs++;
- continue;
- }
-
- AiKbDocument doc;
- if (existing == null) {
- doc = new AiKbDocument();
- doc.setTitle(a.getTitle());
- doc.setSourceType("cms");
- doc.setSourceId(a.getArticleId());
- doc.setSourceRef(a.getCode());
- doc.setContentHash(hash);
- doc.setStatus(0);
- doc.setTenantId(tenantId);
- doc.setCreateTime(LocalDateTime.now());
- doc.setUpdateTime(LocalDateTime.now());
- documentService.save(doc);
- } else {
- doc = existing;
- doc.setTitle(a.getTitle());
- doc.setSourceRef(a.getCode());
- doc.setContentHash(hash);
- doc.setUpdateTime(LocalDateTime.now());
- documentService.updateById(doc);
- // 重新入库:先删除旧 chunk
- chunkService.remove(new LambdaQueryWrapper().eq(AiKbChunk::getDocumentId, doc.getDocumentId()));
- }
-
- int chunks = ingestChunks(doc, text);
- totalChunks += chunks;
- updatedDocs++;
- }
-
- AiKbIngestResult r = new AiKbIngestResult();
- r.setUpdatedDocuments(updatedDocs);
- r.setSkippedDocuments(skippedDocs);
- r.setChunks(totalChunks);
- return r;
- }
-
- public AiKbQueryResult query(Integer tenantId, AiKbQueryRequest request) {
- if (tenantId == null) {
- throw new BusinessException("tenantId 不能为空");
- }
- if (request == null || StrUtil.isBlank(request.getQuery())) {
- throw new BusinessException("query 不能为空");
- }
- int topK = request.getTopK() != null ? request.getTopK() : props.getRagTopK();
- topK = Math.max(1, Math.min(20, topK));
-
- float[] qEmb = embedding(request.getQuery());
- float qNorm = l2(qEmb);
- if (qNorm == 0f) {
- throw new BusinessException("query embedding 为空");
- }
-
- // MVP:按 tenant 拉取最新 N 条候选 chunk,再做余弦相似度排序
- List candidates = chunkService.list(new LambdaQueryWrapper()
- .eq(AiKbChunk::getTenantId, tenantId)
- .orderByDesc(AiKbChunk::getId)
- .last("limit " + props.getRagMaxCandidates()));
-
- PriorityQueue pq = new PriorityQueue<>(Comparator.comparingDouble(h -> h.getScore() == null ? -1d : h.getScore()));
- for (AiKbChunk c : candidates) {
- if (StrUtil.isBlank(c.getEmbedding())) {
- continue;
- }
- float[] cEmb = parseEmbedding(c.getEmbedding());
- if (cEmb == null || cEmb.length == 0) {
- continue;
- }
- Double cNormD = c.getEmbeddingNorm();
- float cNorm = cNormD == null ? l2(cEmb) : cNormD.floatValue();
- if (cNorm == 0f) {
- continue;
- }
- double score = dot(qEmb, cEmb) / (qNorm * cNorm);
- AiKbHit hit = new AiKbHit();
- hit.setChunkId(c.getChunkId());
- hit.setDocumentId(c.getDocumentId());
- hit.setTitle(StrUtil.blankToDefault(c.getTitle(), ""));
- hit.setScore(score);
- // 返回给前端时避免过长
- hit.setContent(clip(c.getContent(), 900));
-
- if (pq.size() < topK) {
- pq.add(hit);
- } else if (hit.getScore() != null && hit.getScore() > pq.peek().getScore()) {
- pq.poll();
- pq.add(hit);
- }
- }
-
- List hits = new ArrayList<>(pq);
- hits.sort((a, b) -> Double.compare(b.getScore(), a.getScore()));
- AiKbQueryResult r = new AiKbQueryResult();
- r.setHits(hits);
- return r;
- }
-
- public AiKbAskResult ask(Integer tenantId, AiKbAskRequest request) {
- if (request == null || StrUtil.isBlank(request.getQuestion())) {
- throw new BusinessException("question 不能为空");
- }
- AiKbQueryRequest q = new AiKbQueryRequest();
- q.setQuery(request.getQuestion());
- q.setTopK(request.getTopK());
- AiKbQueryResult retrieval = query(tenantId, q);
-
- String context = buildContext(retrieval);
- String userPrompt = "上下文资料:\n" + context + "\n\n用户问题:\n" + request.getQuestion();
-
- AiChatRequest chatReq = new AiChatRequest();
- chatReq.setMessages(Arrays.asList(
- new AiMessage("system", AiPrompts.SYSTEM_SUPPORT),
- new AiMessage("user", userPrompt)
- ));
- AiChatResult chat = aiChatService.chat(chatReq);
-
- AiKbAskResult r = new AiKbAskResult();
- r.setAnswer(chat.getContent());
- r.setRetrieval(retrieval);
- return r;
- }
-
- private int ingestChunks(AiKbDocument doc, String text) {
- List chunks = AiTextUtil.chunkText(text, props.getRagChunkSize(), props.getRagChunkOverlap());
- if (chunks.isEmpty()) {
- return 0;
- }
- LocalDateTime now = LocalDateTime.now();
-
- List> futures = new ArrayList<>(chunks.size());
- for (int i = 0; i < chunks.size(); i++) {
- final int idx = i;
- final String chunkText = chunks.get(i);
- futures.add(CompletableFuture.supplyAsync(() -> {
- OllamaEmbeddingResponse emb = ollamaClient.embedding(chunkText);
- if (emb == null || emb.getEmbedding() == null || emb.getEmbedding().isEmpty()) {
- throw new BusinessException("embedding 生成失败");
- }
- float[] v = toFloat(emb.getEmbedding());
- float norm = l2(v);
- AiKbChunk c = new AiKbChunk();
- c.setDocumentId(doc.getDocumentId());
- c.setChunkId(UUID.randomUUID().toString().replace("-", ""));
- c.setChunkIndex(idx);
- c.setTitle(doc.getTitle());
- c.setContent(chunkText);
- c.setContentHash(AiTextUtil.sha256(chunkText));
- c.setEmbedding(JSONUtil.toJSONString(emb.getEmbedding()));
- c.setEmbeddingNorm((double) norm);
- c.setTenantId(doc.getTenantId());
- c.setCreateTime(now);
- c.setDeleted(0);
- return c;
- }, pool()));
- }
-
- List entities = futures.stream().map(f -> {
- try {
- return f.get(10, TimeUnit.MINUTES);
- } catch (Exception e) {
- throw new BusinessException("embedding 批处理失败: " + e.getMessage());
- }
- }).collect(Collectors.toList());
-
- chunkService.saveBatch(entities);
- return entities.size();
- }
-
- private String extractText(MultipartFile file) {
- try {
- String contentType = file.getContentType();
- String filename = file.getOriginalFilename();
-
- // 优先:对纯文本直接读 UTF-8
- if ((contentType != null && contentType.startsWith("text/"))
- || (filename != null && (filename.endsWith(".txt") || filename.endsWith(".md") || filename.endsWith(".html") || filename.endsWith(".htm")))) {
- return AiTextUtil.normalizeWhitespace(new String(file.getBytes(), StandardCharsets.UTF_8));
- }
-
- // 尝试用 tika 解析(注意:当前依赖为 tika-core,解析能力有限)
- String parsed = tika.parseToString(file.getInputStream());
- return AiTextUtil.normalizeWhitespace(parsed);
- } catch (Exception e) {
- return "";
- }
- }
-
- private float[] embedding(String text) {
- OllamaEmbeddingResponse emb = ollamaClient.embedding(text);
- if (emb == null || emb.getEmbedding() == null || emb.getEmbedding().isEmpty()) {
- throw new BusinessException("embedding 生成失败");
- }
- return toFloat(emb.getEmbedding());
- }
-
- private float[] parseEmbedding(String json) {
- try {
- // embedding 是一维数组,存储为 JSON 文本
- double[] d = ObjectUtil.isEmpty(json) ? null : objectMapper.readValue(json, double[].class);
- if (d == null || d.length == 0) {
- return null;
- }
- float[] f = new float[d.length];
- for (int i = 0; i < d.length; i++) {
- f[i] = (float) d[i];
- }
- return f;
- } catch (Exception e) {
- return null;
- }
- }
-
- private static float[] toFloat(List v) {
- float[] out = new float[v.size()];
- for (int i = 0; i < v.size(); i++) {
- Double d = v.get(i);
- out[i] = d == null ? 0f : d.floatValue();
- }
- return out;
- }
-
- private static float dot(float[] a, float[] b) {
- int n = Math.min(a.length, b.length);
- double s = 0d;
- for (int i = 0; i < n; i++) {
- s += (double) a[i] * (double) b[i];
- }
- return (float) s;
- }
-
- private static float l2(float[] a) {
- double s = 0d;
- for (float v : a) {
- s += (double) v * (double) v;
- }
- return (float) Math.sqrt(s);
- }
-
- private static String clip(String s, int max) {
- if (s == null) {
- return "";
- }
- if (s.length() <= max) {
- return s;
- }
- return s.substring(0, max) + "...";
- }
-
- private static String buildContext(AiKbQueryResult retrieval) {
- if (retrieval == null || retrieval.getHits() == null || retrieval.getHits().isEmpty()) {
- return "(无)";
- }
- StringBuilder sb = new StringBuilder();
- for (AiKbHit h : retrieval.getHits()) {
- sb.append("[source:").append(h.getChunkId()).append("] ");
- if (StrUtil.isNotBlank(h.getTitle())) {
- sb.append(h.getTitle()).append("\n");
- }
- sb.append(h.getContent()).append("\n\n");
- }
- return sb.toString();
- }
-}
diff --git a/src/main/java/com/gxwebsoft/ai/service/AiShopAnalyticsService.java b/src/main/java/com/gxwebsoft/ai/service/AiShopAnalyticsService.java
deleted file mode 100644
index 09aad90..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/AiShopAnalyticsService.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.gxwebsoft.ai.service;
-
-import com.gxwebsoft.ai.dto.AiShopMetricsRow;
-import com.gxwebsoft.ai.mapper.AiShopAnalyticsMapper;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.math.BigDecimal;
-import java.math.RoundingMode;
-import java.time.LocalDate;
-import java.time.LocalDateTime;
-import java.util.List;
-
-@Service
-public class AiShopAnalyticsService {
- @Resource
- private AiShopAnalyticsMapper mapper;
-
- public List queryTenantDaily(Integer tenantId, LocalDate startDate, LocalDate endDateInclusive) {
- LocalDateTime start = startDate.atStartOfDay();
- LocalDateTime endExclusive = endDateInclusive.plusDays(1).atStartOfDay();
- List rows = mapper.queryMetrics(tenantId, start, endExclusive);
- if (rows == null) {
- return null;
- }
- for (AiShopMetricsRow r : rows) {
- long orderCnt = r.getOrderCnt() == null ? 0L : r.getOrderCnt();
- long paidCnt = r.getPaidOrderCnt() == null ? 0L : r.getPaidOrderCnt();
- BigDecimal gmv = r.getGmv() == null ? BigDecimal.ZERO : r.getGmv();
-
- if (paidCnt > 0) {
- r.setAov(gmv.divide(BigDecimal.valueOf(paidCnt), 4, RoundingMode.HALF_UP));
- } else {
- r.setAov(BigDecimal.ZERO);
- }
- if (orderCnt > 0) {
- r.setPayRate(BigDecimal.valueOf(paidCnt)
- .divide(BigDecimal.valueOf(orderCnt), 4, RoundingMode.HALF_UP));
- } else {
- r.setPayRate(BigDecimal.ZERO);
- }
- }
- return rows;
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiKbChunkServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiKbChunkServiceImpl.java
deleted file mode 100644
index de9e157..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/impl/AiKbChunkServiceImpl.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.gxwebsoft.ai.service.impl;
-
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.gxwebsoft.ai.entity.AiKbChunk;
-import com.gxwebsoft.ai.mapper.AiKbChunkMapper;
-import com.gxwebsoft.ai.service.AiKbChunkService;
-import org.springframework.stereotype.Service;
-
-@Service
-public class AiKbChunkServiceImpl extends ServiceImpl implements AiKbChunkService {
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/service/impl/AiKbDocumentServiceImpl.java b/src/main/java/com/gxwebsoft/ai/service/impl/AiKbDocumentServiceImpl.java
deleted file mode 100644
index 7295e96..0000000
--- a/src/main/java/com/gxwebsoft/ai/service/impl/AiKbDocumentServiceImpl.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.gxwebsoft.ai.service.impl;
-
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import com.gxwebsoft.ai.entity.AiKbDocument;
-import com.gxwebsoft.ai.mapper.AiKbDocumentMapper;
-import com.gxwebsoft.ai.service.AiKbDocumentService;
-import org.springframework.stereotype.Service;
-
-@Service
-public class AiKbDocumentServiceImpl extends ServiceImpl implements AiKbDocumentService {
-}
-
diff --git a/src/main/java/com/gxwebsoft/ai/util/AiTextUtil.java b/src/main/java/com/gxwebsoft/ai/util/AiTextUtil.java
deleted file mode 100644
index d7044f6..0000000
--- a/src/main/java/com/gxwebsoft/ai/util/AiTextUtil.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package com.gxwebsoft.ai.util;
-
-import cn.hutool.core.util.StrUtil;
-
-import java.nio.charset.StandardCharsets;
-import java.security.MessageDigest;
-import java.util.ArrayList;
-import java.util.List;
-
-public class AiTextUtil {
- private AiTextUtil() {
- }
-
- public static String sha256(String s) {
- if (s == null) {
- return null;
- }
- try {
- MessageDigest md = MessageDigest.getInstance("SHA-256");
- byte[] dig = md.digest(s.getBytes(StandardCharsets.UTF_8));
- StringBuilder sb = new StringBuilder(dig.length * 2);
- for (byte b : dig) {
- sb.append(String.format("%02x", b));
- }
- return sb.toString();
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
-
- /**
- * 很轻量的 HTML 转纯文本(不追求完美,只用于知识库入库前清洗)。
- */
- public static String stripHtml(String html) {
- if (StrUtil.isBlank(html)) {
- return "";
- }
- String s = html;
- s = s.replaceAll("(?is)", " ");
- s = s.replaceAll("(?is)", " ");
- s = s.replaceAll("(?is)
", "\n");
- s = s.replaceAll("(?is)
", "\n");
- s = s.replaceAll("(?is)<[^>]+>", " ");
- // 常见 HTML 实体最小处理
- s = s.replace(" ", " ");
- s = s.replace("<", "<").replace(">", ">").replace("&", "&").replace(""", "\"");
- return normalizeWhitespace(s);
- }
-
- public static String normalizeWhitespace(String s) {
- if (s == null) {
- return "";
- }
- // 合并空白,保留换行用于 chunking
- String x = s.replace("\r", "\n");
- x = x.replaceAll("[\\t\\f\\u000B]+", " ");
- x = x.replaceAll("[ ]{2,}", " ");
- x = x.replaceAll("\\n{3,}", "\n\n");
- return x.trim();
- }
-
- /**
- * 按字符数切分,并做固定 overlap。
- */
- public static List chunkText(String text, int chunkSize, int overlap) {
- String s = normalizeWhitespace(text);
- List out = new ArrayList<>();
- if (StrUtil.isBlank(s)) {
- return out;
- }
- if (chunkSize <= 0) {
- chunkSize = 800;
- }
- if (overlap < 0) {
- overlap = 0;
- }
- if (overlap >= chunkSize) {
- overlap = Math.max(0, chunkSize / 5);
- }
-
- int n = s.length();
- int start = 0;
- while (start < n) {
- int end = Math.min(n, start + chunkSize);
- String chunk = s.substring(start, end).trim();
- if (!chunk.isEmpty()) {
- out.add(chunk);
- }
- if (end >= n) {
- break;
- }
- start = end - overlap;
- if (start < 0) {
- start = 0;
- }
- }
- return out;
- }
-}
-
diff --git a/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java b/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java
deleted file mode 100644
index d296d82..0000000
--- a/src/main/java/com/gxwebsoft/auto/controller/QrLoginController.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package com.gxwebsoft.auto.controller;
-
-import com.gxwebsoft.auto.dto.QrLoginConfirmRequest;
-import com.gxwebsoft.auto.dto.QrLoginGenerateResponse;
-import com.gxwebsoft.auto.dto.QrLoginStatusResponse;
-import com.gxwebsoft.auto.service.QrLoginService;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.ApiResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.*;
-
-import javax.validation.Valid;
-
-/**
- * 认证模块
- *
- * @author 科技小王子
- * @since 2025-03-06 22:50:25
- */
-@Tag(name = "认证模块")
-@RestController
-@RequestMapping("/api/qr-login")
-public class QrLoginController extends BaseController {
-
- @Autowired
- private QrLoginService qrLoginService;
-
- /**
- * 生成扫码登录token
- */
- @Operation(summary = "生成扫码登录token")
- @PostMapping("/generate")
- public ApiResult> generateQrLoginToken() {
- try {
- QrLoginGenerateResponse response = qrLoginService.generateQrLoginToken();
- return success("生成成功", response);
- } catch (Exception e) {
- return fail(e.getMessage());
- }
- }
-
- /**
- * 检查扫码登录状态
- */
- @Operation(summary = "检查扫码登录状态")
- @GetMapping("/status/{token}")
- public ApiResult> checkQrLoginStatus(
- @Parameter(description = "扫码登录token") @PathVariable String token) {
- try {
- QrLoginStatusResponse response = qrLoginService.checkQrLoginStatus(token);
- return success("查询成功", response);
- } catch (Exception e) {
- return fail(e.getMessage());
- }
- }
-
- /**
- * 确认扫码登录
- */
- @Operation(summary = "确认扫码登录")
- @PostMapping("/confirm")
- public ApiResult> confirmQrLogin(@Valid @RequestBody QrLoginConfirmRequest request) {
- try {
- QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request);
- return success("确认成功", response);
- } catch (Exception e) {
- return fail(e.getMessage());
- }
- }
-
- /**
- * 扫码操作(可选接口,用于移动端扫码后更新状态)
- */
- @Operation(summary = "扫码操作")
- @PostMapping("/scan/{token}")
- public ApiResult> scanQrCode(@Parameter(description = "扫码登录token") @PathVariable String token) {
- try {
- boolean result = qrLoginService.scanQrCode(token);
- return success("操作成功", result);
- } catch (Exception e) {
- return fail(e.getMessage());
- }
- }
-
- /**
- * 微信小程序扫码登录确认(便捷接口)
- */
- @Operation(summary = "微信小程序扫码登录确认")
- @PostMapping("/wechat-confirm")
- public ApiResult> wechatMiniProgramConfirm(@Valid @RequestBody QrLoginConfirmRequest request) {
- try {
- // 设置平台为微信小程序
- request.setPlatform("miniprogram");
- QrLoginStatusResponse response = qrLoginService.confirmQrLogin(request);
- return success("微信小程序登录确认成功", response);
- } catch (Exception e) {
- return fail(e.getMessage());
- }
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java
deleted file mode 100644
index f3b423e..0000000
--- a/src/main/java/com/gxwebsoft/auto/dto/QrLoginConfirmRequest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.gxwebsoft.auto.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import javax.validation.constraints.NotBlank;
-
-/**
- * 扫码登录确认请求
- *
- * @author 科技小王子
- * @since 2025-08-31
- */
-@Data
-@Schema(description = "扫码登录确认请求")
-public class QrLoginConfirmRequest {
-
- @Schema(description = "扫码登录token")
- @NotBlank(message = "token不能为空")
- private String token;
-
- @Schema(description = "用户ID")
- private Integer userId;
-
- @Schema(description = "登录平台: web-网页端, app-移动应用, miniprogram-微信小程序")
- private String platform;
-
- @Schema(description = "微信小程序相关信息")
- private WechatMiniProgramInfo wechatInfo;
-
- /**
- * 微信小程序信息
- */
- @Data
- @Schema(description = "微信小程序信息")
- public static class WechatMiniProgramInfo {
- @Schema(description = "微信openid")
- private String openid;
-
- @Schema(description = "微信unionid")
- private String unionid;
-
- @Schema(description = "微信昵称")
- private String nickname;
-
- @Schema(description = "微信头像")
- private String avatar;
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java
deleted file mode 100644
index 563bf1d..0000000
--- a/src/main/java/com/gxwebsoft/auto/dto/QrLoginData.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.gxwebsoft.auto.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import java.time.LocalDateTime;
-
-/**
- * 扫码登录数据模型
- *
- * @author 科技小王子
- * @since 2025-08-31
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class QrLoginData {
-
- /**
- * 扫码登录token
- */
- private String token;
-
- /**
- * 状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期
- */
- private String status;
-
- /**
- * 用户ID(扫码确认后设置)
- */
- private Integer userId;
-
- /**
- * 用户名(扫码确认后设置)
- */
- private String username;
-
- /**
- * 创建时间
- */
- private LocalDateTime createTime;
-
- /**
- * 过期时间
- */
- private LocalDateTime expireTime;
-
- /**
- * JWT访问令牌(确认后生成)
- */
- private String accessToken;
-
-}
diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java
deleted file mode 100644
index f0b69e5..0000000
--- a/src/main/java/com/gxwebsoft/auto/dto/QrLoginGenerateResponse.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.gxwebsoft.auto.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 扫码登录生成响应
- *
- * @author 科技小王子
- * @since 2025-08-31
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Schema(description = "扫码登录生成响应")
-public class QrLoginGenerateResponse {
-
- @Schema(description = "扫码登录token")
- private String token;
-
- @Schema(description = "二维码内容")
- private String qrCode;
-
- @Schema(description = "过期时间(秒)")
- private Long expiresIn;
-
-}
diff --git a/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java b/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java
deleted file mode 100644
index 1eb0d4a..0000000
--- a/src/main/java/com/gxwebsoft/auto/dto/QrLoginStatusResponse.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.gxwebsoft.auto.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-/**
- * 扫码登录状态响应
- *
- * @author 科技小王子
- * @since 2025-08-31
- */
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Schema(description = "扫码登录状态响应")
-public class QrLoginStatusResponse {
-
- @Schema(description = "状态: pending-等待扫码, scanned-已扫码, confirmed-已确认, expired-已过期")
- private String status;
-
- @Schema(description = "JWT访问令牌(仅在confirmed状态时返回)")
- private String accessToken;
-
- @Schema(description = "用户信息(仅在confirmed状态时返回)")
- private Object userInfo;
-
- @Schema(description = "剩余过期时间(秒)")
- private Long expiresIn;
-
-}
diff --git a/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java b/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java
deleted file mode 100644
index 85ed28f..0000000
--- a/src/main/java/com/gxwebsoft/auto/service/QrLoginService.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.gxwebsoft.auto.service;
-
-import com.gxwebsoft.auto.dto.QrLoginConfirmRequest;
-import com.gxwebsoft.auto.dto.QrLoginGenerateResponse;
-import com.gxwebsoft.auto.dto.QrLoginStatusResponse;
-
-/**
- * 扫码登录服务接口
- *
- * @author 科技小王子
- * @since 2025-08-31
- */
-public interface QrLoginService {
-
- /**
- * 生成扫码登录token
- *
- * @return QrLoginGenerateResponse
- */
- QrLoginGenerateResponse generateQrLoginToken();
-
- /**
- * 检查扫码登录状态
- *
- * @param token 扫码登录token
- * @return QrLoginStatusResponse
- */
- QrLoginStatusResponse checkQrLoginStatus(String token);
-
- /**
- * 确认扫码登录
- *
- * @param request 确认请求
- * @return QrLoginStatusResponse
- */
- QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request);
-
- /**
- * 扫码操作(更新状态为已扫码)
- *
- * @param token 扫码登录token
- * @return boolean
- */
- boolean scanQrCode(String token);
-
-}
diff --git a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java b/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java
deleted file mode 100644
index 34658e8..0000000
--- a/src/main/java/com/gxwebsoft/auto/service/impl/QrLoginServiceImpl.java
+++ /dev/null
@@ -1,239 +0,0 @@
-package com.gxwebsoft.auto.service.impl;
-
-import cn.hutool.core.lang.UUID;
-import cn.hutool.core.util.StrUtil;
-import com.gxwebsoft.auto.dto.*;
-import com.gxwebsoft.auto.service.QrLoginService;
-import com.gxwebsoft.common.core.security.JwtSubject;
-import com.gxwebsoft.common.core.security.JwtUtil;
-import com.gxwebsoft.common.core.utils.JSONUtil;
-import com.gxwebsoft.common.core.utils.RedisUtil;
-import com.gxwebsoft.common.system.entity.User;
-import com.gxwebsoft.common.system.service.UserService;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
-
-import java.time.LocalDateTime;
-import java.time.temporal.ChronoUnit;
-import java.util.concurrent.TimeUnit;
-
-import static com.gxwebsoft.common.core.constants.RedisConstants.*;
-
-/**
- * 扫码登录服务实现
- *
- * @author 科技小王子
- * @since 2025-08-31
- */
-@Slf4j
-@Service
-public class QrLoginServiceImpl implements QrLoginService {
-
- @Autowired
- private RedisUtil redisUtil;
-
- @Autowired
- private UserService userService;
-
- @Value("${config.jwt.secret:websoft-jwt-secret-key-2025}")
- private String jwtSecret;
-
- @Value("${config.jwt.expire:86400}")
- private Long jwtExpire;
-
- @Override
- public QrLoginGenerateResponse generateQrLoginToken() {
- // 生成唯一的扫码登录token
- String token = UUID.randomUUID().toString(true);
-
- // 创建扫码登录数据
- QrLoginData qrLoginData = new QrLoginData();
- qrLoginData.setToken(token);
- qrLoginData.setStatus(QR_LOGIN_STATUS_PENDING);
- qrLoginData.setCreateTime(LocalDateTime.now());
- qrLoginData.setExpireTime(LocalDateTime.now().plusSeconds(QR_LOGIN_TOKEN_TTL));
-
- // 存储到Redis,设置过期时间
- String redisKey = QR_LOGIN_TOKEN_KEY + token;
- redisUtil.set(redisKey, qrLoginData, QR_LOGIN_TOKEN_TTL, TimeUnit.SECONDS);
-
- log.info("生成扫码登录token: {}", token);
-
- // 构造二维码内容(这里可以是前端登录页面的URL + token参数)
- String qrCodeContent = "qr-login:" + token;
-
- return new QrLoginGenerateResponse(token, qrCodeContent, QR_LOGIN_TOKEN_TTL);
- }
-
- @Override
- public QrLoginStatusResponse checkQrLoginStatus(String token) {
- if (StrUtil.isBlank(token)) {
- return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L);
- }
-
- String redisKey = QR_LOGIN_TOKEN_KEY + token;
- QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
-
- if (qrLoginData == null) {
- return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L);
- }
-
- // 检查是否过期
- if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) {
- // 删除过期的token
- redisUtil.delete(redisKey);
- return new QrLoginStatusResponse(QR_LOGIN_STATUS_EXPIRED, null, null, 0L);
- }
-
- // 计算剩余过期时间
- long expiresIn = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime());
-
- QrLoginStatusResponse response = new QrLoginStatusResponse();
- response.setStatus(qrLoginData.getStatus());
- response.setExpiresIn(expiresIn);
-
- // 如果已确认,返回token和用户信息
- if (QR_LOGIN_STATUS_CONFIRMED.equals(qrLoginData.getStatus())) {
- response.setAccessToken(qrLoginData.getAccessToken());
-
- // 获取用户信息
- if (qrLoginData.getUserId() != null) {
- User user = userService.getByIdRel(qrLoginData.getUserId());
- if (user != null) {
- // 清除敏感信息
- user.setPassword(null);
- response.setUserInfo(user);
- }
- }
-
- // 确认后删除token,防止重复使用
- redisUtil.delete(redisKey);
- }
-
- return response;
- }
-
- @Override
- public QrLoginStatusResponse confirmQrLogin(QrLoginConfirmRequest request) {
- String token = request.getToken();
- Integer userId = request.getUserId();
- String platform = request.getPlatform();
-
- if (StrUtil.isBlank(token) || userId == null) {
- throw new RuntimeException("参数不能为空");
- }
-
- String redisKey = QR_LOGIN_TOKEN_KEY + token;
- QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
-
- if (qrLoginData == null) {
- throw new RuntimeException("扫码登录token不存在或已过期");
- }
-
- // 检查是否过期
- if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) {
- redisUtil.delete(redisKey);
- throw new RuntimeException("扫码登录token已过期");
- }
-
- // 获取用户信息
- User user = userService.getByIdRel(userId);
- if (user == null) {
- throw new RuntimeException("用户不存在");
- }
-
- // 检查用户状态
- if (user.getStatus() != null && user.getStatus() != 0) {
- throw new RuntimeException("用户已被冻结");
- }
-
- // 如果是微信小程序登录,处理微信相关信息
- if ("miniprogram".equals(platform) && request.getWechatInfo() != null) {
- handleWechatMiniProgramLogin(user, request.getWechatInfo());
- }
-
- // 生成JWT token
- JwtSubject jwtSubject = new JwtSubject(user.getUsername(), user.getTenantId());
- String accessToken = JwtUtil.buildToken(jwtSubject, jwtExpire, jwtSecret);
-
- // 更新扫码登录数据
- qrLoginData.setStatus(QR_LOGIN_STATUS_CONFIRMED);
- qrLoginData.setUserId(userId);
- qrLoginData.setUsername(user.getUsername());
- qrLoginData.setAccessToken(accessToken);
-
- // 更新Redis中的数据
- redisUtil.set(redisKey, qrLoginData, 60L, TimeUnit.SECONDS); // 给前端60秒时间获取token
-
- log.info("用户 {} 通过 {} 平台确认扫码登录,token: {}", user.getUsername(),
- platform != null ? platform : "unknown", token);
-
- // 清除敏感信息
- user.setPassword(null);
-
- return new QrLoginStatusResponse(QR_LOGIN_STATUS_CONFIRMED, accessToken, user, 60L);
- }
-
- /**
- * 处理微信小程序登录相关逻辑
- */
- private void handleWechatMiniProgramLogin(User user, QrLoginConfirmRequest.WechatMiniProgramInfo wechatInfo) {
- // 更新用户的微信信息
- if (StrUtil.isNotBlank(wechatInfo.getOpenid())) {
- user.setOpenid(wechatInfo.getOpenid());
- }
- if (StrUtil.isNotBlank(wechatInfo.getUnionid())) {
- user.setUnionid(wechatInfo.getUnionid());
- }
- if (StrUtil.isNotBlank(wechatInfo.getNickname()) && StrUtil.isBlank(user.getNickname())) {
- user.setNickname(wechatInfo.getNickname());
- }
- if (StrUtil.isNotBlank(wechatInfo.getAvatar()) && StrUtil.isBlank(user.getAvatar())) {
- user.setAvatar(wechatInfo.getAvatar());
- }
-
- // 更新用户信息到数据库
- try {
- userService.updateById(user);
- log.info("更新用户 {} 的微信小程序信息成功", user.getUsername());
- } catch (Exception e) {
- log.warn("更新用户 {} 的微信小程序信息失败: {}", user.getUsername(), e.getMessage());
- }
- }
-
- @Override
- public boolean scanQrCode(String token) {
- if (StrUtil.isBlank(token)) {
- return false;
- }
-
- String redisKey = QR_LOGIN_TOKEN_KEY + token;
- QrLoginData qrLoginData = redisUtil.get(redisKey, QrLoginData.class);
-
- if (qrLoginData == null) {
- return false;
- }
-
- // 检查是否过期
- if (LocalDateTime.now().isAfter(qrLoginData.getExpireTime())) {
- redisUtil.delete(redisKey);
- return false;
- }
-
- // 只有pending状态才能更新为scanned
- if (QR_LOGIN_STATUS_PENDING.equals(qrLoginData.getStatus())) {
- qrLoginData.setStatus(QR_LOGIN_STATUS_SCANNED);
-
- // 计算剩余过期时间
- long remainingSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), qrLoginData.getExpireTime());
- redisUtil.set(redisKey, qrLoginData, remainingSeconds, TimeUnit.SECONDS);
-
- log.info("扫码登录token {} 状态更新为已扫码", token);
- return true;
- }
-
- return false;
- }
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicAppointmentController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicAppointmentController.java
deleted file mode 100644
index cf186e3..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicAppointmentController.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.clinic.service.ClinicAppointmentService;
-import com.gxwebsoft.clinic.entity.ClinicAppointment;
-import com.gxwebsoft.clinic.param.ClinicAppointmentParam;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.PageResult;
-import com.gxwebsoft.common.core.web.PageParam;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 挂号控制器
- *
- * @author 科技小王子
- * @since 2025-10-19 09:27:04
- */
-@Tag(name = "挂号管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-appointment")
-public class ClinicAppointmentController extends BaseController {
- @Resource
- private ClinicAppointmentService clinicAppointmentService;
-
- @Operation(summary = "分页查询挂号")
- @GetMapping("/page")
- public ApiResult> page(ClinicAppointmentParam param) {
- // 使用关联查询
- return success(clinicAppointmentService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:list')")
- @Operation(summary = "查询全部挂号")
- @GetMapping()
- public ApiResult> list(ClinicAppointmentParam param) {
- // 使用关联查询
- return success(clinicAppointmentService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:list')")
- @Operation(summary = "根据id查询挂号")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicAppointmentService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:save')")
- @OperationLog
- @Operation(summary = "添加挂号")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicAppointment clinicAppointment) {
- if (clinicAppointmentService.save(clinicAppointment)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:update')")
- @OperationLog
- @Operation(summary = "修改挂号")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicAppointment clinicAppointment) {
- if (clinicAppointmentService.updateById(clinicAppointment)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:remove')")
- @OperationLog
- @Operation(summary = "删除挂号")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicAppointmentService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:save')")
- @OperationLog
- @Operation(summary = "批量添加挂号")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicAppointmentService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:update')")
- @OperationLog
- @Operation(summary = "批量修改挂号")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicAppointmentService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicAppointment:remove')")
- @OperationLog
- @Operation(summary = "批量删除挂号")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicAppointmentService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorApplyController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorApplyController.java
deleted file mode 100644
index 7471fa7..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorApplyController.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.clinic.service.ClinicDoctorApplyService;
-import com.gxwebsoft.clinic.entity.ClinicDoctorApply;
-import com.gxwebsoft.clinic.param.ClinicDoctorApplyParam;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.PageResult;
-import com.gxwebsoft.common.core.web.PageParam;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 医生入驻申请控制器
- *
- * @author 科技小王子
- * @since 2025-10-19 09:27:04
- */
-@Tag(name = "医生入驻申请管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-doctor-apply")
-public class ClinicDoctorApplyController extends BaseController {
- @Resource
- private ClinicDoctorApplyService clinicDoctorApplyService;
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:list')")
- @Operation(summary = "分页查询医生入驻申请")
- @GetMapping("/page")
- public ApiResult> page(ClinicDoctorApplyParam param) {
- // 使用关联查询
- return success(clinicDoctorApplyService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:list')")
- @Operation(summary = "查询全部医生入驻申请")
- @GetMapping()
- public ApiResult> list(ClinicDoctorApplyParam param) {
- // 使用关联查询
- return success(clinicDoctorApplyService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:list')")
- @Operation(summary = "根据id查询医生入驻申请")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicDoctorApplyService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:save')")
- @OperationLog
- @Operation(summary = "添加医生入驻申请")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicDoctorApply clinicDoctorApply) {
- // 记录当前登录用户id
- // User loginUser = getLoginUser();
- // if (loginUser != null) {
- // clinicDoctorApply.setUserId(loginUser.getUserId());
- // }
- if (clinicDoctorApplyService.save(clinicDoctorApply)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:update')")
- @OperationLog
- @Operation(summary = "修改医生入驻申请")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicDoctorApply clinicDoctorApply) {
- if (clinicDoctorApplyService.updateById(clinicDoctorApply)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:remove')")
- @OperationLog
- @Operation(summary = "删除医生入驻申请")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicDoctorApplyService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:save')")
- @OperationLog
- @Operation(summary = "批量添加医生入驻申请")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicDoctorApplyService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:update')")
- @OperationLog
- @Operation(summary = "批量修改医生入驻申请")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicDoctorApplyService, "apply_id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorApply:remove')")
- @OperationLog
- @Operation(summary = "批量删除医生入驻申请")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicDoctorApplyService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorUserController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorUserController.java
deleted file mode 100644
index 9f7012e..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicDoctorUserController.java
+++ /dev/null
@@ -1,128 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.clinic.entity.ClinicDoctorUser;
-import com.gxwebsoft.clinic.param.ClinicDoctorUserParam;
-import com.gxwebsoft.clinic.service.ClinicDoctorUserService;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.web.PageResult;
-import com.gxwebsoft.common.system.entity.User;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 分销商用户记录表控制器
- *
- * @author 科技小王子
- * @since 2025-10-23 15:58:21
- */
-@Tag(name = "分销商用户记录表管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-doctor-user")
-public class ClinicDoctorUserController extends BaseController {
- @Resource
- private ClinicDoctorUserService clinicDoctorUserService;
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:list')")
- @Operation(summary = "分页查询分销商用户记录表")
- @GetMapping("/page")
- public ApiResult> page(ClinicDoctorUserParam param) {
- // 使用关联查询
- return success(clinicDoctorUserService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:list')")
- @Operation(summary = "查询全部分销商用户记录表")
- @GetMapping()
- public ApiResult> list(ClinicDoctorUserParam param) {
- // 使用关联查询
- return success(clinicDoctorUserService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:list')")
- @Operation(summary = "根据id查询分销商用户记录表")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicDoctorUserService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:save')")
- @OperationLog
- @Operation(summary = "添加分销商用户记录表")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicDoctorUser clinicDoctorUser) {
- // 记录当前登录用户id
- User loginUser = getLoginUser();
- if (loginUser != null) {
- clinicDoctorUser.setUserId(loginUser.getUserId());
- }
- if (clinicDoctorUserService.save(clinicDoctorUser)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:update')")
- @OperationLog
- @Operation(summary = "修改分销商用户记录表")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicDoctorUser clinicDoctorUser) {
- if (clinicDoctorUserService.updateById(clinicDoctorUser)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:remove')")
- @OperationLog
- @Operation(summary = "删除分销商用户记录表")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicDoctorUserService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:save')")
- @OperationLog
- @Operation(summary = "批量添加分销商用户记录表")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicDoctorUserService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:update')")
- @OperationLog
- @Operation(summary = "批量修改分销商用户记录表")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicDoctorUserService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicDoctorUser:remove')")
- @OperationLog
- @Operation(summary = "批量删除分销商用户记录表")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicDoctorUserService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineController.java
deleted file mode 100644
index 28e27c6..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineController.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.clinic.entity.ClinicMedicine;
-import com.gxwebsoft.clinic.param.ClinicMedicineParam;
-import com.gxwebsoft.clinic.service.ClinicMedicineService;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.web.PageResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 药品库控制器
- *
- * @author 科技小王子
- * @since 2025-10-22 02:06:32
- */
-@Tag(name = "药品库管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-medicine")
-public class ClinicMedicineController extends BaseController {
- @Resource
- private ClinicMedicineService clinicMedicineService;
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:list')")
- @Operation(summary = "分页查询药品库")
- @GetMapping("/page")
- public ApiResult> page(ClinicMedicineParam param) {
- // 使用关联查询
- return success(clinicMedicineService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:list')")
- @Operation(summary = "查询全部药品库")
- @GetMapping()
- public ApiResult> list(ClinicMedicineParam param) {
- // 使用关联查询
- return success(clinicMedicineService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:list')")
- @Operation(summary = "根据id查询药品库")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicMedicineService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:save')")
- @OperationLog
- @Operation(summary = "添加药品库")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicMedicine clinicMedicine) {
- // 记录当前登录用户id
- // User loginUser = getLoginUser();
- // if (loginUser != null) {
- // clinicMedicine.setUserId(loginUser.getUserId());
- // }
- if (clinicMedicineService.save(clinicMedicine)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:update')")
- @OperationLog
- @Operation(summary = "修改药品库")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicMedicine clinicMedicine) {
- if (clinicMedicineService.updateById(clinicMedicine)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:remove')")
- @OperationLog
- @Operation(summary = "删除药品库")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicMedicineService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:save')")
- @OperationLog
- @Operation(summary = "批量添加药品库")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicMedicineService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:update')")
- @OperationLog
- @Operation(summary = "批量修改药品库")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicMedicineService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicine:remove')")
- @OperationLog
- @Operation(summary = "批量删除药品库")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicMedicineService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineInoutController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineInoutController.java
deleted file mode 100644
index e1b89a8..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineInoutController.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.clinic.entity.ClinicMedicineInout;
-import com.gxwebsoft.clinic.param.ClinicMedicineInoutParam;
-import com.gxwebsoft.clinic.service.ClinicMedicineInoutService;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.web.PageResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 出入库控制器
- *
- * @author 科技小王子
- * @since 2025-10-22 02:06:32
- */
-@Tag(name = "出入库管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-medicine-inout")
-public class ClinicMedicineInoutController extends BaseController {
- @Resource
- private ClinicMedicineInoutService clinicMedicineInoutService;
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:list')")
- @Operation(summary = "分页查询出入库")
- @GetMapping("/page")
- public ApiResult> page(ClinicMedicineInoutParam param) {
- // 使用关联查询
- return success(clinicMedicineInoutService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:list')")
- @Operation(summary = "查询全部出入库")
- @GetMapping()
- public ApiResult> list(ClinicMedicineInoutParam param) {
- // 使用关联查询
- return success(clinicMedicineInoutService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:list')")
- @Operation(summary = "根据id查询出入库")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicMedicineInoutService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:save')")
- @OperationLog
- @Operation(summary = "添加出入库")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicMedicineInout clinicMedicineInout) {
- // 记录当前登录用户id
- // User loginUser = getLoginUser();
- // if (loginUser != null) {
- // clinicMedicineInout.setUserId(loginUser.getUserId());
- // }
- if (clinicMedicineInoutService.save(clinicMedicineInout)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:update')")
- @OperationLog
- @Operation(summary = "修改出入库")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicMedicineInout clinicMedicineInout) {
- if (clinicMedicineInoutService.updateById(clinicMedicineInout)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:remove')")
- @OperationLog
- @Operation(summary = "删除出入库")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicMedicineInoutService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:save')")
- @OperationLog
- @Operation(summary = "批量添加出入库")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicMedicineInoutService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:update')")
- @OperationLog
- @Operation(summary = "批量修改出入库")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicMedicineInoutService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineInout:remove')")
- @OperationLog
- @Operation(summary = "批量删除出入库")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicMedicineInoutService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineStockController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineStockController.java
deleted file mode 100644
index 867b425..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicMedicineStockController.java
+++ /dev/null
@@ -1,127 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.clinic.entity.ClinicMedicineStock;
-import com.gxwebsoft.clinic.param.ClinicMedicineStockParam;
-import com.gxwebsoft.clinic.service.ClinicMedicineStockService;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.web.PageResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 药品库存控制器
- *
- * @author 科技小王子
- * @since 2025-10-22 02:06:32
- */
-@Tag(name = "药品库存管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-medicine-stock")
-public class ClinicMedicineStockController extends BaseController {
- @Resource
- private ClinicMedicineStockService clinicMedicineStockService;
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:list')")
- @Operation(summary = "分页查询药品库存")
- @GetMapping("/page")
- public ApiResult> page(ClinicMedicineStockParam param) {
- // 使用关联查询
- return success(clinicMedicineStockService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:list')")
- @Operation(summary = "查询全部药品库存")
- @GetMapping()
- public ApiResult> list(ClinicMedicineStockParam param) {
- // 使用关联查询
- return success(clinicMedicineStockService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:list')")
- @Operation(summary = "根据id查询药品库存")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicMedicineStockService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:save')")
- @OperationLog
- @Operation(summary = "添加药品库存")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicMedicineStock clinicMedicineStock) {
- // 记录当前登录用户id
- // User loginUser = getLoginUser();
- // if (loginUser != null) {
- // clinicMedicineStock.setUserId(loginUser.getUserId());
- // }
- if (clinicMedicineStockService.save(clinicMedicineStock)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:update')")
- @OperationLog
- @Operation(summary = "修改药品库存")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicMedicineStock clinicMedicineStock) {
- if (clinicMedicineStockService.updateById(clinicMedicineStock)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:remove')")
- @OperationLog
- @Operation(summary = "删除药品库存")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicMedicineStockService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:save')")
- @OperationLog
- @Operation(summary = "批量添加药品库存")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicMedicineStockService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:update')")
- @OperationLog
- @Operation(summary = "批量修改药品库存")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicMedicineStockService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicMedicineStock:remove')")
- @OperationLog
- @Operation(summary = "批量删除药品库存")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicMedicineStockService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicPatientUserController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicPatientUserController.java
deleted file mode 100644
index 87aa108..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicPatientUserController.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.clinic.service.ClinicPatientUserService;
-import com.gxwebsoft.clinic.entity.ClinicPatientUser;
-import com.gxwebsoft.clinic.param.ClinicPatientUserParam;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.PageResult;
-import com.gxwebsoft.common.core.web.PageParam;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.system.entity.User;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 患者控制器
- *
- * @author 科技小王子
- * @since 2025-10-19 09:27:04
- */
-@Tag(name = "患者管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-patient-user")
-public class ClinicPatientUserController extends BaseController {
- @Resource
- private ClinicPatientUserService clinicPatientUserService;
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:list')")
- @Operation(summary = "分页查询患者")
- @GetMapping("/page")
- public ApiResult> page(ClinicPatientUserParam param) {
- // 使用关联查询
- return success(clinicPatientUserService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:list')")
- @Operation(summary = "查询全部患者")
- @GetMapping()
- public ApiResult> list(ClinicPatientUserParam param) {
- // 使用关联查询
- return success(clinicPatientUserService.listRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:list')")
- @Operation(summary = "根据id查询患者")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicPatientUserService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:save')")
- @OperationLog
- @Operation(summary = "添加患者")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicPatientUser clinicPatientUser) {
- // 记录当前登录用户id
- User loginUser = getLoginUser();
- if (loginUser != null) {
- clinicPatientUser.setUserId(loginUser.getUserId());
- }
- if (clinicPatientUserService.save(clinicPatientUser)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:update')")
- @OperationLog
- @Operation(summary = "修改患者")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicPatientUser clinicPatientUser) {
- if (clinicPatientUserService.updateById(clinicPatientUser)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:remove')")
- @OperationLog
- @Operation(summary = "删除患者")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicPatientUserService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:save')")
- @OperationLog
- @Operation(summary = "批量添加患者")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicPatientUserService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:update')")
- @OperationLog
- @Operation(summary = "批量修改患者")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicPatientUserService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPatientUser:remove')")
- @OperationLog
- @Operation(summary = "批量删除患者")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicPatientUserService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionController.java
deleted file mode 100644
index f51872e..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionController.java
+++ /dev/null
@@ -1,191 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import cn.hutool.core.util.IdUtil;
-import com.gxwebsoft.clinic.dto.PrescriptionOrderRequest;
-import com.gxwebsoft.clinic.entity.ClinicPrescription;
-import com.gxwebsoft.clinic.param.ClinicPrescriptionParam;
-import com.gxwebsoft.clinic.service.ClinicPrescriptionService;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.web.PageResult;
-import com.gxwebsoft.common.system.entity.User;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.util.List;
-
-/**
- * 处方主表
-控制器
- *
- * @author 科技小王子
- * @since 2025-10-22 02:01:13
- */
-@Tag(name = "处方主表管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-prescription")
-public class ClinicPrescriptionController extends BaseController {
- @Resource
- private ClinicPrescriptionService clinicPrescriptionService;
-
- @Operation(summary = "分页查询处方主表")
- @GetMapping("/page")
- public ApiResult> page(ClinicPrescriptionParam param) {
- // 使用关联查询
- return success(clinicPrescriptionService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:list')")
- @Operation(summary = "查询全部处方主表")
- @GetMapping()
- public ApiResult> list(ClinicPrescriptionParam param) {
- // 使用关联查询
- return success(clinicPrescriptionService.listRel(param));
- }
-
- @Operation(summary = "根据id查询处方主表")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicPrescriptionService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:save')")
- @OperationLog
- @Operation(summary = "添加处方主表")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicPrescription clinicPrescription) {
- // 记录当前登录用户id
- User loginUser = getLoginUser();
- if (loginUser != null) {
- clinicPrescription.setDoctorId(loginUser.getUserId());
- // 生成订单号
- String orderNo = Long.toString(IdUtil.getSnowflakeNextId());
- clinicPrescription.setOrderNo(orderNo);
- }
- if (clinicPrescriptionService.save(clinicPrescription)) {
- // 返回处方数据,包含处方ID
- return success("添加成功",clinicPrescriptionService.getByLastId(clinicPrescription));
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:update')")
- @OperationLog
- @Operation(summary = "修改处方主表")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicPrescription clinicPrescription) {
- if (clinicPrescriptionService.updateById(clinicPrescription)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:remove')")
- @OperationLog
- @Operation(summary = "删除处方主表")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicPrescriptionService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:save')")
- @OperationLog
- @Operation(summary = "批量添加处方主表")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicPrescriptionService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:update')")
- @OperationLog
- @Operation(summary = "批量修改处方主表")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicPrescriptionService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:remove')")
- @OperationLog
- @Operation(summary = "批量删除处方主表")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicPrescriptionService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @Operation(summary = "创建处方订单")
- @PostMapping("/order")
- public ApiResult> createOrder(@RequestBody PrescriptionOrderRequest request) {
- try {
- // 1. 参数校验
- if (request.getPrescriptionId() == null) {
- return fail("处方ID不能为空");
- }
- if (request.getPayType() == null) {
- return fail("支付方式不能为空");
- }
-
- // 2. 查询处方信息
- ClinicPrescription prescription = clinicPrescriptionService.getById(request.getPrescriptionId());
- if (prescription == null) {
- return fail("处方不存在");
- }
-
- // 3. 检查处方状态
- if (prescription.getStatus() != null && prescription.getStatus() == 2) {
- return fail("该处方已支付,无需重复支付");
- }
- if (prescription.getStatus() != null && prescription.getStatus() == 3) {
- return fail("该处方已取消,无法支付");
- }
-
- // 4. 更新处方订单信息
- ClinicPrescription updatePrescription = new ClinicPrescription();
- updatePrescription.setId(request.getPrescriptionId());
-
- // 根据支付类型更新状态
- if (request.getPayType() == 1) {
- // 微信支付,状态保持为正常,等待支付回调
- updatePrescription.setStatus(0);
- } else if (request.getPayType() == 4 || request.getPayType() == 5) {
- // 现金支付或POS机支付,直接标记为已支付
- updatePrescription.setStatus(2);
- updatePrescription.setIsSettled(1);
- updatePrescription.setSettleTime(LocalDateTime.now());
- } else if (request.getPayType() == 6) {
- // 免费,直接标记为已完成
- updatePrescription.setStatus(1);
- updatePrescription.setIsSettled(1);
- updatePrescription.setSettleTime(LocalDateTime.now());
- }
-
- if (clinicPrescriptionService.updateById(updatePrescription)) {
- return success("订单创建成功", prescription);
- }
- return fail("订单创建失败");
-
- } catch (Exception e) {
- return fail("订单创建失败:" + e.getMessage());
- }
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionItemController.java b/src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionItemController.java
deleted file mode 100644
index eda3050..0000000
--- a/src/main/java/com/gxwebsoft/clinic/controller/ClinicPrescriptionItemController.java
+++ /dev/null
@@ -1,121 +0,0 @@
-package com.gxwebsoft.clinic.controller;
-
-import com.gxwebsoft.clinic.entity.ClinicPrescriptionItem;
-import com.gxwebsoft.clinic.param.ClinicPrescriptionItemParam;
-import com.gxwebsoft.clinic.service.ClinicPrescriptionItemService;
-import com.gxwebsoft.common.core.annotation.OperationLog;
-import com.gxwebsoft.common.core.web.ApiResult;
-import com.gxwebsoft.common.core.web.BaseController;
-import com.gxwebsoft.common.core.web.BatchParam;
-import com.gxwebsoft.common.core.web.PageResult;
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.web.bind.annotation.*;
-
-import javax.annotation.Resource;
-import java.util.List;
-
-/**
- * 处方明细表
-控制器
- *
- * @author 科技小王子
- * @since 2025-10-22 02:01:13
- */
-@Tag(name = "处方明细表管理")
-@RestController
-@RequestMapping("/api/clinic/clinic-prescription-item")
-public class ClinicPrescriptionItemController extends BaseController {
- @Resource
- private ClinicPrescriptionItemService clinicPrescriptionItemService;
-
- @Operation(summary = "分页查询处方明细表")
- @GetMapping("/page")
- public ApiResult> page(ClinicPrescriptionItemParam param) {
- // 使用关联查询
- return success(clinicPrescriptionItemService.pageRel(param));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:list')")
- @Operation(summary = "查询全部处方明细表")
- @GetMapping()
- public ApiResult> list(ClinicPrescriptionItemParam param) {
- // 使用关联查询
- return success(clinicPrescriptionItemService.listRel(param));
- }
-
- @Operation(summary = "根据id查询处方明细表")
- @GetMapping("/{id}")
- public ApiResult get(@PathVariable("id") Integer id) {
- // 使用关联查询
- return success(clinicPrescriptionItemService.getByIdRel(id));
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:save')")
- @OperationLog
- @Operation(summary = "添加处方明细表")
- @PostMapping()
- public ApiResult> save(@RequestBody ClinicPrescriptionItem clinicPrescriptionItem) {
- if (clinicPrescriptionItemService.save(clinicPrescriptionItem)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:update')")
- @OperationLog
- @Operation(summary = "修改处方明细表")
- @PutMapping()
- public ApiResult> update(@RequestBody ClinicPrescriptionItem clinicPrescriptionItem) {
- if (clinicPrescriptionItemService.updateById(clinicPrescriptionItem)) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:remove')")
- @OperationLog
- @Operation(summary = "删除处方明细表")
- @DeleteMapping("/{id}")
- public ApiResult> remove(@PathVariable("id") Integer id) {
- if (clinicPrescriptionItemService.removeById(id)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:save')")
- @OperationLog
- @Operation(summary = "批量添加处方明细表")
- @PostMapping("/batch")
- public ApiResult> saveBatch(@RequestBody List list) {
- if (clinicPrescriptionItemService.saveBatch(list)) {
- return success("添加成功");
- }
- return fail("添加失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:update')")
- @OperationLog
- @Operation(summary = "批量修改处方明细表")
- @PutMapping("/batch")
- public ApiResult> removeBatch(@RequestBody BatchParam batchParam) {
- if (batchParam.update(clinicPrescriptionItemService, "id")) {
- return success("修改成功");
- }
- return fail("修改失败");
- }
-
- @PreAuthorize("hasAuthority('clinic:clinicPrescription:remove')")
- @OperationLog
- @Operation(summary = "批量删除处方明细表")
- @DeleteMapping("/batch")
- public ApiResult> removeBatch(@RequestBody List ids) {
- if (clinicPrescriptionItemService.removeByIds(ids)) {
- return success("删除成功");
- }
- return fail("删除失败");
- }
-
-}
diff --git a/src/main/java/com/gxwebsoft/clinic/controller/业务中台-排班信息接口对接文档20251114(2).docx b/src/main/java/com/gxwebsoft/clinic/controller/业务中台-排班信息接口对接文档20251114(2).docx
deleted file mode 100644
index 8b2171604dfd002142f9d3e93b93d3ed999672cf..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 115163
zcmbrlV{m8D)9)K+l8J5Gn%K6DiJf0;+qP}*0{^X{w{d+plQ
zt5^5;vujm#@7jto;1IAN{~2NO`-1<57sUu2Li>(dtro1pgcuWn*eku
z8;$UVJtGBF!L+S8_#UpVBle$bZe!5gbkbkC$(?g!1r{EZ$~
z^AyedeSgoo%4s|Y8NwfdvR*gO4&fyho<{%Ff0D4Au!g%5g;cQ34m_hP`YbqhM?Et1Lv{n$
zXhm?2gWN?jdAtgY8f|tw@pdHU1Ij4GK+ycoHKTv_c=_MKK9~*9?sUXT(%^N7xur@8
z`Jyo7UiCcE(sY$9VvNZ5X}pOV3n9Uk>c$GAuS9Ivv7m3%8*G08m!^
zgW0ot!zQWh+WogVUEDX3xfqvu-E&h%px&k`wvMj)S|?SO4IizR_(Wy)0(nWbHh2b&
z(4`-ByJ?9!7S*dl<9n7vz8E@)`)bX!nmx#6K>`|0@?`J4e(1fbmJ}lLlu-3cU`z
zu0QOSYXq3WY|xiKmAflR_Zim`S!k7
zpcu2yFz*A4Rtq5@S|_Fgp@*$_u<@^@EMgK5aM6{?MaO}%fW&}bI4BZLmPLbNkW&hD
zfVJ6`T4zy0nC)^RA6Sj_8vS$W9`ZD1Y3O+6ZDH1SxF0YBA`zUui-(uC>7WZf&`4g0
zuN;Y?H7&OcQrsIags`q9%Y)`;ji0{xr8R4Uko?E7YJR4<$!j##D9dI^rb^pTO)aN#
zWA)~Xx*K0#_g7J<^okuhNJ#{m91jJYJv7S=2oTUZv*|oDO-Y(8PF6@9F+i0YpFS6k
z?3lLOWu>7H-df#XoRKN>JD^hS(zGifT8HClB8w)ztjhWR>EZRGTg&fd!0fZp!$H%>PaY{r&7R
z`5&FDe{@j)8yy!XXFI@u__QQW*<~?dgHaPoP>n71L$llWV48q6S;X!gfIXQ2(Ie}{LifWnC^79?n^7*wIm}oa;7j7vOhF+M}^^!55aZiXw^wo<@;{>63-`|pp{=k
zWP2~OC=~Dzd#k?i(WDfa7
zMT=1`sF?{@TDesNFu_#9x0^o~^aao5hkjU8+o+xEuVnPAL(>(311jN9(ch@blN02Y&kgqcDxaZiy;l@YXz(b
z^r)XqH44a$Gs0H9T$UZB$z||KzIp^>Zh;>9UH4=Izjl8Aii_ea+wgho>||7&+wLRT
z>9TpA*fWb$O29O8ZE048vP)cwC+B&i?`CIShdX~qpZdE`KA{Ha_SB{zuF(*>sXLBd
zpzg#!yFmXB#Q*aN3HcAAo1LS{|8_dYI
Xth-qMpv
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