From 1298a558e8140b8f93c28f943e26947e5de3ef35 Mon Sep 17 00:00:00 2001 From: marsal Date: Wed, 8 Jul 2026 16:23:09 +0800 Subject: [PATCH] wip: baseline changes before channel-conversation-task boundary implementation --- .env.example | 32 + .gitignore | 5 + admin-web/src/api/account.ts | 60 +- admin-web/src/api/channel.ts | 30 +- admin-web/src/api/channelAccount.ts | 33 + admin-web/src/api/channelSubject.ts | 21 + admin-web/src/api/client.ts | 8 +- admin-web/src/api/crm.ts | 36 +- admin-web/src/api/inboundFlow.ts | 75 ++ admin-web/src/api/inboundWebhook.ts | 13 + admin-web/src/api/ivrFlow.ts | 75 ++ admin-web/src/api/sendPolicy.ts | 25 + admin-web/src/api/sendRecord.ts | 31 + admin-web/src/components/Layout.vue | 9 + admin-web/src/router/index.ts | 6 + admin-web/src/stores/auth.ts | 18 +- admin-web/src/views/ChannelAccount.vue | 340 ++++++-- admin-web/src/views/ChannelSubject.vue | 239 +++++- admin-web/src/views/Conversation.vue | 348 ++++++-- admin-web/src/views/FlowTrace.vue | 15 +- admin-web/src/views/InboundFlow.vue | 763 +++++++++++++++-- admin-web/src/views/InboundWebhook.vue | 83 ++ admin-web/src/views/IvrFlow.vue | 780 ++++++++++++++++++ admin-web/src/views/Login.vue | 9 + admin-web/src/views/MessageTest.vue | 1 - admin-web/src/views/Person.vue | 383 ++++++++- admin-web/src/views/SendPolicy.vue | 183 ++++ admin-web/src/views/SendRecord.vue | 472 +++++++++-- admin-web/src/views/Tag.vue | 197 ++++- admin-web/src/views/TenantManagement.vue | 199 ++++- admin-web/src/views/UserManagement.vue | 288 ++++++- admin-web/tsconfig.app.tsbuildinfo | 2 +- backend/mci-server/Dockerfile | 10 +- backend/mci-server/pom.xml | 5 + .../admin/controller/AccountController.java | 39 +- .../admin/controller/TenantController.java | 36 + .../mci/admin/dto/CreateTenantRequest.java | 4 + .../mci/admin/dto/TenantWebhookResponse.java | 16 + .../mci/admin/dto/TestWebhookRequest.java | 11 + .../mci/admin/dto/TestWebhookResponse.java | 14 + .../mci/admin/dto/UpdateTenantRequest.java | 15 + .../admin/dto/UpdateTenantWebhookRequest.java | 18 + .../sino/mci/admin/dto/UpdateUserRequest.java | 19 + .../com/sino/mci/admin/entity/Tenant.java | 1 + .../sino/mci/admin/init/DataInitializer.java | 117 +++ .../mci/admin/service/AccountService.java | 148 +++- .../sino/mci/admin/service/TenantService.java | 129 +++ .../controller/ChannelAccountController.java | 32 + .../controller/ChannelSubjectController.java | 25 + .../controller/ConversationController.java | 17 + .../dto/ChannelAccountInitRequest.java | 2 + .../dto/UpdateConversationRequest.java | 18 + .../mci/channel/dto/UpdateStatusRequest.java | 11 + .../mci/channel/messaging/RabbitConfig.java | 11 + .../mci/channel/service/AccountSelector.java | 102 +++ .../service/ChannelSubjectService.java | 52 ++ .../channel/service/ConversationService.java | 27 + .../service/SessionArchivePullService.java | 3 + .../mci/crm/controller/PersonController.java | 26 + .../mci/crm/controller/TagController.java | 17 + .../sino/mci/crm/dto/UpdatePersonRequest.java | 17 + .../sino/mci/crm/dto/UpdateTagRequest.java | 13 + .../sino/mci/crm/service/PersonService.java | 41 + .../com/sino/mci/crm/service/TagService.java | 25 + .../mci/exception/WebhookPushException.java | 20 + .../controller/FlowDefinitionController.java | 8 + .../flow/controller/IvrFlowController.java | 69 ++ .../flow/service/FlowDefinitionService.java | 17 +- .../inbound/controller/IntentController.java | 31 + .../inbound/dto/IntentRecognizeRequest.java | 15 + .../sino/mci/inbound/engine/FlowContext.java | 54 ++ .../inbound/engine/FlowDefinitionParser.java | 85 ++ .../com/sino/mci/inbound/engine/FlowEdge.java | 7 + .../sino/mci/inbound/engine/FlowEngine.java | 203 ++++- .../com/sino/mci/inbound/engine/FlowNode.java | 9 + .../engine/IntentRecognitionService.java | 4 +- .../engine/MockIntentRecognitionService.java | 57 +- .../engine/SpelConditionEvaluator.java | 38 + .../inbound/engine/WebhookPushService.java | 113 ++- .../intent/IntentRecognitionService.java | 287 +++++++ .../sino/mci/inbound/intent/IntentResult.java | 35 + .../intent/OpenAiChatClientConfig.java | 40 + .../inbound/intent/OpenAiRestChatModel.java | 89 ++ .../mci/inbound/mq/InboundEventMessage.java | 35 + .../mci/inbound/mq/InboundFlowConsumer.java | 39 + .../mq/InboundFlowMessageProducer.java | 56 ++ .../inbound/mq/InboundMessageConsumer.java | 79 ++ .../inbound/mq/InboundMessageProducer.java | 52 ++ .../service/InboundContextService.java | 146 ++++ .../service/InboundMessageService.java | 20 + .../webhook/WebhookPayloadBuilder.java | 81 ++ .../mci/inbound/webhook/WebhookSignature.java | 11 + .../webhook/WebhookSignatureService.java | 67 ++ .../java/com/sino/mci/mq/config/MqConfig.java | 119 +++ .../com/sino/mci/policy/engine/Policy.java | 44 + .../mci/policy/engine/PolicyCheckResult.java | 18 + .../sino/mci/policy/engine/PolicyEngine.java | 240 ++++++ .../mci/policy/engine/PolicyGroupConfig.java | 47 ++ .../policy/engine/PolicyPreviewResult.java | 17 + .../sino/mci/policy/engine/PolicyType.java | 18 + .../sino/mci/policy/engine/RetryConfig.java | 16 + .../mci/send/controller/SendController.java | 78 +- .../sino/mci/send/dto/ChannelStatistics.java | 14 + .../sino/mci/send/dto/SendPreviewRequest.java | 19 + .../mci/send/dto/SendPreviewResponse.java | 25 + .../sino/mci/send/dto/SendRecordResponse.java | 3 + .../mci/send/dto/SendStatisticsResponse.java | 17 + .../mci/send/dto/SendTaskStatusResponse.java | 14 + .../com/sino/mci/send/entity/SendRecord.java | 3 + .../sino/mci/send/mq/SendMessageConsumer.java | 116 +++ .../sino/mci/send/mq/SendMessageProducer.java | 29 + .../sino/mci/send/mq/SendRecordMessage.java | 22 + .../mci/send/service/SendCallbackService.java | 70 ++ .../mci/send/service/SendPreviewService.java | 200 +++++ .../sino/mci/send/service/SendService.java | 485 ++++++++--- .../com/sino/mci/send/service/SendTarget.java | 15 + .../src/main/resources/application-dev.yml | 11 + .../V18__add_send_record_retry_fields.sql | 5 + .../db/migration/V19__add_tenant_ext_info.sql | 2 + .../TenantWebhookControllerTest.java | 148 ++++ .../channel/service/AccountSelectorTest.java | 123 +++ .../controller/IvrFlowControllerTest.java | 83 ++ .../mci/inbound/engine/FlowEngineTest.java | 165 ++++ .../inbound/engine/InboundEndToEndTest.java | 142 ++++ .../intent/IntentRecognitionServiceTest.java | 63 ++ .../service/InboundContextServiceTest.java | 161 ++++ .../webhook/WebhookPayloadBuilderTest.java | 117 +++ .../webhook/WebhookSignatureServiceTest.java | 79 ++ .../controller/SendPreviewControllerTest.java | 266 ++++++ .../mci/send/mq/SendMessageConsumerTest.java | 211 +++++ .../mci/send/service/PolicyEngineTest.java | 130 +++ .../mci/send/service/SendEndToEndTest.java | 130 +++ .../send/service/SendServiceFailoverTest.java | 202 +++++ backend/pom.xml | 11 + docker-compose.yml | 58 +- ...7-08-channel-conversation-task-boundary.md | 577 +++++++++++++ ...annel-conversation-task-boundary-design.md | 178 ++++ docs/testing/2026-07-07-test-plan.md | 326 ++++++++ scripts/local-up.sh | 63 +- 139 files changed, 11781 insertions(+), 666 deletions(-) create mode 100644 .env.example create mode 100644 admin-web/src/api/channelAccount.ts create mode 100644 admin-web/src/api/channelSubject.ts create mode 100644 admin-web/src/api/inboundFlow.ts create mode 100644 admin-web/src/api/inboundWebhook.ts create mode 100644 admin-web/src/api/ivrFlow.ts create mode 100644 admin-web/src/api/sendPolicy.ts create mode 100644 admin-web/src/api/sendRecord.ts create mode 100644 admin-web/src/views/InboundWebhook.vue create mode 100644 admin-web/src/views/IvrFlow.vue create mode 100644 admin-web/src/views/SendPolicy.vue create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/controller/TenantController.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/dto/TenantWebhookResponse.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/dto/TestWebhookRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/dto/TestWebhookResponse.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/dto/UpdateTenantRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/dto/UpdateTenantWebhookRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/dto/UpdateUserRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/init/DataInitializer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/admin/service/TenantService.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/channel/dto/UpdateConversationRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/channel/dto/UpdateStatusRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/channel/service/AccountSelector.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/crm/dto/UpdatePersonRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/crm/dto/UpdateTagRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/exception/WebhookPushException.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/flow/controller/IvrFlowController.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/controller/IntentController.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/dto/IntentRecognizeRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/engine/FlowContext.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/engine/FlowDefinitionParser.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/engine/FlowEdge.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/engine/FlowNode.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/engine/SpelConditionEvaluator.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/intent/IntentRecognitionService.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/intent/IntentResult.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/intent/OpenAiChatClientConfig.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/intent/OpenAiRestChatModel.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/mq/InboundEventMessage.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/mq/InboundFlowConsumer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/mq/InboundFlowMessageProducer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/mq/InboundMessageConsumer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/mq/InboundMessageProducer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/service/InboundContextService.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/webhook/WebhookPayloadBuilder.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/webhook/WebhookSignature.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/inbound/webhook/WebhookSignatureService.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/mq/config/MqConfig.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/Policy.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/PolicyCheckResult.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/PolicyEngine.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/PolicyGroupConfig.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/PolicyPreviewResult.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/PolicyType.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/policy/engine/RetryConfig.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/dto/ChannelStatistics.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/dto/SendPreviewRequest.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/dto/SendPreviewResponse.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/dto/SendStatisticsResponse.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/dto/SendTaskStatusResponse.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/mq/SendMessageConsumer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/mq/SendMessageProducer.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/mq/SendRecordMessage.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/service/SendCallbackService.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/service/SendPreviewService.java create mode 100644 backend/mci-server/src/main/java/com/sino/mci/send/service/SendTarget.java create mode 100644 backend/mci-server/src/main/resources/db/migration/V18__add_send_record_retry_fields.sql create mode 100644 backend/mci-server/src/main/resources/db/migration/V19__add_tenant_ext_info.sql create mode 100644 backend/mci-server/src/test/java/com/sino/mci/admin/controller/TenantWebhookControllerTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/channel/service/AccountSelectorTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/flow/controller/IvrFlowControllerTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/inbound/engine/FlowEngineTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/inbound/engine/InboundEndToEndTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/inbound/intent/IntentRecognitionServiceTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/inbound/service/InboundContextServiceTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/inbound/webhook/WebhookPayloadBuilderTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/inbound/webhook/WebhookSignatureServiceTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/send/controller/SendPreviewControllerTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/send/mq/SendMessageConsumerTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/send/service/PolicyEngineTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/send/service/SendEndToEndTest.java create mode 100644 backend/mci-server/src/test/java/com/sino/mci/send/service/SendServiceFailoverTest.java create mode 100644 docs/superpowers/plans/2026-07-08-channel-conversation-task-boundary.md create mode 100644 docs/superpowers/specs/2026-07-08-channel-conversation-task-boundary-design.md create mode 100644 docs/testing/2026-07-07-test-plan.md diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..ce94b9e --- /dev/null +++ b/.env.example @@ -0,0 +1,32 @@ +# MySQL +MYSQL_ROOT_PASSWORD=root +MYSQL_DATABASE=sino_mci +MYSQL_USER=root +MYSQL_PORT=3306 + +# Redis +REDIS_PORT=6379 + +# RabbitMQ +RABBITMQ_DEFAULT_USER=guest +RABBITMQ_DEFAULT_PASS=guest +RABBITMQ_AMQP_PORT=5672 +RABBITMQ_MGMT_PORT=15672 + +# SeaweedFS +SEAWEEDFS_MASTER_PORT=9333 +SEAWEEDFS_S3_PORT=8888 + +# Application ports +MCI_SERVER_PORT=8080 +ADMIN_WEB_PORT=80 +CHANNEL_SERVICE_PORT=8000 + +# JVM options for mci-server +JAVA_OPTS=-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 + +# Spring profile +SPRING_PROFILES_ACTIVE=dev + +# OpenAI API key (optional) +OPENAI_API_KEY=your-openai-api-key-here diff --git a/.gitignore b/.gitignore index 03fd31a..86912da 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,11 @@ __pycache__/ venv/ .env +# Local runtime data +data/ +*-data/ +logs/ + # Node node_modules/ dist/ diff --git a/admin-web/src/api/account.ts b/admin-web/src/api/account.ts index 6cd6004..151b060 100644 --- a/admin-web/src/api/account.ts +++ b/admin-web/src/api/account.ts @@ -6,20 +6,70 @@ export function login(data: { tenantCode: string; username: string; password: st }) } -export function createTenant(data: { tenantCode: string; tenantName: string }) { +export function createTenant(data: { + tenantCode: string + tenantName: string + inboundWebhookUrl?: string + initAdminUsername?: string + initAdminPassword?: string +}) { return client.post('/api/v1/account/tenant', data) } +export function updateTenant( + id: number, + data: { tenantName: string; inboundWebhookUrl?: string; status?: number } +) { + return client.put(`/api/v1/account/tenant/${id}`, data) +} + +export function deleteTenant(id: number) { + return client.delete(`/api/v1/account/tenant/${id}`) +} + export function listTenants() { return client.get('/api/v1/account/tenant/list') } -export function createUser(data: { username: string; password: string; realName?: string; roleCodes?: string[] }) { - return client.post('/api/v1/account/user', data) +export function listRoles() { + return client.get('/api/v1/account/role/list') } -export function listUsers() { - return client.get('/api/v1/account/user/list') +export function createUser( + tenantCode: string, + data: { + username: string + password: string + realName?: string + phone?: string + roleCodes?: string[] + } +) { + return client.post('/api/v1/account/user', data, { + headers: { 'X-Tenant-Code': tenantCode }, + }) +} + +export function updateUser( + id: number, + data: { + realName: string + phone?: string + status?: number + roleCodes?: string[] + } +) { + return client.put(`/api/v1/account/user/${id}`, data) +} + +export function deleteUser(id: number) { + return client.delete(`/api/v1/account/user/${id}`) +} + +export function listUsers(tenantCode?: string) { + return client.get('/api/v1/account/user/list', { + params: tenantCode ? { tenantCode } : {}, + }) } export function me() { diff --git a/admin-web/src/api/channel.ts b/admin-web/src/api/channel.ts index d9d33cc..066edf2 100644 --- a/admin-web/src/api/channel.ts +++ b/admin-web/src/api/channel.ts @@ -1,24 +1,6 @@ import client from './client' - -export function createChannelSubject(data: any) { - return client.post('/api/v1/channel-subject', data) -} - -export function listChannelSubjects() { - return client.get('/api/v1/channel-subject/list') -} - -export function createChannelAccount(data: any) { - return client.post('/api/v1/channel-account', data) -} - -export function listChannelAccounts() { - return client.get('/api/v1/channel-account/list') -} - -export function initChannelAccount(data: any) { - return client.post('/api/v1/channel-account/init', data) -} +export * from './channelSubject' +export * from './channelAccount' export function createConversation(data: any) { return client.post('/api/v1/conversation', data) @@ -28,6 +10,14 @@ export function listConversations() { return client.get('/api/v1/conversation/list') } +export function updateConversation(id: number, data: any) { + return client.put(`/api/v1/conversation/${id}`, data) +} + +export function deleteConversation(id: number) { + return client.delete(`/api/v1/conversation/${id}`) +} + export function bindConversationAccount(conversationId: number, data: any) { return client.post(`/api/v1/conversation/${conversationId}/bind-account`, data) } diff --git a/admin-web/src/api/channelAccount.ts b/admin-web/src/api/channelAccount.ts new file mode 100644 index 0000000..2522951 --- /dev/null +++ b/admin-web/src/api/channelAccount.ts @@ -0,0 +1,33 @@ +import client from './client' + +export function createChannelAccount(data: any) { + return client.post('/api/v1/channel-account', data) +} + +export function listChannelAccounts() { + return client.get('/api/v1/channel-account/list') +} + +export function updateChannelAccount(id: number, data: any) { + return client.put(`/api/v1/channel-account/${id}`, data) +} + +export function deleteChannelAccount(id: number) { + return client.delete(`/api/v1/channel-account/${id}`) +} + +export function updateChannelAccountStatus(id: number, data: any) { + return client.patch(`/api/v1/channel-account/${id}/status`, data) +} + +export function getChannelAccountStatus(id: number) { + return client.get(`/api/v1/channel-account/${id}/status`) +} + +export function initChannelAccount(data: any) { + return client.post('/api/v1/channel-account/init', data) +} + +export function syncChannelAccountConversations(id: number) { + return client.post(`/api/v1/channel-account/${id}/sync-conversations`) +} diff --git a/admin-web/src/api/channelSubject.ts b/admin-web/src/api/channelSubject.ts new file mode 100644 index 0000000..14c385b --- /dev/null +++ b/admin-web/src/api/channelSubject.ts @@ -0,0 +1,21 @@ +import client from './client' + +export function createChannelSubject(data: any) { + return client.post('/api/v1/channel-subject', data) +} + +export function listChannelSubjects() { + return client.get('/api/v1/channel-subject/list') +} + +export function updateChannelSubject(id: number, data: any) { + return client.put(`/api/v1/channel-subject/${id}`, data) +} + +export function deleteChannelSubject(id: number) { + return client.delete(`/api/v1/channel-subject/${id}`) +} + +export function updateChannelSubjectStatus(id: number, data: any) { + return client.patch(`/api/v1/channel-subject/${id}/status`, data) +} diff --git a/admin-web/src/api/client.ts b/admin-web/src/api/client.ts index fccfa25..2c9fb72 100644 --- a/admin-web/src/api/client.ts +++ b/admin-web/src/api/client.ts @@ -21,7 +21,13 @@ client.interceptors.request.use((config) => { }) client.interceptors.response.use( - (response) => response.data, + (response) => { + const data = response.data + if (data && typeof data === 'object' && 'code' in data && data.code !== 0) { + return Promise.reject(new Error(data.message || '请求失败')) + } + return data + }, (error) => { if (error.response?.status === 401) { localStorage.clear() diff --git a/admin-web/src/api/crm.ts b/admin-web/src/api/crm.ts index 4e023b2..b149bcc 100644 --- a/admin-web/src/api/crm.ts +++ b/admin-web/src/api/crm.ts @@ -8,10 +8,42 @@ export function listPersons() { return client.get('/api/v1/person/list') } -export function listTags() { - return client.get('/api/v1/tag/list') +export function updatePerson(id: number, data: any) { + return client.put(`/api/v1/person/${id}`, data) +} + +export function deletePerson(id: number) { + return client.delete(`/api/v1/person/${id}`) +} + +export function listPersonIdentities(personId: number) { + return client.get(`/api/v1/person/${personId}/identities`) +} + +export function createPersonIdentity(personId: number, data: any) { + return client.post(`/api/v1/person/${personId}/identities`, data) +} + +export function deletePersonIdentity(personId: number, identityId: number) { + return client.delete(`/api/v1/person/${personId}/identities/${identityId}`) +} + +export function bindPersonTags(personId: number, data: any) { + return client.post(`/api/v1/person/${personId}/bind-tags`, data) } export function createTag(data: any) { return client.post('/api/v1/tag', data) } + +export function listTags() { + return client.get('/api/v1/tag/list') +} + +export function updateTag(id: number, data: any) { + return client.put(`/api/v1/tag/${id}`, data) +} + +export function deleteTag(id: number) { + return client.delete(`/api/v1/tag/${id}`) +} diff --git a/admin-web/src/api/inboundFlow.ts b/admin-web/src/api/inboundFlow.ts new file mode 100644 index 0000000..c3a398d --- /dev/null +++ b/admin-web/src/api/inboundFlow.ts @@ -0,0 +1,75 @@ +import client from './client' + +export interface FlowNodeConfig { + [key: string]: any +} + +export interface FlowNode { + id: string + type: string + config: FlowNodeConfig +} + +export interface FlowEdge { + source: string + target: string + label?: string +} + +export interface FlowDefinition { + nodes: FlowNode[] + edges: FlowEdge[] +} + +export interface FlowRecord { + id: number + flowCode: string + flowName: string + flowType: number + status: number + definitionJson: string | FlowDefinition + version?: number + createTime?: string + updateTime?: string +} + +export interface SaveFlowPayload { + flowCode: string + flowName: string + flowType?: string + definitionJson: FlowDefinition +} + +export function listFlows() { + return client.get('/api/v1/inbound/flow/list') +} + +export function getFlow(flowCode: string) { + return client.get(`/api/v1/inbound/flow/${flowCode}`) +} + +export function saveFlow(data: SaveFlowPayload) { + return client.post('/api/v1/inbound/flow', { + ...data, + flowType: data.flowType || 'inbound', + }) +} + +export function updateFlow(flowCode: string, data: SaveFlowPayload) { + return client.post(`/api/v1/inbound/flow/${flowCode}`, { + ...data, + flowType: data.flowType || 'inbound', + }) +} + +export function publishFlow(flowCode: string) { + return client.post(`/api/v1/inbound/flow/${flowCode}/publish`) +} + +export function offlineFlow(flowCode: string) { + return client.post(`/api/v1/inbound/flow/${flowCode}/offline`) +} + +export function deleteFlow(flowCode: string) { + return client.delete(`/api/v1/inbound/flow/${flowCode}`) +} diff --git a/admin-web/src/api/inboundWebhook.ts b/admin-web/src/api/inboundWebhook.ts new file mode 100644 index 0000000..c388053 --- /dev/null +++ b/admin-web/src/api/inboundWebhook.ts @@ -0,0 +1,13 @@ +import client from './client' + +export function getInboundWebhookConfig() { + return client.get('/api/v1/account/tenant/webhook') +} + +export function updateInboundWebhookConfig(data: any) { + return client.post('/api/v1/account/tenant/webhook', data) +} + +export function testInboundWebhook(data: { url: string }) { + return client.post('/api/v1/account/tenant/webhook/test', data) +} diff --git a/admin-web/src/api/ivrFlow.ts b/admin-web/src/api/ivrFlow.ts new file mode 100644 index 0000000..a18c30c --- /dev/null +++ b/admin-web/src/api/ivrFlow.ts @@ -0,0 +1,75 @@ +import client from './client' + +export interface FlowNodeConfig { + [key: string]: any +} + +export interface FlowNode { + id: string + type: string + config: FlowNodeConfig +} + +export interface FlowEdge { + source: string + target: string + label?: string +} + +export interface FlowDefinition { + nodes: FlowNode[] + edges: FlowEdge[] +} + +export interface FlowRecord { + id: number + flowCode: string + flowName: string + flowType: number + status: number + definitionJson: string | FlowDefinition + version?: number + createTime?: string + updateTime?: string +} + +export interface SaveFlowPayload { + flowCode: string + flowName: string + flowType?: string + definitionJson: FlowDefinition +} + +export function listFlows() { + return client.get('/api/v1/flow/list') +} + +export function getFlow(flowCode: string) { + return client.get(`/api/v1/flow/${flowCode}`) +} + +export function saveFlow(data: SaveFlowPayload) { + return client.post('/api/v1/flow', { + ...data, + flowType: data.flowType || 'ivr', + }) +} + +export function updateFlow(flowCode: string, data: SaveFlowPayload) { + return client.post(`/api/v1/flow/${flowCode}`, { + ...data, + flowType: data.flowType || 'ivr', + }) +} + +export function publishFlow(flowCode: string) { + return client.post(`/api/v1/flow/${flowCode}/publish`) +} + +export function offlineFlow(flowCode: string) { + return client.post(`/api/v1/flow/${flowCode}/offline`) +} + +export function deleteFlow(flowCode: string) { + return client.delete(`/api/v1/flow/${flowCode}`) +} diff --git a/admin-web/src/api/sendPolicy.ts b/admin-web/src/api/sendPolicy.ts new file mode 100644 index 0000000..26a9d42 --- /dev/null +++ b/admin-web/src/api/sendPolicy.ts @@ -0,0 +1,25 @@ +import client from './client' + +export function listPolicyGroups() { + return client.get('/api/v1/policy-group/list') +} + +export function getPolicyGroup(groupCode: string) { + return client.get(`/api/v1/policy-group/${groupCode}`) +} + +export function createPolicyGroup(data: any) { + return client.post('/api/v1/policy-group', data) +} + +export function updatePolicyGroup(groupCode: string, data: any) { + return client.post(`/api/v1/policy-group/${groupCode}`, data) +} + +export function updatePolicyGroupStatus(groupCode: string, status: number) { + return client.post(`/api/v1/policy-group/${groupCode}/status`, { status }) +} + +export function deletePolicyGroup(groupCode: string) { + return client.post(`/api/v1/policy-group/${groupCode}/delete`) +} diff --git a/admin-web/src/api/sendRecord.ts b/admin-web/src/api/sendRecord.ts new file mode 100644 index 0000000..3c8de4a --- /dev/null +++ b/admin-web/src/api/sendRecord.ts @@ -0,0 +1,31 @@ +import client from './client' + +export interface QueryRecordsParams { + taskId?: string + start?: string + end?: string + channelType?: string + status?: number | string + page?: number + size?: number +} + +export function queryRecords(params: QueryRecordsParams) { + return client.get('/api/v1/send/records', { params }) +} + +export function getTaskStatus(taskId: string) { + return client.get(`/api/v1/send/${taskId}/status`) +} + +export function cancelTask(taskId: string) { + return client.post(`/api/v1/send/${taskId}/cancel`) +} + +export function testSend(data: any) { + return client.post('/api/v1/send/test', data) +} + +export function getStatistics(start: string, end: string) { + return client.get('/api/v1/send/statistics', { params: { start, end } }) +} diff --git a/admin-web/src/components/Layout.vue b/admin-web/src/components/Layout.vue index 15444aa..bd42040 100644 --- a/admin-web/src/components/Layout.vue +++ b/admin-web/src/components/Layout.vue @@ -27,12 +27,21 @@ 入站流程 + + IVR 流程 + 流程追踪 消息测试 + + 入站 Webhook + + + 发送策略 + 租户管理 diff --git a/admin-web/src/router/index.ts b/admin-web/src/router/index.ts index 9b7d78a..84472df 100644 --- a/admin-web/src/router/index.ts +++ b/admin-web/src/router/index.ts @@ -8,10 +8,13 @@ import Person from '../views/Person.vue' import Tag from '../views/Tag.vue' import SendRecord from '../views/SendRecord.vue' import InboundFlow from '../views/InboundFlow.vue' +import IvrFlow from '../views/IvrFlow.vue' import FlowTrace from '../views/FlowTrace.vue' import MessageTest from '../views/MessageTest.vue' import TenantManagement from '../views/TenantManagement.vue' import UserManagement from '../views/UserManagement.vue' +import InboundWebhook from '../views/InboundWebhook.vue' +import SendPolicy from '../views/SendPolicy.vue' import Login from '../views/Login.vue' import { useAuthStore } from '../stores/auth' @@ -34,8 +37,11 @@ const routes = [ { path: 'tag', component: Tag, meta: { title: '标签' } }, { path: 'send-record', component: SendRecord, meta: { title: '发送记录' } }, { path: 'inbound-flow', component: InboundFlow, meta: { title: '入站流程' } }, + { path: 'ivr-flow', component: IvrFlow, meta: { title: 'IVR 流程' } }, { path: 'flow-trace', component: FlowTrace, meta: { title: '流程追踪' } }, { path: 'message-test', component: MessageTest, meta: { title: '消息测试' } }, + { path: 'inbound-webhook', component: InboundWebhook, meta: { title: '入站 Webhook' } }, + { path: 'send-policy', component: SendPolicy, meta: { title: '发送策略' } }, { path: 'tenant-management', component: TenantManagement, meta: { title: '租户管理' } }, { path: 'user-management', component: UserManagement, meta: { title: '用户管理' } }, ], diff --git a/admin-web/src/stores/auth.ts b/admin-web/src/stores/auth.ts index 8ee8c1f..5f74f6c 100644 --- a/admin-web/src/stores/auth.ts +++ b/admin-web/src/stores/auth.ts @@ -1,11 +1,21 @@ import { defineStore } from 'pinia' import { ref, computed } from 'vue' +function parseJson(value: string | null): string[] { + if (!value) return [] + try { + const parsed = JSON.parse(value) + return Array.isArray(parsed) ? parsed : [] + } catch { + return [] + } +} + export const useAuthStore = defineStore('auth', () => { const token = ref(localStorage.getItem('mci-token') || '') const tenantCode = ref(localStorage.getItem('mci-tenant-code') || '') - const username = ref('') - const roleCodes = ref([]) + const username = ref(localStorage.getItem('mci-username') || '') + const roleCodes = ref(parseJson(localStorage.getItem('mci-role-codes'))) const isLoggedIn = computed(() => !!token.value) @@ -16,6 +26,8 @@ export const useAuthStore = defineStore('auth', () => { roleCodes.value = roles localStorage.setItem('mci-token', t) localStorage.setItem('mci-tenant-code', tc) + localStorage.setItem('mci-username', user) + localStorage.setItem('mci-role-codes', JSON.stringify(roles)) } function clearAuth() { @@ -25,6 +37,8 @@ export const useAuthStore = defineStore('auth', () => { roleCodes.value = [] localStorage.removeItem('mci-token') localStorage.removeItem('mci-tenant-code') + localStorage.removeItem('mci-username') + localStorage.removeItem('mci-role-codes') } return { token, tenantCode, username, roleCodes, isLoggedIn, setAuth, clearAuth } diff --git a/admin-web/src/views/ChannelAccount.vue b/admin-web/src/views/ChannelAccount.vue index 2620fb2..0c30cbb 100644 --- a/admin-web/src/views/ChannelAccount.vue +++ b/admin-web/src/views/ChannelAccount.vue @@ -1,68 +1,192 @@ + + diff --git a/admin-web/src/views/ChannelSubject.vue b/admin-web/src/views/ChannelSubject.vue index 736c9e4..ceacfda 100644 --- a/admin-web/src/views/ChannelSubject.vue +++ b/admin-web/src/views/ChannelSubject.vue @@ -1,59 +1,173 @@ + + diff --git a/admin-web/src/views/Conversation.vue b/admin-web/src/views/Conversation.vue index 5f90592..06e23ca 100644 --- a/admin-web/src/views/Conversation.vue +++ b/admin-web/src/views/Conversation.vue @@ -1,54 +1,99 @@ + + diff --git a/admin-web/src/views/FlowTrace.vue b/admin-web/src/views/FlowTrace.vue index 3b48999..1f35977 100644 --- a/admin-web/src/views/FlowTrace.vue +++ b/admin-web/src/views/FlowTrace.vue @@ -1,13 +1,9 @@ @@ -107,4 +110,10 @@ async function handleSubmit() { width: 100%; margin-top: 8px; } +.login-hint { + margin-top: 16px; + font-size: 12px; + color: #909399; + text-align: center; +} diff --git a/admin-web/src/views/MessageTest.vue b/admin-web/src/views/MessageTest.vue index ea162af..2b87ec0 100644 --- a/admin-web/src/views/MessageTest.vue +++ b/admin-web/src/views/MessageTest.vue @@ -1,6 +1,5 @@