feat(ai): 完善知识库接口

This commit is contained in:
v-zhangjc9
2025-05-16 19:00:55 +08:00
parent be976290b6
commit 5d49c82190
22 changed files with 800 additions and 128 deletions

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GitCommitMessageStorage">
<option name="messageStorage">
<MessageStorage />
</option>
</component>
</project>

View File

@@ -0,0 +1,6 @@
#!/bin/bash
root_path=$(dirname $(cd $(dirname $0);pwd))
source $(realpath $root_path/..)/bin/library.sh
deploy service-ai-core
package service-ai-chat
upload $root_path/service-ai-chat/target/service-ai-chat-1.0.0-SNAPSHOT.jar

View File

@@ -30,6 +30,10 @@
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-qdrant-store-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-markdown-document-reader</artifactId>
</dependency>
</dependencies>
<build>

View File

@@ -1,19 +1,13 @@
package com.lanyuanxiaoyao.service.ai.knowledge;
import com.ulisesbocchio.jasyptspringboot.annotation.EnableEncryptableProperties;
import jakarta.annotation.Resource;
import java.net.MalformedURLException;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.model.Media;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.core.io.FileUrlResource;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.util.MimeTypeUtils;
/**
* @author lanyuanxiaoyao
@@ -25,35 +19,11 @@ import org.springframework.util.MimeTypeUtils;
@EnableEncryptableProperties
@EnableRetry
public class KnowledgeApplication implements ApplicationRunner {
@Resource
private ChatClient.Builder builder;
public static void main(String[] args) {
SpringApplication.run(KnowledgeApplication.class, args);
}
@Override
public void run(ApplicationArguments args) throws Exception {
ChatClient client = builder.build();
String content = client.prompt()
.user(
prompt -> {
try {
prompt
.text("如实描述图片中的内容,不要加入自己的思考以及与图片内容无关的任何文本")
.media(
Media.builder()
.mimeType(MimeTypeUtils.IMAGE_PNG)
.data(new FileUrlResource("/Users/lanyuanxiaoyao/Pictures/数据使用合同签订.png"))
.build()
);
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
)
.call()
.content();
System.out.println(content);
public void run(ApplicationArguments args) {
}
}

View File

@@ -1,9 +1,28 @@
package com.lanyuanxiaoyao.service.ai.knowledge.controller;
import jakarta.annotation.PostConstruct;
import com.lanyuanxiaoyao.service.ai.knowledge.entity.CollectionVO;
import com.lanyuanxiaoyao.service.ai.knowledge.entity.PointVO;
import io.qdrant.client.QdrantClient;
import io.qdrant.client.grpc.Collections;
import io.qdrant.client.grpc.Points;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.eclipse.collections.api.factory.Lists;
import org.eclipse.collections.api.list.ImmutableList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.qdrant.QdrantVectorStore;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
/**
@@ -15,10 +34,108 @@ import org.springframework.web.bind.annotation.RestController;
public class KnowledgeController {
private static final Logger logger = LoggerFactory.getLogger(KnowledgeController.class);
public KnowledgeController() {
private final QdrantClient client;
private final EmbeddingModel embeddingModel;
public KnowledgeController(VectorStore vectorStore, EmbeddingModel embeddingModel) {
client = (QdrantClient) vectorStore.getNativeClient().orElseThrow();
this.embeddingModel = embeddingModel;
}
@PostConstruct
public void initial() {
@PostMapping("add")
public void add(
@RequestParam("name") String name,
@RequestParam("strategy") String strategy
) throws ExecutionException, InterruptedException {
logger.info("Enter method: add[name, strategy]. name:{},strategy:{}", name, strategy);
client.createCollectionAsync(
name,
Collections.VectorParams.newBuilder()
.setDistance(Collections.Distance.valueOf(strategy))
.setSize(embeddingModel.dimensions())
.build()
).get();
}
@GetMapping("list")
public ImmutableList<CollectionVO> list() throws ExecutionException, InterruptedException {
return client.listCollectionsAsync()
.get()
.stream()
.collect(Collectors.toCollection(Lists.mutable::empty))
.collect(name -> {
try {
Collections.CollectionInfo info = client.getCollectionInfoAsync(name).get();
CollectionVO vo = new CollectionVO();
vo.setName(name);
vo.setPoints(info.getPointsCount());
vo.setSegments(info.getSegmentsCount());
vo.setStatus(info.getStatus().name());
Collections.VectorParams vectorParams = info.getConfig().getParams().getVectorsConfig().getParams();
vo.setStrategy(vectorParams.getDistance().name());
vo.setSize(vectorParams.getSize());
return vo;
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
})
.toImmutable();
}
@GetMapping("list_points")
public ImmutableList<PointVO> listPoints(@RequestParam("name") String name) throws ExecutionException, InterruptedException {
Points.ScrollResponse response = client.scrollAsync(
Points.ScrollPoints.newBuilder()
.setCollectionName(name)
// .setLimit(2)
.setWithPayload(Points.WithPayloadSelector.newBuilder().setEnable(true).build())
.setWithVectors(Points.WithVectorsSelector.newBuilder().setEnable(false).build())
.build()
)
.get();
return response.getResultList()
.stream()
.collect(Collectors.toCollection(Lists.mutable::empty))
.collect(point -> {
PointVO vo = new PointVO();
vo.setId(point.getId().getUuid());
vo.setText(point.getPayloadMap().get("doc_content").getStringValue());
return vo;
})
.toImmutable();
}
@GetMapping("delete")
public void delete(@RequestParam("name") String name) throws ExecutionException, InterruptedException {
client.deleteCollectionAsync(name).get();
}
@PostMapping(value = "preview_text", consumes = "text/plain;charset=utf-8")
public ImmutableList<String> previewText(
@RequestParam("name") String name,
@RequestParam(value = "mode", defaultValue = "normal") String mode,
@RequestBody String text
) {
return Lists.immutable.empty();
}
@PostMapping(value = "process_text", consumes = "text/plain;charset=utf-8")
public void processText(
@RequestParam("name") String name,
@RequestBody String text
) {
VectorStore source = QdrantVectorStore.builder(client, embeddingModel)
.collectionName(name)
.initializeSchema(true)
.build();
MarkdownDocumentReader reader = new MarkdownDocumentReader(
new ByteArrayResource(text.getBytes(StandardCharsets.UTF_8)),
MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.build()
);
source.add(reader.get());
}
}

View File

@@ -0,0 +1,74 @@
package com.lanyuanxiaoyao.service.ai.knowledge.entity;
/**
* @author lanyuanxiaoyao
* @version 20250516
*/
public class CollectionVO {
private String name;
private String strategy;
private Long size;
private Long points;
private Long segments;
private String status;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getStrategy() {
return strategy;
}
public void setStrategy(String strategy) {
this.strategy = strategy;
}
public Long getSize() {
return size;
}
public void setSize(Long size) {
this.size = size;
}
public Long getPoints() {
return points;
}
public void setPoints(Long points) {
this.points = points;
}
public Long getSegments() {
return segments;
}
public void setSegments(Long segments) {
this.segments = segments;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
@Override
public String toString() {
return "CollectionVO{" +
"name='" + name + '\'' +
", strategy='" + strategy + '\'' +
", size=" + size +
", points=" + points +
", segments=" + segments +
", status='" + status + '\'' +
'}';
}
}

View File

@@ -0,0 +1,34 @@
package com.lanyuanxiaoyao.service.ai.knowledge.entity;
/**
* @author lanyuanxiaoyao
* @version 20250516
*/
public class PointVO {
private String id;
private String text;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
return "PointVO{" +
"id='" + id + '\'' +
", text='" + text + '\'' +
'}';
}
}

