docs(backend): 重构后端README文档,完善项目文档与开发指南

- 全面更新README,添加项目特性、架构设计与技术栈介绍
- 详细补充项目结构说明及模块设计规范
- 增加快速开始步骤,包括环境配置、数据库初始化和服务启动
- 补充主要API模块路径和认证授权使用示例
- 添加开发指南、数据库迁移与测试方法
- 集成监控、日志级别说明及性能监控内容
- 完善Docker、传统部署及Nginx配置示例
- 添加贡献指南和代码规范说明
- 新增MCP模块概述及智能对话API接口文档
- 清理和移除module_ai中旧的mcp_server相关实现代码
- 在api/v1初始化文件中注册AI模块路由
- 修改resource模块,增强资源路径安全检查和文件类型检测逻辑
- 增强资源搜索和上传服务的健壮性及安全性
This commit is contained in:
zhangtao
2025-09-08 00:45:34 +08:00
parent 8cb93d31d1
commit 01fd6c8ea4
66 changed files with 5692 additions and 5193 deletions
+2 -13
View File
@@ -1,11 +1,5 @@
<template>
<wd-calendar
v-model="dateRange"
:label="label"
type="daterange"
:placeholder="placeholder"
@confirm="handleConfirm"
/>
<wd-calendar v-model="dateRange" :label="label" type="daterange" :placeholder="placeholder" @confirm="handleConfirm" />
</template>
<script lang="ts" setup>
@@ -38,12 +32,7 @@ watch(
} else if (typeof val === "string" && val.includes(",")) {
const [startDate, endDate] = val.split(",");
if (
startDate &&
endDate &&
!isNaN(new Date(startDate).getTime()) &&
!isNaN(new Date(endDate).getTime())
) {
if (startDate && endDate && !isNaN(new Date(startDate).getTime()) && !isNaN(new Date(endDate).getTime())) {
dateRange.value = [new Date(startDate).getTime(), new Date(endDate).getTime()];
} else {
dateRange.value = null;
+5 -27
View File
@@ -1,13 +1,5 @@
<template>
<wd-picker
v-model="selectedValues"
:columns="pickerColumns"
:display-format="displayFormat"
:column-change="handleColumnChange"
:label="label"
:placeholder="pickerColumns.length ? '请选择' : '加载中...'"
:required="required"
/>
<wd-picker v-model="selectedValues" :columns="pickerColumns" :display-format="displayFormat" :column-change="handleColumnChange" :label="label" :placeholder="pickerColumns.length ? '请选择' : '加载中...'" :required="required" />
</template>
<script setup lang="ts">
@@ -80,9 +72,7 @@ const findTreePath = (value: number | string): number[] | string[] => {
* @param treeData 树形数据
* @returns Picker 所需的 columns 格式
*/
const transformTreeToColumns = (
treeData: OptionType[]
): Array<Array<{ label: string; value: string | number }>> => {
const transformTreeToColumns = (treeData: OptionType[]): Array<Array<{ label: string; value: string | number }>> => {
const columns: Array<Array<{ label: string; value: string | number }>> = [];
for (let depth = 0; depth <= selectedValues.value.length; depth++) {
@@ -117,12 +107,7 @@ watch(
/**
* 处理列的变化,动态更新后续列的数据
*/
function handleColumnChange(
pickerView: PickerViewInstance,
value: Record<string, any> | Record<string, any>[],
columnIndex: number,
resolve: () => void
) {
function handleColumnChange(pickerView: PickerViewInstance, value: Record<string, any> | Record<string, any>[], columnIndex: number, resolve: () => void) {
const selectedValue = selectedValues.value[selectedValues.value.length - 1] || undefined;
emits("update:modelValue", selectedValue);
@@ -134,11 +119,7 @@ function handleColumnChange(
/**
* 动态更新所有后续列的数据
*/
function updatePickerColumns(
pickerView: PickerViewInstance,
parentId: string | number,
columnIndex: number
) {
function updatePickerColumns(pickerView: PickerViewInstance, parentId: string | number, columnIndex: number) {
const nextColumnIndex = columnIndex + 1;
const children = findChildren(parentId, props.data);
@@ -151,10 +132,7 @@ function updatePickerColumns(
/**
* 根据节点value查找其子节点数据
*/
function findChildren(
parentId: string | number,
list: Record<string, any>[]
): Record<string, any>[] {
function findChildren(parentId: string | number, list: Record<string, any>[]): Record<string, any>[] {
for (const item of list) {
if (item.value === parentId && item.children) {
return item.children;
@@ -28,17 +28,7 @@
<!-- APP和H5采用renderjs渲染图表 -->
<!-- #ifdef APP-VUE || H5 -->
<block v-if="echarts">
<view
v-show="showchart"
:id="'EC' + cid"
:style="{ background: background }"
style="width: 100%; height: 100%"
:data-directory="directory"
:prop="echartsOpts"
:change:prop="rdcharts.ecinit"
:resize="echartsResize"
:change:resize="rdcharts.ecresize"
/>
<view v-show="showchart" :id="'EC' + cid" :style="{ background: background }" style="width: 100%; height: 100%" :data-directory="directory" :prop="echartsOpts" :change:prop="rdcharts.ecinit" :resize="echartsResize" :change:resize="rdcharts.ecresize" />
</block>
<block v-else>
<view
@@ -53,14 +43,7 @@
v-on:touchmove="rdcharts.touchMove"
v-on:touchend="rdcharts.touchEnd"
>
<canvas
v-show="showchart"
:id="cid"
:canvasId="cid"
:style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
:disable-scroll="disableScroll"
@error="_error"
/>
<canvas v-show="showchart" :id="cid" :canvasId="cid" :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }" :disable-scroll="disableScroll" @error="_error" />
</view>
</block>
<!-- #endif -->
@@ -83,72 +66,25 @@
/>
</block>
<block v-if="!ontouch">
<canvas
v-show="showchart"
:id="cid"
:canvasId="cid"
:width="cWidth * pixel"
:height="cHeight * pixel"
:style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
:disable-scroll="disScroll"
@tap="_tap"
@error="_error"
/>
<canvas v-show="showchart" :id="cid" :canvasId="cid" :width="cWidth * pixel" :height="cHeight * pixel" :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }" :disable-scroll="disScroll" @tap="_tap" @error="_error" />
</block>
<!-- #endif -->
<!-- 其他小程序通过vue渲染图表 -->
<!-- #ifdef MP-WEIXIN || MP-BAIDU || MP-QQ || MP-TOUTIAO || MP-KUAISHOU || MP-LARK || MP-JD || MP-360 -->
<block v-if="type2d">
<view v-if="ontouch" @tap="_tap">
<canvas
v-show="showchart"
:id="cid"
:canvasId="cid"
:style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
type="2d"
:disable-scroll="disScroll"
@touchstart="_touchStart"
@touchmove="_touchMove"
@touchend="_touchEnd"
@error="_error"
/>
<canvas v-show="showchart" :id="cid" :canvasId="cid" :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }" type="2d" :disable-scroll="disScroll" @touchstart="_touchStart" @touchmove="_touchMove" @touchend="_touchEnd" @error="_error" />
</view>
<view v-if="!ontouch" @tap="_tap">
<canvas
v-show="showchart"
:id="cid"
:canvasId="cid"
:style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
type="2d"
:disable-scroll="disScroll"
@error="_error"
/>
<canvas v-show="showchart" :id="cid" :canvasId="cid" :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }" type="2d" :disable-scroll="disScroll" @error="_error" />
</view>
</block>
<block v-if="!type2d">
<view v-if="ontouch" @tap="_tap">
<canvas
v-if="showchart"
:id="cid"
:canvasId="cid"
:style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
:disable-scroll="disScroll"
@touchstart="_touchStart"
@touchmove="_touchMove"
@touchend="_touchEnd"
@error="_error"
/>
<canvas v-if="showchart" :id="cid" :canvasId="cid" :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }" :disable-scroll="disScroll" @touchstart="_touchStart" @touchmove="_touchMove" @touchend="_touchEnd" @error="_error" />
</view>
<view v-if="!ontouch">
<canvas
v-if="showchart"
:id="cid"
:canvasId="cid"
:style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }"
:disable-scroll="disScroll"
@tap="_tap"
@error="_error"
/>
<canvas v-if="showchart" :id="cid" :canvasId="cid" :style="{ width: cWidth + 'px', height: cHeight + 'px', background: background }" :disable-scroll="disScroll" @tap="_tap" @error="_error" />
</view>
</block>
<!-- #endif -->
@@ -166,10 +102,7 @@ function deepCloneAssign(origin = {}, ...args) {
for (let i in args) {
for (let key in args[i]) {
if (args[i].hasOwnProperty(key)) {
origin[key] =
args[i][key] && typeof args[i][key] === "object"
? deepCloneAssign(Array.isArray(args[i][key]) ? [] : {}, origin[key], args[i][key])
: args[i][key];
origin[key] = args[i][key] && typeof args[i][key] === "object" ? deepCloneAssign(Array.isArray(args[i][key]) ? [] : {}, origin[key], args[i][key]) : args[i][key];
}
}
}
@@ -474,11 +407,7 @@ export default {
optsProps: {
handler(val, oldval) {
if (typeof val === "object") {
if (
JSON.stringify(val) !== JSON.stringify(oldval) &&
this.echarts === false &&
this.optsWatch == true
) {
if (JSON.stringify(val) !== JSON.stringify(oldval) && this.echarts === false && this.optsWatch == true) {
this.checkData(this.drawData);
}
} else {
@@ -562,11 +491,7 @@ export default {
}
// #ifdef MP-WEIXIN
this.inWx = true;
if (
this.canvas2d === false ||
systemInfo.platform === "windows" ||
systemInfo.platform === "mac"
) {
if (this.canvas2d === false || systemInfo.platform === "windows" || systemInfo.platform === "mac") {
this.type2d = false;
} else {
this.type2d = true;
@@ -656,12 +581,7 @@ export default {
methods: {
beforeInit() {
this.mixinDatacomErrorMessage = null;
if (
typeof this.chartData === "object" &&
this.chartData != null &&
this.chartData.series !== undefined &&
this.chartData.series.length > 0
) {
if (typeof this.chartData === "object" && this.chartData != null && this.chartData.series !== undefined && this.chartData.series.length > 0) {
//拷贝一下chartData,为了opts变更后统一数据来源
this.drawData = deepCloneAssign({}, this.chartData);
this.mixinDatacomLoading = false;
@@ -920,11 +840,7 @@ export default {
ctx.canvas.willReadFrequently = true;
cfu.option[cid].context = ctx;
cfu.option[cid].rotateLock = cfu.option[cid].rotate;
if (
cfu.instance[cid] &&
cfu.option[cid] &&
cfu.option[cid].update === true
) {
if (cfu.instance[cid] && cfu.option[cid] && cfu.option[cid].update === true) {
this._updataUChart(cid);
} else {
canvas.width = data.width * this.pixel;
@@ -939,8 +855,7 @@ export default {
}
} else {
this.showchart = false;
this.mixinDatacomErrorMessage =
"参数错误:开启2d模式后,未获取到dom节点,canvas-id:" + cid;
this.mixinDatacomErrorMessage = "参数错误:开启2d模式后,未获取到dom节点,canvas-id:" + cid;
}
});
} else {
@@ -1086,10 +1001,7 @@ export default {
offset: offset,
textList: tc.textList,
formatter: (item, category, index, opts) => {
if (
typeof cfu.option[cid].tooltipFormat === "string" &&
cfu.formatter[cfu.option[cid].tooltipFormat]
) {
if (typeof cfu.option[cid].tooltipFormat === "string" && cfu.formatter[cfu.option[cid].tooltipFormat]) {
return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);
} else {
return this._tooltipDefault(item, category, index, opts);
@@ -1099,10 +1011,7 @@ export default {
} else {
cfu.instance[cid].showToolTip(e, {
formatter: (item, category, index, opts) => {
if (
typeof cfu.option[cid].tooltipFormat === "string" &&
cfu.formatter[cfu.option[cid].tooltipFormat]
) {
if (typeof cfu.option[cid].tooltipFormat === "string" && cfu.formatter[cfu.option[cid].tooltipFormat]) {
return cfu.formatter[cfu.option[cid].tooltipFormat](item, category, index, opts);
} else {
return this._tooltipDefault(item, category, index, opts);
@@ -1224,19 +1133,10 @@ export default {
if (cfu.option[cid].enableScroll === true && e.changedTouches.length == 1) {
cfu.instance[cid].scroll(e);
}
if (
this.ontap === true &&
cfu.option[cid].enableScroll === false &&
this.onmovetip === true
) {
if (this.ontap === true && cfu.option[cid].enableScroll === false && this.onmovetip === true) {
this._tap(e, true);
}
if (
this.ontouch === true &&
cfu.option[cid].enableScroll === true &&
this.onzoom === true &&
e.changedTouches.length == 2
) {
if (this.ontouch === true && cfu.option[cid].enableScroll === true && this.onzoom === true && e.changedTouches.length == 2) {
cfu.instance[cid].dobuleZoom(e);
}
this.emitMsg({
@@ -1263,11 +1163,7 @@ export default {
opts: cfu.instance[cid].opts,
},
});
if (
this.ontap === true &&
cfu.option[cid].enableScroll === false &&
this.onmovetip === true
) {
if (this.ontap === true && cfu.option[cid].enableScroll === false && this.onmovetip === true) {
this._tap(e, true);
}
},
@@ -19,33 +19,11 @@
// 通用配置项
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
const color = [
"#1890FF",
"#91CB74",
"#FAC858",
"#EE6666",
"#73C0DE",
"#3CA272",
"#FC8452",
"#9A60B4",
"#ea7ccc",
];
const color = ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"];
const cfe = {
//demotype为自定义图表类型
type: [
"pie",
"ring",
"rose",
"funnel",
"line",
"column",
"area",
"radar",
"gauge",
"candle",
"demotype",
],
type: ["pie", "ring", "rose", "funnel", "line", "column", "area", "radar", "gauge", "candle", "demotype"],
//增加自定义图表类型,如果需要categories,请在这里加入您的图表类型例如最后的"demotype"
categories: ["line", "column", "area", "radar", "gauge", "candle", "demotype"],
//instance为实例变量承载属性,option为eopts承载属性,不要删除
@@ -17,17 +17,7 @@
*/
// 主题颜色配置:如每个图表类型需要不同主题,请在对应图表类型上更改color属性
const color = [
"#1890FF",
"#91CB74",
"#FAC858",
"#EE6666",
"#73C0DE",
"#3CA272",
"#FC8452",
"#9A60B4",
"#ea7ccc",
];
const color = ["#1890FF", "#91CB74", "#FAC858", "#EE6666", "#73C0DE", "#3CA272", "#FC8452", "#9A60B4", "#ea7ccc"];
//事件转换函数,主要用作格式化x轴为时间轴,根据需求自行修改
const formatDateTime = (timeStamp, returnType) => {
@@ -61,66 +51,11 @@ const formatDateTime = (timeStamp, returnType) => {
const cfu = {
//demotype为自定义图表类型,一般不需要自定义图表类型,只需要改根节点上对应的类型即可
type: [
"pie",
"ring",
"rose",
"word",
"funnel",
"map",
"arcbar",
"line",
"column",
"mount",
"bar",
"area",
"radar",
"gauge",
"candle",
"mix",
"tline",
"tarea",
"scatter",
"bubble",
"demotype",
],
range: [
"饼状图",
"圆环图",
"玫瑰图",
"词云图",
"漏斗图",
"地图",
"圆弧进度条",
"折线图",
"柱状图",
"山峰图",
"条状图",
"区域图",
"雷达图",
"仪表盘",
"K线图",
"混合图",
"时间轴折线",
"时间轴区域",
"散点图",
"气泡图",
"自定义类型",
],
type: ["pie", "ring", "rose", "word", "funnel", "map", "arcbar", "line", "column", "mount", "bar", "area", "radar", "gauge", "candle", "mix", "tline", "tarea", "scatter", "bubble", "demotype"],
range: ["饼状图", "圆环图", "玫瑰图", "词云图", "漏斗图", "地图", "圆弧进度条", "折线图", "柱状图", "山峰图", "条状图", "区域图", "雷达图", "仪表盘", "K线图", "混合图", "时间轴折线", "时间轴区域", "散点图", "气泡图", "自定义类型"],
//增加自定义图表类型,如果需要categories,请在这里加入您的图表类型,例如最后的"demotype"
//自定义类型时需要注意"tline","tarea","scatter","bubble"等时间轴(矢量x轴)类图表,没有categories,不需要加入categories
categories: [
"line",
"column",
"mount",
"bar",
"area",
"radar",
"gauge",
"candle",
"mix",
"demotype",
],
categories: ["line", "column", "mount", "bar", "area", "radar", "gauge", "candle", "mix", "demotype"],
//instance为实例变量承载属性,不要删除
instance: {},
//option为opts及eopts承载属性,不要删除
File diff suppressed because it is too large Load Diff