抽离cos服务

This commit is contained in:
zpc 2025-11-12 00:13:56 +08:00
parent 9183ee09c0
commit 215107a625
18 changed files with 955 additions and 331 deletions

View File

@ -7,7 +7,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>寰梦后台管理</title>
<script type="module" crossorigin src="/static/js/index-D6L9FI9G.js"></script>
<script type="module" crossorigin src="/static/js/index-B89JIRFb.js"></script>
<link rel="stylesheet" crossorigin href="/static/css/index-CzwB8sLn.css">
</head>

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
body[data-v-b95e5a42]{margin:0;padding:0;overflow:hidden}.login[data-v-b95e5a42]{display:flex;justify-content:center;align-items:center;height:100vh;background:url(/static/jpg/login-1-DvL7JDPn.jpg) no-repeat;background-size:cover}.login .login-card[data-v-b95e5a42]{height:600px;width:1000px;box-shadow:0 16px 48px 16px #000000b8,0 12px 32px #000,0 8px 16px -8px #000;display:flex;border-radius:5px}.login .login-card .flex-left[data-v-b95e5a42]{flex:1;width:450px}.login .login-card .flex-left img[data-v-b95e5a42]{height:100%}.login .login-card .flex-right[data-v-b95e5a42]{flex:1;background-color:#fff;display:flex;justify-content:center;flex-direction:column;border-top-right-radius:5px;border-bottom-right-radius:5px}.login .login-card .title[data-v-b95e5a42]{text-align:center;font-size:30px;padding:20px;font-weight:700}.login .login-card .el-input-group__append[data-v-b95e5a42]{padding:0!important}.login .login-card .el-input-group__append .login-code[data-v-b95e5a42]{height:38px}@media (max-width: 1024px){.flex-left[data-v-b95e5a42]{display:none;flex:0!important}.flex-left img[data-v-b95e5a42]{height:auto!important;width:80%!important}.flex-right[data-v-b95e5a42]{border-radius:5px}}@media (min-width: 600px) and (max-width: 1024px){.login-card[data-v-b95e5a42]{width:70%!important}}@media (max-width: 600px){.login-card[data-v-b95e5a42]{width:100%!important}}.lang-content[data-v-b95e5a42]{position:absolute;right:24px;top:20px}

View File

@ -40,7 +40,6 @@ declare module 'vue' {
AInputSearch: typeof import('ant-design-vue/es')['InputSearch']
ALayout: typeof import('ant-design-vue/es')['Layout']
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
ALayoutFooter: typeof import('ant-design-vue/es')['LayoutFooter']
ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader']
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
AMenu: typeof import('ant-design-vue/es')['Menu']
@ -82,7 +81,6 @@ declare module 'vue' {
AUploadDragger: typeof import('ant-design-vue/es')['UploadDragger']
BarChartTransverse: typeof import('./core/components/charts/BarChartTransverse.vue')['default']
ColumnSetting: typeof import('./core/components/curd/components/ColumnSetting.vue')['default']
copy: typeof import('./core/components/layouts/LayoutMode1 copy.vue')['default']
ExternalJump: typeof import('./core/components/ExternalJump.vue')['default']
FindBack: typeof import('./core/components/FindBack.vue')['default']
GenerateCron: typeof import('./core/components/GenerateCron.vue')['default']

View File

@ -8,6 +8,7 @@ import NProgress from "nprogress";
import Cookies from "universal-cookie";
import AppConsts from "../../utils/AppConsts";
import TImageConfigService from "@/services/apps/T_Image_Configs/TImageConfigService";
import CosService from "@/services/system/CosService";
import COS from "cos-js-sdk-v5";
import { log } from "@antv/g2plot/lib/utils";
/**
@ -390,7 +391,7 @@ class Tools {
* @returns
*/
static async cosUploadFile(file: File, modelName?: string) {
var request = await TImageConfigService.getGenerateTemporaryKey(
const request = await CosService.getGenerateTemporaryKey(
file.name,
modelName
);

View File

@ -47,34 +47,6 @@ export default class TImageConfigService {
static findForm(id?: string | undefined | number) {
return Http.get(`${this.urlPrefix}/findForm${id ? "/" + id : ""}`);
}
/**
* key
*
* @param
* @returns
*/
static getCosSign() {
return Http.get(`${this.urlPrefix}/GetCosSign`);
}
/**
* token
*
* @param
* @returns
*/
static getGenerateTemporaryKey(fileName?: string, modelName?: string) {
if (fileName != null) {
fileName = escape(fileName);
// query
}
return Http.get(
`${this.urlPrefix
}/GetGenerateTemporaryKey?fileName=${fileName}&modelName=${escape(
modelName || ""
)}`
);
}
/**
*
*

View File

@ -0,0 +1,39 @@
import Http from "@/core/utils/Http";
import type ApiResult from "@/core/typings/ApiResult";
import type { GenerateTemporaryModel } from "./types/cos";
/**
* COS
*/
export default class CosService {
static urlPrefix = "/api/v1/admin/Cos";
/**
* COS URL
*/
static getCosSign(): Promise<ApiResult<string>> {
return Http.get(`${this.urlPrefix}/GetCosSign`);
}
/**
*
* @param fileName
* @param modelName
*/
static getGenerateTemporaryKey(
fileName?: string,
modelName?: string
): Promise<ApiResult<GenerateTemporaryModel>> {
const params: Record<string, string> = {};
if (fileName != null) {
params.fileName = escape(fileName);
}
if (modelName != null) {
params.modelName = escape(modelName);
}
return Http.get(`${this.urlPrefix}/GetGenerateTemporaryKey`, params) as Promise<
ApiResult<GenerateTemporaryModel>
>;
}
}

View File

@ -0,0 +1,17 @@
export interface GenerateTemporaryModel {
credentials: {
token: string;
tmpSecretId: string;
tmpSecretKey: string;
};
expiration: string;
expiredTime: number;
startTime: number;
requestId: string;
bucket: string;
region: string;
prefixes: string;
filePath: string;
domainName: string;
}

View File

@ -1,137 +1,149 @@
<script lang="ts" setup>
import { reactive, ref } from "vue";
import { FormInstance } from "ant-design-vue";
import Tools from "@/core/utils/Tools";
import T_OrderService from "@/services/apps/T_Orders/T_OrderService";
import { reactive, ref } from "vue";
import { FormInstance } from "ant-design-vue";
import Tools from "@/core/utils/Tools";
import T_OrderService from "@/services/apps/T_Orders/T_OrderService";
//
const props = defineProps<{ onSuccess: () => void }>();
//
const props = defineProps<{ onSuccess: () => void }>();
const state = reactive({
vm: {
id: "",
form: {} as any,
},
visible: false,
loading: false,
});
const state = reactive({
vm: {
id: "",
form: {} as any,
},
visible: false,
loading: false,
});
//
const refForm = ref<FormInstance>();
//
defineExpose({
/**
* 打开表单初始化
* @param key
*/
open: (key: string = "") => {
state.visible = true;
if (state.visible) {
state.vm.id = key;
}
refForm.value?.resetFields();
//
state.loading = true;
T_OrderService.findForm(key).then((res) => {
state.loading = false;
if (res.code != 200) return;
state.vm = res.data;
});
},
});
//
const refForm = ref<FormInstance>();
//
defineExpose({
/**
*保存数据
* 打开表单初始化
* @param key
*/
function save() {
refForm.value?.validate().then(async () => {
try {
state.loading = true;
const result = await T_OrderService.saveForm(state.vm.id, state.vm.form);
state.loading = false;
if (result.code != 200) return;
Tools.message.success("操作成功!");
props.onSuccess();
state.visible = false;
} catch (error) {
state.loading = false;
}
open: (key: string = "") => {
state.visible = true;
if (state.visible) {
state.vm.id = key;
}
refForm.value?.resetFields();
//
state.loading = true;
T_OrderService.findForm(key).then((res) => {
state.loading = false;
if (res.code != 200) return;
state.vm = res.data;
});
}
},
});
/**
*保存数据
*/
function save() {
refForm.value?.validate().then(async () => {
try {
state.loading = true;
const result = await T_OrderService.saveForm(state.vm.id, state.vm.form);
state.loading = false;
if (result.code != 200) return;
Tools.message.success("操作成功!");
props.onSuccess();
state.visible = false;
} catch (error) {
state.loading = false;
}
});
}
</script>
<template>
<a-modal v-model:open="state.visible" :title="state.vm.id ? '编辑' : '新建'" centered @ok="state.visible = false" :width="800">
<template #footer>
<a-button type="primary" :loading="state.loading" @click="save()"> 提交</a-button>
<a-button @click="state.visible = false">关闭</a-button>
</template>
<a-spin :spinning="state.loading">
<a-form ref="refForm" layout="vertical" :model="state.vm.form">
<a-row :gutter="[16, 0]">
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="项目" name="tenantId">
<hm-tenant-select v-model:value="state.vm.form.tenantId" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="创建时间" name="createdAt" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.createdAt" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单创建时间" name="orderDate" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.orderDate" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单编号" name="orderId" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.orderId" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单支付时间" name="paymentDate" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.paymentDate" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单创建天" name="paymentDay" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.paymentDay" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单支付方式" name="paymentMethod" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.paymentMethod" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="购买的产品Id" name="productId" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.productId" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单状态" name="status" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.status" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="价格" name="totalPrice" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.totalPrice" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="修改时间" name="updatedAt" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.updatedAt" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="用户Id" name="userId" :rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.userId" placeholder="请输入" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-spin>
</a-modal>
<a-modal v-model:open="state.visible" :title="state.vm.id ? '编辑' : '新建'" centered @ok="state.visible = false"
:width="800">
<template #footer>
<a-button type="primary" :loading="state.loading" @click="save()"> 提交</a-button>
<a-button @click="state.visible = false">关闭</a-button>
</template>
<a-spin :spinning="state.loading">
<a-form ref="refForm" layout="vertical" :model="state.vm.form">
<a-row :gutter="[16, 0]">
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="项目" name="tenantId">
<hm-tenant-select v-model:value="state.vm.form.tenantId" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="创建时间" name="createdAt"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.createdAt" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单创建时间" name="orderDate"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.orderDate" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单编号" name="orderId"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.orderId" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单支付时间" name="paymentDate"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.paymentDate" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单创建天" name="paymentDay"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.paymentDay" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单支付方式" name="paymentMethod"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.paymentMethod" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="购买的产品Id" name="productId"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.productId" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="订单状态" name="status"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.status" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="价格" name="totalPrice"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.totalPrice" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="修改时间" name="updatedAt"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.updatedAt" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24">
<a-form-item label="用户Id" name="userId"
:rules="[{ required: true, message: '请输入', trigger: 'blur' }]">
<a-input v-model:value="state.vm.form.userId" placeholder="请输入" />
</a-form-item>
</a-col>
</a-row>
</a-form>
</a-spin>
</a-modal>
</template>

View File

@ -0,0 +1,264 @@
# 腾讯云COS服务接口迁移文档
## 📋 更新概述
本次更新将腾讯云COS相关接口从 `TImageConfigController` 迁移到独立的 `CosController`实现服务分离COS服务不再与业务数据库关联。
---
## 🆕 一、新增接口
### 1.1 获取COS签名
**接口信息:**
- **请求路径**`GET /api/v1/admin/Cos/GetCosSign`
- **请求方法**`GET`
- **是否需要认证**:是
- **描述**获取腾讯云COS预签名URL
**请求参数:**
**响应示例:**
```json
"https://cos.ap-shanghai.myqcloud.com/miaoyu-1308826010/exampleobject?sign=..."
```
**响应字段说明:**
- 返回类型:`string`
- 说明预签名的COS上传URL
---
### 1.2 获取临时密钥
**接口信息:**
- **请求路径**`GET /api/v1/admin/Cos/GetGenerateTemporaryKey`
- **请求方法**`GET`
- **是否需要认证**:是
- **描述**获取腾讯云COS临时访问密钥STS
**请求参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| fileName | string | 否 | 文件名,不传则生成时间戳文件名 |
| modelName | string | 否 | 模型名称,默认值为 "images" |
**请求示例:**
```http
GET /api/v1/admin/Cos/GetGenerateTemporaryKey?fileName=test.jpg&modelName=images
```
**响应示例:**
```json
{
"credentials": {
"token": "xxx",
"tmpSecretId": "xxx",
"tmpSecretKey": "xxx"
},
"expiration": "2024-12-07T10:00:00Z",
"expiredTime": 1701936000,
"startTime": 1701935400,
"requestId": "xxx",
"bucket": "miaoyu-1308826010",
"region": "ap-shanghai",
"prefixes": "miaoyu",
"filePath": "miaoyu/images/20241207/1701936000.jpg",
"domainName": "https://cos.shhuanmeng.com/"
}
```
**响应字段说明:**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| credentials | object | 临时访问凭证 |
| credentials.token | string | 临时Token |
| credentials.tmpSecretId | string | 临时SecretId |
| credentials.tmpSecretKey | string | 临时SecretKey |
| expiration | string | 过期时间ISO8601格式 |
| expiredTime | number | 过期时间Unix时间戳 |
| startTime | number | 开始时间Unix时间戳 |
| requestId | string | 请求ID |
| bucket | string | 存储桶名称 |
| region | string | 地域 |
| prefixes | string | 路径前缀 |
| filePath | string | 文件路径 |
| domainName | string | 访问域名 |
---
## ⚠️ 二、废弃接口
以下接口已废弃,请使用新的接口替代:
### 2.1 废弃获取COS签名
- **旧接口路径**`GET /api/v1/admin/TImageConfig/GetCosSign`
- **新接口路径**`GET /api/v1/admin/Cos/GetCosSign`
- **状态**:❌ 已废弃,请使用新接口
### 2.2 废弃:获取临时密钥
- **旧接口路径**`GET /api/v1/admin/TImageConfig/GetGenerateTemporaryKey`
- **新接口路径**`GET /api/v1/admin/Cos/GetGenerateTemporaryKey`
- **状态**:❌ 已废弃,请使用新接口
---
## 🔄 三、接口迁移对照表
| 功能 | 旧接口 | 新接口 | 状态 |
|------|--------|--------|------|
| 获取COS签名 | `GET /api/v1/admin/TImageConfig/GetCosSign` | `GET /api/v1/admin/Cos/GetCosSign` | ✅ 已迁移 |
| 获取临时密钥 | `GET /api/v1/admin/TImageConfig/GetGenerateTemporaryKey` | `GET /api/v1/admin/Cos/GetGenerateTemporaryKey` | ✅ 已迁移 |
---
## 📝 四、前端修改说明
### 4.1 接口地址变更
**修改前:**
```typescript
// 获取COS签名
const getCosSign = async () => {
return axios.get('/api/v1/admin/TImageConfig/GetCosSign');
};
// 获取临时密钥
const getTemporaryKey = async (fileName?: string, modelName?: string) => {
return axios.get('/api/v1/admin/TImageConfig/GetGenerateTemporaryKey', {
params: { fileName, modelName }
});
};
```
**修改后:**
```typescript
// 获取COS签名
const getCosSign = async () => {
return axios.get('/api/v1/admin/Cos/GetCosSign');
};
// 获取临时密钥
const getTemporaryKey = async (fileName?: string, modelName?: string) => {
return axios.get('/api/v1/admin/Cos/GetGenerateTemporaryKey', {
params: { fileName, modelName }
});
};
```
### 4.2 API服务更新
**建议更新 API 服务文件:**
```typescript
// api/cos.ts
export const cosApi = {
// 获取COS签名
getCosSign: (): Promise<string> => {
return axios.get('/api/v1/admin/Cos/GetCosSign').then(res => res.data);
},
// 获取临时密钥
getTemporaryKey: (params?: {
fileName?: string;
modelName?: string;
}): Promise<GenerateTemporaryModel> => {
return axios.get('/api/v1/admin/Cos/GetGenerateTemporaryKey', {
params
}).then(res => res.data);
}
};
```
### 4.3 类型定义(如需要)
```typescript
// types/cos.ts
export interface GenerateTemporaryModel {
credentials: {
token: string;
tmpSecretId: string;
tmpSecretKey: string;
};
expiration: string;
expiredTime: number;
startTime: number;
requestId: string;
bucket: string;
region: string;
prefixes: string;
filePath: string;
domainName: string;
}
```
---
## ✨ 五、注意事项
### 5.1 接口兼容性
- **旧接口已完全废弃**,不再可用
- **新接口的参数和返回值与旧接口完全一致**,仅路径变更
- **无需修改业务逻辑**,只需更新接口地址
### 5.2 权限控制
- 新接口需要认证(`[Authorize]`
- 权限控制规则与旧接口一致
- 如果旧接口有特殊权限配置,新接口保持相同配置
### 5.3 错误处理
- 错误响应格式保持不变
- 错误码和错误信息保持一致
- 前端错误处理逻辑无需修改
---
## 🔍 六、验证清单
请确认以下内容已完成:
- [ ] 更新所有调用 `TImageConfig/GetCosSign` 的代码
- [ ] 更新所有调用 `TImageConfig/GetGenerateTemporaryKey` 的代码
- [ ] 更新 API 服务文件中的接口地址
- [ ] 测试新接口是否正常工作
- [ ] 移除对旧接口的引用
- [ ] 更新相关文档和注释
---
## 📞 七、技术支持
如有疑问,请联系后端开发团队。
**文档版本**v1.0
**更新日期**2024年12月
**后端版本**COS服务分离重构版本
---
## 📚 附录:完整接口列表
### 新接口列表
1. **获取COS签名**
- `GET /api/v1/admin/Cos/GetCosSign`
2. **获取临时密钥**
- `GET /api/v1/admin/Cos/GetGenerateTemporaryKey?fileName={fileName}&modelName={modelName}`
### 已废弃接口列表
1. ~~`GET /api/v1/admin/TImageConfig/GetCosSign`~~
2. ~~`GET /api/v1/admin/TImageConfig/GetGenerateTemporaryKey`~~
---
**文档结束**

View File

@ -2,9 +2,6 @@ using HZY.Framework.Repository.EntityFramework.Extensions;
using IdGen;
using MiaoYu.Api.Admin.Models.Dtos.Apps.Cos;
using MiaoYu.Core.Cos.Models;
using MiaoYu.Core.Cos.Services;
using MiaoYu.Repository.ChatAI.Admin.Entities;
using NPOI.SS.Formula.Functions;
@ -16,12 +13,9 @@ namespace MiaoYu.Api.Admin.ApplicationServices.Apps;
/// </summary>
public class TImageConfigService : ApplicationService<IRepository<T_Image_Config>>
{
private ICodeCosService codeCosService;
public TImageConfigService(IRepository<T_Image_Config> defaultRepository,
ICodeCosService codeCosService)
public TImageConfigService(IRepository<T_Image_Config> defaultRepository)
: base(defaultRepository)
{
this.codeCosService = codeCosService;
}
/// <summary>
@ -143,54 +137,6 @@ public class TImageConfigService : ApplicationService<IRepository<T_Image_Config
return ExcelUtil.ExportExcelByPagingView(tableViewModel, null, "Id");
}
/// <summary>
/// 获取签名
/// </summary>
/// <returns></returns>
public string GetCosSign()
{
var (sign, ex) = codeCosService.GenerateSignURL(new MiaoYu.Core.Cos.Models.CosGenerateSign());
return sign;
}
/// <summary>
/// 获取临时签名
/// </summary>
/// <returns></returns>
public GenerateTemporaryModel GetGenerateTemporaryKey(string fileName = "", string modelName = "")
{
var t = new CosGenerateSign()
{
Prefixes = "miaoyu"
};
if (string.IsNullOrEmpty(modelName))
{
modelName = $"images";
}
var tempFile = fileName;
if (!string.IsNullOrEmpty(tempFile))
{
var ext = Path.GetExtension(tempFile);
if (!string.IsNullOrEmpty(ext))
{
Random random = new Random();
tempFile = $"{DateTime.Now.ToUnixTimeSeconds()}{ext}";
}
}
var model = codeCosService.GenerateTemporaryKey(t);
//
GenerateTemporaryModel generateTemporaryModel = model.CopyObject<CodeCosGenerateTemporaryKeyEntity, GenerateTemporaryModel>() ?? new GenerateTemporaryModel();
generateTemporaryModel.Bucket = t.Bucket + "-" + t.AppId;
generateTemporaryModel.Region = t.Region;
generateTemporaryModel.Prefixes = t.Prefixes;
generateTemporaryModel.FilePath = $"{t.Prefixes}/{modelName}/{DateTime.Now.ToString("yyyMMdd")}/{tempFile}";
generateTemporaryModel.DomainName = "https://cos.shhuanmeng.com/";
return generateTemporaryModel;
}
/// <summary>
/// 获取图片url
/// </summary>

View File

@ -0,0 +1,83 @@
using MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos;
using MiaoYu.Core.ApplicationServices;
using MiaoYu.Core.Cos.Configs;
using MiaoYu.Core.Cos.Models;
using MiaoYu.Core.Cos.Services;
namespace MiaoYu.Api.Admin.ApplicationServices.Systems.Cos;
/// <summary>
/// 腾讯云COS服务
/// </summary>
public class CosService : ApplicationService
{
private readonly ICodeCosService _codeCosService;
private readonly TencentConfig _tencentConfig;
public CosService(ICodeCosService codeCosService, TencentConfig tencentConfig)
{
_codeCosService = codeCosService;
_tencentConfig = tencentConfig;
}
/// <summary>
/// 获取签名
/// </summary>
/// <returns></returns>
public string GetCosSign()
{
var (sign, ex) = _codeCosService.GenerateSignURL(new CosGenerateSign());
return sign;
}
/// <summary>
/// 获取临时签名
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="modelName">模型名称</param>
/// <returns></returns>
public GenerateTemporaryModel GetGenerateTemporaryKey(string fileName = "", string modelName = "")
{
var cosConfig = _tencentConfig.CosConfig;
var t = new CosGenerateSign()
{
Prefixes = "miaoyu",
Bucket = cosConfig?.Bucket,
Region = cosConfig?.Region,
AppId = cosConfig?.AppId,
SecretId = cosConfig?.SecretId,
SecretKey = cosConfig?.SecretKey,
DurationSecond = cosConfig?.DurationSecond ?? 300
};
if (string.IsNullOrEmpty(modelName))
{
modelName = "images";
}
var tempFile = fileName;
if (!string.IsNullOrEmpty(tempFile))
{
var ext = Path.GetExtension(tempFile);
if (!string.IsNullOrEmpty(ext))
{
// 使用 UTC 时间生成时间戳文件名
tempFile = $"{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}{ext}";
}
}
var model = _codeCosService.GenerateTemporaryKey(t);
// 复制对象属性
var generateTemporaryModel = model.CopyObject<CodeCosGenerateTemporaryKeyEntity, GenerateTemporaryModel>() ?? new GenerateTemporaryModel();
generateTemporaryModel.Bucket = t.Bucket + "-" + t.AppId;
generateTemporaryModel.Region = t.Region;
generateTemporaryModel.Prefixes = t.Prefixes;
// 修复日期格式yyyMMdd -> yyyyMMdd
generateTemporaryModel.FilePath = $"{t.Prefixes}/{modelName}/{DateTime.Now.ToString("yyyyMMdd")}/{tempFile}";
generateTemporaryModel.DomainName = "https://cos.shhuanmeng.com/";
return generateTemporaryModel;
}
}

View File

@ -0,0 +1,36 @@
using MiaoYu.Core.Cos.Models;
namespace MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos;
/// <summary>
/// cos临时密钥
/// </summary>
public class GenerateTemporaryModel : CodeCosGenerateTemporaryKeyEntity
{
/// <summary>
/// 存储桶名称
/// </summary>
public string Bucket { get; set; }
/// <summary>
/// 关于地域的详情见
/// </summary>
public string Region { get; set; }
/// <summary>
/// 前缀
/// </summary>
public string Prefixes { get; set; }
/// <summary>
/// 文件路径
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// 域名
/// </summary>
public string DomainName { get; set; }
}

View File

@ -1,6 +1,4 @@
using MiaoYu.Api.Admin.ApplicationServices.Apps;
using MiaoYu.Api.Admin.Models.Dtos.Apps.Cos;
using MiaoYu.Core.Cos.Models;
using MiaoYu.Repository.ChatAI.Admin.Entities;
using Microsoft.AspNetCore.Http.Extensions;
@ -109,31 +107,8 @@ public class TImageConfigController : AdminControllerBase<TImageConfigService>
base.HttpContext.DownLoadFile(data, Tools.GetFileContentType[".xls"].ToStr(), name);
}
/// <summary>
///
/// </summary>
/// <returns></returns>
[HttpGet]
[ActionDescriptor(DisplayName = "获取cos加密")]
public string GetCosSign()
{
return this._defaultService.GetCosSign();
}
/// <summary>
/// 获取临时密钥
/// </summary>
/// <returns></returns>
[HttpGet]
[ActionDescriptor(DisplayName = "获取cos临时密钥")]
public GenerateTemporaryModel GetGenerateTemporaryKey(string fileName = "", string modelName = "")
{
return this._defaultService.GetGenerateTemporaryKey(fileName, modelName);
}
/// <summary>
/// 获取临时密钥
/// 获取图片
/// </summary>
/// <returns></returns>
[ActionDescriptor(DisplayName = "获取图片")]

View File

@ -0,0 +1,41 @@
using MiaoYu.Api.Admin.ApplicationServices.Systems.Cos;
using MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos;
namespace MiaoYu.Api.Admin.Controllers.Systems;
/// <summary>
/// 腾讯云COS控制器
/// </summary>
[ControllerDescriptor(DisplayName = "腾讯云COS")]
public class CosController : AdminControllerBase<CosService>
{
public CosController(CosService defaultService)
: base(defaultService)
{
}
/// <summary>
/// 获取COS签名
/// </summary>
/// <returns></returns>
[HttpGet]
[ActionDescriptor(DisplayName = "获取COS签名")]
public string GetCosSign()
{
return this._defaultService.GetCosSign();
}
/// <summary>
/// 获取临时密钥
/// </summary>
/// <param name="fileName">文件名</param>
/// <param name="modelName">模型名称</param>
/// <returns></returns>
[HttpGet]
[ActionDescriptor(DisplayName = "获取COS临时密钥")]
public GenerateTemporaryModel GetGenerateTemporaryKey(string fileName = "", string modelName = "")
{
return this._defaultService.GetGenerateTemporaryKey(fileName, modelName);
}
}

View File

@ -289,18 +289,6 @@
<param name="pagingSearchInput"></param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.Apps.TImageConfigService.GetCosSign">
<summary>
获取签名
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.Apps.TImageConfigService.GetGenerateTemporaryKey(System.String,System.String)">
<summary>
获取临时签名
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.Apps.TImageConfigService.GetImageUrl(System.Int32)">
<summary>
获取图片url
@ -1634,6 +1622,55 @@
</summary>
<returns></returns>
</member>
<member name="T:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.CosService">
<summary>
腾讯云COS服务
</summary>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.CosService.GetCosSign">
<summary>
获取签名
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.CosService.GetGenerateTemporaryKey(System.String,System.String)">
<summary>
获取临时签名
</summary>
<param name="fileName">文件名</param>
<param name="modelName">模型名称</param>
<returns></returns>
</member>
<member name="T:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos.GenerateTemporaryModel">
<summary>
cos临时密钥
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos.GenerateTemporaryModel.Bucket">
<summary>
存储桶名称
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos.GenerateTemporaryModel.Region">
<summary>
关于地域的详情见
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos.GenerateTemporaryModel.Prefixes">
<summary>
前缀
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos.GenerateTemporaryModel.FilePath">
<summary>
文件路径
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.ApplicationServices.Systems.Cos.Dtos.GenerateTemporaryModel.DomainName">
<summary>
域名
</summary>
</member>
<member name="T:MiaoYu.Api.Admin.ApplicationServices.Systems.PermissionService">
<summary>
系统权限服务
@ -2338,21 +2375,9 @@
<param name="pagingSearchInput"></param>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.Apps.TImageConfigController.GetCosSign">
<summary>
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.Apps.TImageConfigController.GetGenerateTemporaryKey(System.String,System.String)">
<summary>
获取临时密钥
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.Apps.TImageConfigController.Image(System.Int32)">
<summary>
获取临时密钥
获取图片
</summary>
<returns></returns>
</member>
@ -3745,6 +3770,25 @@
路由前缀
</summary>
</member>
<member name="T:MiaoYu.Api.Admin.Controllers.Systems.CosController">
<summary>
腾讯云COS控制器
</summary>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.Systems.CosController.GetCosSign">
<summary>
获取COS签名
</summary>
<returns></returns>
</member>
<member name="M:MiaoYu.Api.Admin.Controllers.Systems.CosController.GetGenerateTemporaryKey(System.String,System.String)">
<summary>
获取临时密钥
</summary>
<param name="fileName">文件名</param>
<param name="modelName">模型名称</param>
<returns></returns>
</member>
<member name="T:MiaoYu.Api.Admin.Controllers.Systems.PersonalCenterController">
<summary>
个人中心
@ -4367,36 +4411,6 @@
</summary>
<returns></returns>
</member>
<member name="T:MiaoYu.Api.Admin.Models.Dtos.Apps.Cos.GenerateTemporaryModel">
<summary>
cos临时密钥
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.Apps.Cos.GenerateTemporaryModel.Bucket">
<summary>
存储桶名称
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.Apps.Cos.GenerateTemporaryModel.Region">
<summary>
关于地域的详情见
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.Apps.Cos.GenerateTemporaryModel.Prefixes">
<summary>
前缀
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.Apps.Cos.GenerateTemporaryModel.FilePath">
<summary>
文件路径
</summary>
</member>
<member name="P:MiaoYu.Api.Admin.Models.Dtos.Apps.Cos.GenerateTemporaryModel.DomainName">
<summary>
域名
</summary>
</member>
<member name="T:MiaoYu.Api.Admin.Models.Dtos.DevelopmentTool.DataSourceDto">
<summary>
数据源信息 DTO

View File

@ -1,36 +0,0 @@
using MiaoYu.Core.Cos.Models;
namespace MiaoYu.Api.Admin.Models.Dtos.Apps.Cos
{
/// <summary>
/// cos临时密钥
/// </summary>
public class GenerateTemporaryModel : CodeCosGenerateTemporaryKeyEntity
{
/// <summary>
/// 存储桶名称
/// </summary>
public string Bucket { get; set; }
/// <summary>
/// 关于地域的详情见
/// </summary>
public string Region { get; set; }
/// <summary>
/// 前缀
/// </summary>
public string Prefixes { get; set; }
/// <summary>
/// 文件路径
/// </summary>
public string FilePath { get; set; }
/// <summary>
/// 域名
/// </summary>
public string DomainName { get; set; }
}
}

View File

@ -0,0 +1,264 @@
# 腾讯云COS服务接口迁移文档
## 📋 更新概述
本次更新将腾讯云COS相关接口从 `TImageConfigController` 迁移到独立的 `CosController`实现服务分离COS服务不再与业务数据库关联。
---
## 🆕 一、新增接口
### 1.1 获取COS签名
**接口信息:**
- **请求路径**`GET /api/v1/admin/Cos/GetCosSign`
- **请求方法**`GET`
- **是否需要认证**:是
- **描述**获取腾讯云COS预签名URL
**请求参数:**
**响应示例:**
```json
"https://cos.ap-shanghai.myqcloud.com/miaoyu-1308826010/exampleobject?sign=..."
```
**响应字段说明:**
- 返回类型:`string`
- 说明预签名的COS上传URL
---
### 1.2 获取临时密钥
**接口信息:**
- **请求路径**`GET /api/v1/admin/Cos/GetGenerateTemporaryKey`
- **请求方法**`GET`
- **是否需要认证**:是
- **描述**获取腾讯云COS临时访问密钥STS
**请求参数:**
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| fileName | string | 否 | 文件名,不传则生成时间戳文件名 |
| modelName | string | 否 | 模型名称,默认值为 "images" |
**请求示例:**
```http
GET /api/v1/admin/Cos/GetGenerateTemporaryKey?fileName=test.jpg&modelName=images
```
**响应示例:**
```json
{
"credentials": {
"token": "xxx",
"tmpSecretId": "xxx",
"tmpSecretKey": "xxx"
},
"expiration": "2024-12-07T10:00:00Z",
"expiredTime": 1701936000,
"startTime": 1701935400,
"requestId": "xxx",
"bucket": "miaoyu-1308826010",
"region": "ap-shanghai",
"prefixes": "miaoyu",
"filePath": "miaoyu/images/20241207/1701936000.jpg",
"domainName": "https://cos.shhuanmeng.com/"
}
```
**响应字段说明:**
| 字段名 | 类型 | 说明 |
|--------|------|------|
| credentials | object | 临时访问凭证 |
| credentials.token | string | 临时Token |
| credentials.tmpSecretId | string | 临时SecretId |
| credentials.tmpSecretKey | string | 临时SecretKey |
| expiration | string | 过期时间ISO8601格式 |
| expiredTime | number | 过期时间Unix时间戳 |
| startTime | number | 开始时间Unix时间戳 |
| requestId | string | 请求ID |
| bucket | string | 存储桶名称 |
| region | string | 地域 |
| prefixes | string | 路径前缀 |
| filePath | string | 文件路径 |
| domainName | string | 访问域名 |
---
## ⚠️ 二、废弃接口
以下接口已废弃,请使用新的接口替代:
### 2.1 废弃获取COS签名
- **旧接口路径**`GET /api/v1/admin/TImageConfig/GetCosSign`
- **新接口路径**`GET /api/v1/admin/Cos/GetCosSign`
- **状态**:❌ 已废弃,请使用新接口
### 2.2 废弃:获取临时密钥
- **旧接口路径**`GET /api/v1/admin/TImageConfig/GetGenerateTemporaryKey`
- **新接口路径**`GET /api/v1/admin/Cos/GetGenerateTemporaryKey`
- **状态**:❌ 已废弃,请使用新接口
---
## 🔄 三、接口迁移对照表
| 功能 | 旧接口 | 新接口 | 状态 |
|------|--------|--------|------|
| 获取COS签名 | `GET /api/v1/admin/TImageConfig/GetCosSign` | `GET /api/v1/admin/Cos/GetCosSign` | ✅ 已迁移 |
| 获取临时密钥 | `GET /api/v1/admin/TImageConfig/GetGenerateTemporaryKey` | `GET /api/v1/admin/Cos/GetGenerateTemporaryKey` | ✅ 已迁移 |
---
## 📝 四、前端修改说明
### 4.1 接口地址变更
**修改前:**
```typescript
// 获取COS签名
const getCosSign = async () => {
return axios.get('/api/v1/admin/TImageConfig/GetCosSign');
};
// 获取临时密钥
const getTemporaryKey = async (fileName?: string, modelName?: string) => {
return axios.get('/api/v1/admin/TImageConfig/GetGenerateTemporaryKey', {
params: { fileName, modelName }
});
};
```
**修改后:**
```typescript
// 获取COS签名
const getCosSign = async () => {
return axios.get('/api/v1/admin/Cos/GetCosSign');
};
// 获取临时密钥
const getTemporaryKey = async (fileName?: string, modelName?: string) => {
return axios.get('/api/v1/admin/Cos/GetGenerateTemporaryKey', {
params: { fileName, modelName }
});
};
```
### 4.2 API服务更新
**建议更新 API 服务文件:**
```typescript
// api/cos.ts
export const cosApi = {
// 获取COS签名
getCosSign: (): Promise<string> => {
return axios.get('/api/v1/admin/Cos/GetCosSign').then(res => res.data);
},
// 获取临时密钥
getTemporaryKey: (params?: {
fileName?: string;
modelName?: string;
}): Promise<GenerateTemporaryModel> => {
return axios.get('/api/v1/admin/Cos/GetGenerateTemporaryKey', {
params
}).then(res => res.data);
}
};
```
### 4.3 类型定义(如需要)
```typescript
// types/cos.ts
export interface GenerateTemporaryModel {
credentials: {
token: string;
tmpSecretId: string;
tmpSecretKey: string;
};
expiration: string;
expiredTime: number;
startTime: number;
requestId: string;
bucket: string;
region: string;
prefixes: string;
filePath: string;
domainName: string;
}
```
---
## ✨ 五、注意事项
### 5.1 接口兼容性
- **旧接口已完全废弃**,不再可用
- **新接口的参数和返回值与旧接口完全一致**,仅路径变更
- **无需修改业务逻辑**,只需更新接口地址
### 5.2 权限控制
- 新接口需要认证(`[Authorize]`
- 权限控制规则与旧接口一致
- 如果旧接口有特殊权限配置,新接口保持相同配置
### 5.3 错误处理
- 错误响应格式保持不变
- 错误码和错误信息保持一致
- 前端错误处理逻辑无需修改
---
## 🔍 六、验证清单
请确认以下内容已完成:
- [ ] 更新所有调用 `TImageConfig/GetCosSign` 的代码
- [ ] 更新所有调用 `TImageConfig/GetGenerateTemporaryKey` 的代码
- [ ] 更新 API 服务文件中的接口地址
- [ ] 测试新接口是否正常工作
- [ ] 移除对旧接口的引用
- [ ] 更新相关文档和注释
---
## 📞 七、技术支持
如有疑问,请联系后端开发团队。
**文档版本**v1.0
**更新日期**2024年12月
**后端版本**COS服务分离重构版本
---
## 📚 附录:完整接口列表
### 新接口列表
1. **获取COS签名**
- `GET /api/v1/admin/Cos/GetCosSign`
2. **获取临时密钥**
- `GET /api/v1/admin/Cos/GetGenerateTemporaryKey?fileName={fileName}&modelName={modelName}`
### 已废弃接口列表
1. ~~`GET /api/v1/admin/TImageConfig/GetCosSign`~~
2. ~~`GET /api/v1/admin/TImageConfig/GetGenerateTemporaryKey`~~
---
**文档结束**