View File

@@ -24,18 +24,17 @@ spring:
darkcode: ENC(0jzpQ7T6S+P7bZrENgYsUoLhlqGvw7DA2MN3BRqEOwq7plhtg72vuuiPQNnr3DaYz0CpyTvxInhpx11W3VZ1trD6NINh7O3LN70ZqO5pWXk=)
ai:
openai:
base-url: http://localhost:3000
api-key: '*XMySqV%>hR&v>>g*NwCs3tpQ5FVMFEF2VHVTj<MYQd$&@$sY7CgqNyea4giJi4'
base-url: http://132.121.206.65:10086
api-key: ENC(K+Hff9QGC+fcyi510VIDd9CaeK/IN5WBJ9rlkUsHEdDgIidW+stHHJlsK0lLPUXXREha+ToQZqqDXJrqSE+GUKCXklFhelD8bRHFXBIeP/ZzT2cxhzgKUXgjw3S0Qw2R)
chat:
options:
model: '/models/InternVL3-1B-Instruct-Q8_0.gguf'
model: 'Qwen3-1.7'
embedding:
options:
model: 'Bge-m3'
# vectorstore:
# qdrant:
# api-key: lanyuanxiaoyao
# initialize-schema: true
vectorstore:
qdrant:
api-key: lanyuanxiaoyao
jasypt:
encryptor:
password: 'r#(R,P"Dp^A47>WSn:Wn].gs/+"v:q_Q*An~zF*g-@j@jtSTv5H/,S-3:R?r9R}.'

View File

@@ -0,0 +1,105 @@
import React from 'react'
import {useParams} from 'react-router'
import {amisRender, crudCommonOptions} from '../../../util/amis.tsx'
const DataDetail: React.FC = () => {
const {name} = useParams()
return (
<div className="import-detail h-full">
{amisRender(
{
className: 'h-full',
type: 'page',
title: `数据详情 (知识库:${name}`,
size: 'lg',
actions: [],
body: [
{
type: 'crud',
api: {
url: 'http://127.0.0.1:8080/knowledge/list_points?name=${name}',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
...crudCommonOptions(),
headerToolbar: [
'reload',
],
columns: [
{
name: 'id',
hidden: true,
},
{
name: 'text',
label: '内容',
},
{
type: 'operation',
label: '操作',
width: 100,
buttons: [
{
type: 'action',
label: '编辑',
level: 'link',
size: 'lg',
actionType: 'dialog',
dialog: {
title: '编辑文段',
size: 'md',
body: {
type: 'form',
body: [
{
type: 'input-text',
name: 'id',
disabled: true,
label: '文段ID',
},
{
type: 'editor',
label: '内容',
name: 'text',
language: 'plaintext',
options: {
lineNumbers: 'off',
wordWrap: 'bounded',
},
},
],
},
},
},
{
type: 'action',
label: '删除',
className: 'text-danger hover:text-red-600',
level: 'link',
size: 'xs',
actionType: 'ajax',
api: {
method: 'get',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
confirmText: '确认删除',
confirmTitle: '删除',
},
],
},
],
},
],
},
{
name: name,
},
)}
</div>
)
}
export default DataDetail

View File

@@ -0,0 +1,144 @@
import React from 'react'
import {useParams} from 'react-router'
import styled from 'styled-components'
import {amisRender} from '../../../util/amis.tsx'
const ImportDataDiv = styled.div`
.antd-EditorControl {
min-height: 500px !important;
}
`
const DataImport: React.FC = () => {
const {name} = useParams()
return (
<ImportDataDiv className="import-data h-full">
{amisRender({
type: 'page',
title: `数据导入 (知识库:${name}`,
body: [
[
{
className: 'h-full',
type: 'grid',
columns: [
{
body: [
{
type: 'form',
wrapWithPanel: false,
mode: 'horizontal',
actions: [],
body: [
{
name: 'mode',
type: 'radios',
label: '解析模式',
value: 'normal',
options: [
{
value: 'normal',
label: '常规模式',
},
{
value: 'llm',
label: '智能模式',
},
{
value: 'qa',
label: 'Q/A模式',
},
],
},
{
name: 'type',
type: 'radios',
label: '数据形式',
value: 'text',
options: [
{
value: 'text',
label: '文本',
},
{
value: 'file',
label: '文件',
},
],
},
{
visibleOn: 'type === \'text\'',
type: 'editor',
label: '数据内容',
name: 'content',
language: 'plaintext',
options: {
lineNumbers: 'off',
wordWrap: 'bounded',
},
},
{
visibleOn: 'type === \'file\'',
type: 'input-file',
name: 'files',
label: '数据文件',
accept: '.txt,.csv',
autoUpload: false,
drag: true,
multiple: true,
},
{
className: 'text-right',
type: 'button-toolbar',
buttons: [
{
type: 'action',
label: '预览',
},
{
type: 'submit',
label: '提交',
level: 'primary',
},
],
},
],
},
],
},
{
body: [
{
type: 'card',
className: 'h-full',
header: {
title: '解析预览',
subTitle: '截取部份文本进行解析预览',
},
body: [
{
type: 'list',
source: '${rows}',
listItem: [
{
body: {
type: 'tpl',
tpl: '${content}',
},
},
],
},
],
},
],
},
],
},
],
],
})}
</ImportDataDiv>
)
}
export default DataImport

View File

@@ -0,0 +1,176 @@
import React from 'react'
import {useNavigate} from 'react-router'
import {amisRender, crudCommonOptions, mappingField, mappingItem} from '../../../util/amis.tsx'
const strategyMapping = [
mappingItem('文本', 'Cosine'),
mappingItem('图片', 'Euclid'),
]
const statusMapping = [
mappingItem('正常', 'Green', 'label-success'),
mappingItem('优化中', 'Yellow', 'label-warning'),
mappingItem('错误', 'Red', 'label-danger'),
mappingItem('等待中', 'Grey', 'label-primary'),
]
const Knowledge: React.FC = () => {
const navigate = useNavigate()
return (
<div className="knowledge">
{amisRender(
{
type: 'page',
title: '知识库',
body: [
{
type: 'crud',
api: {
url: 'http://127.0.0.1:8080/knowledge/list',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
...crudCommonOptions(),
headerToolbar: [
'reload',
{
type: 'action',
label: '',
icon: 'fa fa-plus',
actionType: 'dialog',
dialog: {
title: '新增知识库',
size: 'md',
body: {
type: 'form',
api: {
url: 'http://127.0.0.1:8080/knowledge/add',
dataType: 'form',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
body: [
{
type: 'input-text',
name: 'name',
label: '名称',
},
{
type: 'select',
name: 'strategy',
label: '类型',
value: 'Cosine',
options: [
{
label: '文本',
value: 'Cosine',
},
{
label: '图片',
value: 'Euclid',
disabled: true,
},
],
},
],
},
},
},
],
columns: [
{
name: 'name',
label: '名称',
},
{
label: '类型',
width: 80,
align: 'center',
...mappingField('strategy', strategyMapping),
},
{
name: 'points',
label: '文本数',
width: 80,
align: 'center',
},
{
label: '状态',
width: 80,
align: 'center',
...mappingField('status', statusMapping),
},
{
type: 'operation',
label: '操作',
width: 150,
buttons: [
{
type: 'action',
label: '详情',
level: 'link',
size: 'xs',
onEvent: {
click: {
actions: [
{
actionType: 'custom',
// @ts-ignore
script: (context, action, event) => {
navigate(`/ai/knowledge/detail/${context.props.data['name']}`)
},
},
],
},
},
},
{
type: 'action',
label: '导入',
level: 'link',
size: 'xs',
onEvent: {
click: {
actions: [
{
actionType: 'custom',
// @ts-ignore
script: (context, action, event) => {
navigate(`/ai/knowledge/import/${context.props.data['name']}`)
},
},
],
},
},
},
{
type: 'action',
label: '删除',
className: 'text-danger hover:text-red-600',
level: 'link',
size: 'xs',
actionType: 'ajax',
api: {
method: 'get',
url: 'http://127.0.0.1:8080/knowledge/delete?name=${name}',
headers: {
'Authorization': 'Basic QXhoRWJzY3dzSkRiWU1IMjpjWXhnM2I0UHRXb1ZENVNqRmF5V3h0blNWc2p6UnNnNA==',
},
},
confirmText: '确认删除',
confirmTitle: '删除',
},
],
},
],
},
],
},
)}
</div>
)
}
export default Knowledge

View File

@@ -6,7 +6,7 @@ import {
serviceLogByAppName,
serviceLogByAppNameAndHost,
time,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const cloudCrud = (title: string, path: string) => {
return {

View File

@@ -1,5 +1,5 @@
import React from 'react'
import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../../util/amis.ts'
import {amisRender, commonInfo, crudCommonOptions, readOnlyDialogOptions} from '../../util/amis.tsx'
const color = (number: number) => {
let color = 'text-success'

View File

@@ -6,7 +6,7 @@ import {
paginationCommonOptions,
time,
yarnQueueCrud,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const queueCrud = (name: string) => {
return {

View File

@@ -14,7 +14,7 @@ import {
tableMetaDialog,
tableRunningStateMapping,
timeAndFrom,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
function Table() {
return (

View File

@@ -1,5 +1,5 @@
import React from 'react'
import {amisRender, commonInfo, paginationCommonOptions, serviceLogByAppName, yarnCrudColumns} from '../../util/amis.ts'
import {amisRender, commonInfo, paginationCommonOptions, serviceLogByAppName, yarnCrudColumns} from '../../util/amis.tsx'
const Task: React.FC = () => {
return (

View File

@@ -10,7 +10,7 @@ import {
paginationCommonOptions,
readOnlyDialogOptions,
timelineColumns,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const Tool: React.FC = () => {
return (

View File

@@ -10,7 +10,7 @@ import {
paginationCommonOptions,
tableMetaDialog,
versionUpdateStateMapping,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
function Version() {
return (

View File

@@ -7,7 +7,7 @@ import {
paginationCommonOptions,
yarnCrudColumns,
yarnQueueCrud,
} from '../../util/amis.ts'
} from '../../util/amis.tsx'
const Yarn: React.FC = () => {
const {clusters, queue, search} = useParams()

View File

@@ -1,5 +1,5 @@
import React from 'react'
import {amisRender, commonInfo, yarnQueueCrud} from '../../util/amis.ts'
import {amisRender, commonInfo, yarnQueueCrud} from '../../util/amis.tsx'
const YarnCluster: React.FC = () => {
return (

View File

@@ -3,6 +3,7 @@ import {
CloudOutlined,
ClusterOutlined,
CompressOutlined,
DatabaseOutlined,
InfoCircleOutlined,
OpenAIOutlined,
QuestionOutlined,
@@ -14,6 +15,9 @@ import {
import {Navigate, type RouteObject} from 'react-router'
import Conversation from './pages/ai/Conversation.tsx'
import Inspection from './pages/ai/Inspection.tsx'
import DataDetail from './pages/ai/knowledge/DataDetail.tsx'
import DataImport from './pages/ai/knowledge/DataImport.tsx'
import Knowledge from './pages/ai/knowledge/Knowledge.tsx'
import App from './pages/App.tsx'
import Cloud from './pages/overview/Cloud.tsx'
import Overview from './pages/overview/Overview.tsx'
@@ -24,7 +28,7 @@ import Tool from './pages/overview/Tool.tsx'
import Version from './pages/overview/Version.tsx'
import Yarn from './pages/overview/Yarn.tsx'
import YarnCluster from './pages/overview/YarnCluster.tsx'
import {commonInfo} from './util/amis.ts'
import {commonInfo} from './util/amis.tsx'
export const routes: RouteObject[] = [
{
@@ -86,6 +90,18 @@ export const routes: RouteObject[] = [
path: 'conversation',
Component: Conversation,
},
{
path: 'knowledge',
Component: Knowledge,
},
{
path: 'knowledge/import/:name',
Component: DataImport,
},
{
path: 'knowledge/detail/:name',
Component: DataDetail,
},
],
},
],
@@ -179,6 +195,11 @@ export const menus = {
name: '智能巡检',
icon: <CheckSquareOutlined/>,
},
{
path: '/ai/knowledge',
name: '知识库',
icon: <DatabaseOutlined/>,
},
],
},
],

View File

@@ -1,4 +1,4 @@
import {attachmentAdpator, makeTranslator, render, type Schema} from 'amis'
import {AlertComponent, attachmentAdpator, makeTranslator, render, type Schema, ToastComponent} from 'amis'
import 'amis/lib/themes/antd.css'
import 'amis/lib/helper.css'
@@ -58,85 +58,99 @@ const responseAdaptor = () => (response: any) => {
}
}
export const amisRender = (schema: Schema) => {
return render(
schema,
{
theme: 'antd',
},
{
fetcher: async (api: any) => {
let {url, method, data, responseType, config, headers} = api
config = config || {}
config.url = url
config.withCredentials = true
responseType && (config.responseType = responseType)
export const amisRender = (schema: Schema, data: Record<any, any> = {}) => {
const theme = 'antd'
const locale = 'zh-CN'
return (
<>
<ToastComponent
theme={theme}
key="toast"
position={'top-right'}
locale={locale}
/>
<AlertComponent theme={theme} key="alert" locale={locale}/>
{render(
schema,
{
data: data,
theme: theme,
},
{
fetcher: async (api: any) => {
let {url, method, data, responseType, config, headers} = api
config = config || {}
config.url = url
config.withCredentials = true
responseType && (config.responseType = responseType)
if (config.cancelExecutor) {
config.cancelToken = new (axios as any).CancelToken(
config.cancelExecutor,
)
}
config.headers = headers || {}
config.method = method
config.data = data
if (method === 'get' && data) {
config.params = data
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data)
config.headers['Content-Type'] = 'application/json'
}
// 支持返回各种报错信息
config.validateStatus = function () {
return true
}
let response = await axios(config)
response = await attachmentAdpator(response, __, api)
response = responseAdaptor()(response)
if (response.status >= 400) {
if (response.data) {
// 主要用于 raw: 模式下,后端自己校验登录,
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href),
)
return new Promise(() => {
})
} else if (response.data.msg) {
throw new Error(response.data.msg)
} else {
throw new Error(
'System.requestError' + JSON.stringify(response.data, null, 2),
if (config.cancelExecutor) {
config.cancelToken = new (axios as any).CancelToken(
config.cancelExecutor,
)
}
} else {
throw new Error(
`${'System.requestErrorStatus'} ${response.status}`,
)
}
}
return response
},
isCancel: (value: any) => (axios as any).isCancel(value),
},
config.headers = headers || {}
config.method = method
config.data = data
if (method === 'get' && data) {
config.params = data
} else if (data && data instanceof FormData) {
// config.headers['Content-Type'] = 'multipart/form-data';
} else if (
data &&
typeof data !== 'string' &&
!(data instanceof Blob) &&
!(data instanceof ArrayBuffer)
) {
data = JSON.stringify(data)
config.headers['Content-Type'] = 'application/json'
}
// 支持返回各种报错信息
config.validateStatus = function () {
return true
}
let response = await axios(config)
response = await attachmentAdpator(response, __, api)
response = responseAdaptor()(response)
if (response.status >= 400) {
if (response.data) {
// 主要用于 raw: 模式下,后端自己校验登录,
if (
response.status === 401 &&
response.data.location &&
response.data.location.startsWith('http')
) {
location.href = response.data.location.replace(
'{{redirect}}',
encodeURIComponent(location.href),
)
return new Promise(() => {
})
} else if (response.data.msg) {
throw new Error(response.data.msg)
} else {
throw new Error(
'System.requestError' + JSON.stringify(response.data, null, 2),
)
}
} else {
throw new Error(
`${'System.requestErrorStatus'} ${response.status}`,
)
}
}
return response
},
isCancel: (value: any) => (axios as any).isCancel(value),
},
)}
</>
)
}