Compare commits
3 Commits
2498b4deed
...
e516105893
| Author | SHA1 | Date |
|---|---|---|
|
|
e516105893 | |
|
|
8d7cd70cfc | |
|
|
cb72d4edae |
|
|
@ -25,6 +25,23 @@ export function getDept(deptId) {
|
|||
})
|
||||
}
|
||||
|
||||
// 部门火山引擎配置(解密后明文)
|
||||
export function getDeptArk(deptId) {
|
||||
return request({
|
||||
url: '/system/dept/ark/' + deptId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 保存部门火山引擎配置
|
||||
export function updateDeptArk(data) {
|
||||
return request({
|
||||
url: '/system/dept/ark',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 新增部门
|
||||
export function addDept(data) {
|
||||
return request({
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@
|
|||
-->
|
||||
<el-table-column label="用户昵称" align="center" prop="nickname" />
|
||||
<el-table-column label="归属部门" align="center" prop="deptName" width="120" show-overflow-tooltip />
|
||||
<!--
|
||||
<el-table-column label="邮箱" align="center" prop="email" />
|
||||
<el-table-column label="性别" align="center" prop="gender">
|
||||
<template slot-scope="scope">
|
||||
|
|
@ -119,6 +120,7 @@
|
|||
<el-tag v-else type="danger">女</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
-->
|
||||
<el-table-column label="状态" align="center" key="status">
|
||||
<template slot-scope="scope">
|
||||
<el-switch
|
||||
|
|
@ -129,7 +131,9 @@
|
|||
></el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!--
|
||||
<el-table-column label="邀请码" align="center" prop="invitationCode" />
|
||||
-->
|
||||
<el-table-column label="登录时间" align="center" prop="loginTime" width="150">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.loginTime, '{y}-{m}-{d}') }}</span>
|
||||
|
|
|
|||
|
|
@ -65,6 +65,14 @@
|
|||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
v-if="isFirstLevelRow(scope.row) || isSecondLevelRow(scope.row)"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-setting"
|
||||
@click="handleArkConfig(scope.row)"
|
||||
v-hasPermi="['system:dept:query', 'system:dept:edit']"
|
||||
>火山配置</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
|
|
@ -93,7 +101,7 @@
|
|||
</el-table>
|
||||
|
||||
<!-- 添加或修改部门对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="760px" append-to-body>
|
||||
<el-dialog :title="title" :visible.sync="open" width="800px" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="24" v-if="form.parentId !== 0">
|
||||
|
|
@ -160,14 +168,24 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<p class="model-parm-hint" style="margin: 0; padding-top: 8px">限制本部门下「启用」状态账号数量;0 或不填表示不限制。</p>
|
||||
<p class="model-parm-hint" style="margin: 0; padding-top: 8px"> 限制本部门下「启用」状态账号数量;0 或不填表示不限制。</p>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="isSecondLevelCompanyForm">
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<span v-if="isFirstLevelEditForm" class="form-tip">一级部门仅允许修改名称</span>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog title="火山配置" :visible.sync="arkOpen" width="800px" append-to-body @close="resetArkForm">
|
||||
<el-form ref="arkFormRef" :model="arkForm" label-width="120px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="Byte API Key">
|
||||
<el-input
|
||||
v-model="form.byteApiKey"
|
||||
v-model="arkForm.byteApiKey"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="选填"
|
||||
|
|
@ -176,11 +194,11 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="isSecondLevelCompanyForm">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="火山配置项目">
|
||||
<el-input
|
||||
v-model="form.project"
|
||||
v-model="arkForm.project"
|
||||
type="password"
|
||||
show-password
|
||||
placeholder="选填"
|
||||
|
|
@ -189,13 +207,13 @@
|
|||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row v-if="isSecondLevelCompanyForm">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="视频模型">
|
||||
<div class="model-parm-block">
|
||||
<div
|
||||
v-for="(row, idx) in modelParamRows"
|
||||
:key="'mp-' + idx"
|
||||
v-for="(row, idx) in arkModelParamRows"
|
||||
:key="'ark-mp-' + idx"
|
||||
class="model-parm-row"
|
||||
>
|
||||
<el-input
|
||||
|
|
@ -211,13 +229,13 @@
|
|||
<el-button
|
||||
type="text"
|
||||
icon="el-icon-delete"
|
||||
:disabled="modelParamRows.length <= 1"
|
||||
@click="removeModelParamRow(idx)"
|
||||
:disabled="arkModelParamRows.length <= 1"
|
||||
@click="removeArkModelParamRow(idx)"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="text" icon="el-icon-plus" @click="addModelParamRow">添加模型</el-button>
|
||||
<el-button type="text" icon="el-icon-plus" @click="addArkModelParamRow">添加模型</el-button>
|
||||
<p class="model-parm-hint">
|
||||
保存为 JSON 写入 model_parm;门户「视频生成」按用户所属二级部门读取。
|
||||
保存为 JSON 写入 ai_dept_ark_config.model_parm;门户「视频生成」按 ai_user.dept_id 与本部门配置读取。
|
||||
留空则使用 portal.video.models。
|
||||
</p>
|
||||
</div>
|
||||
|
|
@ -226,9 +244,8 @@
|
|||
</el-row>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<span v-if="isFirstLevelEditForm" class="form-tip">一级部门仅允许修改名称</span>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
<el-button type="primary" @click="submitArkForm" v-hasPermi="['system:dept:edit']">确 定</el-button>
|
||||
<el-button @click="arkOpen = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
|
@ -265,7 +282,7 @@
|
|||
</style>
|
||||
|
||||
<script>
|
||||
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild } from "@/api/system/dept"
|
||||
import { listDept, getDept, delDept, addDept, updateDept, listDeptExcludeChild, getDeptArk, updateDeptArk } from "@/api/system/dept"
|
||||
import Treeselect from "@riophae/vue-treeselect"
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||
|
||||
|
|
@ -287,6 +304,14 @@ export default {
|
|||
title: "",
|
||||
// 是否显示弹出层
|
||||
open: false,
|
||||
arkOpen: false,
|
||||
arkDeptId: null,
|
||||
arkForm: {
|
||||
byteApiKey: undefined,
|
||||
project: undefined,
|
||||
modelParm: undefined
|
||||
},
|
||||
arkModelParamRows: [{ label: '', value: '' }],
|
||||
// 是否展开,默认全部展开
|
||||
isExpandAll: true,
|
||||
// 重新渲染表格状态
|
||||
|
|
@ -299,7 +324,6 @@ export default {
|
|||
// 表单参数
|
||||
form: {},
|
||||
originalForm: {},
|
||||
modelParamRows: [{ label: '', value: '' }],
|
||||
// 表单校验
|
||||
rules: {
|
||||
parentId: [
|
||||
|
|
@ -332,17 +356,6 @@ export default {
|
|||
this.getList()
|
||||
},
|
||||
computed: {
|
||||
/** 二级公司:ancestors 为 0,100(即上级为根公司 dept_id=100) */
|
||||
isSecondLevelCompanyForm() {
|
||||
if (this.form.ancestors === "0,100") {
|
||||
return true
|
||||
}
|
||||
const pid = this.form.parentId
|
||||
if (pid !== undefined && pid !== null && Number(pid) === 100) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
isFirstLevelEditForm() {
|
||||
return this.form.deptId !== undefined && this.isFirstLevelRow(this.form)
|
||||
}
|
||||
|
|
@ -371,39 +384,78 @@ export default {
|
|||
return current
|
||||
})
|
||||
},
|
||||
syncModelRowsFromForm() {
|
||||
this.modelParamRows = [{ label: '', value: '' }]
|
||||
const raw = this.form.modelParm
|
||||
syncArkModelRowsFromForm() {
|
||||
this.arkModelParamRows = [{ label: '', value: '' }]
|
||||
const raw = this.arkForm.modelParm
|
||||
if (!raw || String(raw).trim() === '') {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const arr = JSON.parse(raw)
|
||||
if (Array.isArray(arr) && arr.length) {
|
||||
this.modelParamRows = arr.map(x => ({
|
||||
this.arkModelParamRows = arr.map(x => ({
|
||||
label: (x && x.label) ? String(x.label) : '',
|
||||
value: (x && x.value) ? String(x.value) : ''
|
||||
}))
|
||||
}
|
||||
} catch (e) {
|
||||
this.modelParamRows = [{ label: '', value: '' }]
|
||||
this.arkModelParamRows = [{ label: '', value: '' }]
|
||||
}
|
||||
},
|
||||
buildModelParmPayload() {
|
||||
if (!this.isSecondLevelCompanyForm) {
|
||||
buildArkModelParmPayload() {
|
||||
const rows = (this.arkModelParamRows || []).filter(r => r.label && r.value)
|
||||
this.arkForm.modelParm = rows.length ? JSON.stringify(rows) : ''
|
||||
},
|
||||
addArkModelParamRow() {
|
||||
this.arkModelParamRows.push({ label: '', value: '' })
|
||||
},
|
||||
removeArkModelParamRow(idx) {
|
||||
if (this.arkModelParamRows.length <= 1) {
|
||||
return
|
||||
}
|
||||
const rows = (this.modelParamRows || []).filter(r => r.label && r.value)
|
||||
this.form.modelParm = rows.length ? JSON.stringify(rows) : ''
|
||||
this.arkModelParamRows.splice(idx, 1)
|
||||
},
|
||||
addModelParamRow() {
|
||||
this.modelParamRows.push({ label: '', value: '' })
|
||||
resetArkForm() {
|
||||
this.arkDeptId = null
|
||||
this.arkForm = {
|
||||
byteApiKey: undefined,
|
||||
project: undefined,
|
||||
modelParm: undefined
|
||||
}
|
||||
this.arkModelParamRows = [{ label: '', value: '' }]
|
||||
if (this.$refs.arkFormRef) {
|
||||
this.$refs.arkFormRef.resetFields()
|
||||
}
|
||||
},
|
||||
removeModelParamRow(idx) {
|
||||
if (this.modelParamRows.length <= 1) {
|
||||
handleArkConfig(row) {
|
||||
if (!this.isFirstLevelRow(row) && !this.isSecondLevelRow(row)) {
|
||||
return
|
||||
}
|
||||
this.modelParamRows.splice(idx, 1)
|
||||
this.arkDeptId = row.deptId
|
||||
getDeptArk(row.deptId).then(response => {
|
||||
const d = response.data || {}
|
||||
this.arkForm = {
|
||||
byteApiKey: d.byteApiKey,
|
||||
project: d.project,
|
||||
modelParm: d.modelParm
|
||||
}
|
||||
this.syncArkModelRowsFromForm()
|
||||
this.arkOpen = true
|
||||
})
|
||||
},
|
||||
submitArkForm() {
|
||||
this.buildArkModelParmPayload()
|
||||
const payload = {
|
||||
deptId: this.arkDeptId,
|
||||
byteApiKey: this.arkForm.byteApiKey !== undefined && this.arkForm.byteApiKey !== null ? this.arkForm.byteApiKey : '',
|
||||
project: this.arkForm.project !== undefined && this.arkForm.project !== null ? this.arkForm.project : '',
|
||||
modelParm: this.arkForm.modelParm !== undefined && this.arkForm.modelParm !== null ? this.arkForm.modelParm : ''
|
||||
}
|
||||
updateDeptArk(payload).then(() => {
|
||||
this.$modal.msgSuccess('保存成功')
|
||||
this.arkOpen = false
|
||||
this.resetArkForm()
|
||||
})
|
||||
},
|
||||
/** 查询部门列表 */
|
||||
getList() {
|
||||
|
|
@ -441,13 +493,9 @@ export default {
|
|||
phone: undefined,
|
||||
email: undefined,
|
||||
maxUserCount: undefined,
|
||||
byteApiKey: undefined,
|
||||
project: undefined,
|
||||
modelParm: undefined,
|
||||
status: "0"
|
||||
}
|
||||
this.originalForm = {}
|
||||
this.modelParamRows = [{ label: '', value: '' }]
|
||||
this.resetForm("form")
|
||||
},
|
||||
/** 搜索按钮操作 */
|
||||
|
|
@ -489,7 +537,6 @@ export default {
|
|||
getDept(row.deptId).then(response => {
|
||||
this.form = response.data
|
||||
this.originalForm = { ...response.data }
|
||||
this.syncModelRowsFromForm()
|
||||
this.open = true
|
||||
this.title = "修改部门"
|
||||
listDeptExcludeChild(row.deptId).then(response => {
|
||||
|
|
@ -506,7 +553,6 @@ export default {
|
|||
submitForm: function() {
|
||||
this.$refs["form"].validate(valid => {
|
||||
if (valid) {
|
||||
this.buildModelParmPayload()
|
||||
if (this.form.deptId == undefined && !this.deptOptions.some(item => Number(item.deptId) === Number(this.form.parentId))) {
|
||||
this.$modal.msgError("仅允许在一级部门下创建二级部门")
|
||||
return
|
||||
|
|
@ -521,9 +567,6 @@ export default {
|
|||
this.form.email = oldDept.email
|
||||
this.form.status = oldDept.status
|
||||
this.form.maxUserCount = oldDept.maxUserCount
|
||||
this.form.byteApiKey = oldDept.byteApiKey
|
||||
this.form.project = oldDept.project
|
||||
this.form.modelParm = oldDept.modelParm
|
||||
}
|
||||
updateDept(this.form).then(response => {
|
||||
this.$modal.msgSuccess("修改成功")
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ import java.util.Set;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 门户视频生成:按用户二级部门 byte_api_key 调用火山;任务列表含库表与火山过滤列表。
|
||||
* 门户视频生成:按 {@code ai_user.dept_id} 对应 {@code ai_dept_ark_config} 的 API Key 调用火山;任务列表含库表与火山过滤列表。
|
||||
*/
|
||||
@Api(tags = "门户-视频生成")
|
||||
@RestController
|
||||
|
|
@ -104,7 +104,7 @@ public class PortalVideoController extends BaseController {
|
|||
}
|
||||
return out.isEmpty() ? null : out;
|
||||
} catch (Exception e) {
|
||||
logger.warn("解析二级部门 model_parm 失败: {}", e.getMessage());
|
||||
logger.warn("解析部门火山配置 model_parm 失败: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,12 +13,15 @@ import org.springframework.web.bind.annotation.PutMapping;
|
|||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.ai.domain.AiDeptArkConfig;
|
||||
import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
||||
import com.ruoyi.ai.service.IDeptChargeRefundService;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.constant.UserConstants;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.common.core.request.system.DeptArkConfigRequest;
|
||||
import com.ruoyi.common.core.request.system.DeptChargeRefundRequest;
|
||||
import com.ruoyi.common.core.request.system.DeptPointsCorrectionRequest;
|
||||
import com.ruoyi.common.enums.BusinessType;
|
||||
|
|
@ -40,6 +43,9 @@ public class SysDeptController extends BaseController
|
|||
@Autowired
|
||||
private IDeptChargeRefundService deptChargeRefundService;
|
||||
|
||||
@Autowired
|
||||
private IAiDeptArkConfigService aiDeptArkConfigService;
|
||||
|
||||
/**
|
||||
* 获取部门列表
|
||||
*/
|
||||
|
|
@ -74,6 +80,46 @@ public class SysDeptController extends BaseController
|
|||
return success(deptService.selectDeptById(deptId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门火山引擎配置(解密后明文,无配置时返回仅含 deptId 的空对象)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:dept:query')")
|
||||
@GetMapping("/ark/{deptId}")
|
||||
public AjaxResult getArk(@PathVariable Long deptId)
|
||||
{
|
||||
deptService.checkDeptDataScope(deptId);
|
||||
if (!deptService.isArkConfigurableDept(deptId))
|
||||
{
|
||||
return error("仅一级或二级部门可配置火山引擎");
|
||||
}
|
||||
AiDeptArkConfig ark = aiDeptArkConfigService.selectDecryptedByDeptId(deptId);
|
||||
if (ark == null)
|
||||
{
|
||||
AiDeptArkConfig empty = new AiDeptArkConfig();
|
||||
empty.setDeptId(deptId);
|
||||
return success(empty);
|
||||
}
|
||||
return success(ark);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存部门火山引擎配置(写入 ai_dept_ark_config)
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('system:dept:edit')")
|
||||
@Log(title = "部门火山配置", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/ark")
|
||||
public AjaxResult saveArk(@Validated @RequestBody DeptArkConfigRequest request)
|
||||
{
|
||||
Long deptId = request.getDeptId();
|
||||
deptService.checkDeptDataScope(deptId);
|
||||
if (!deptService.isArkConfigurableDept(deptId))
|
||||
{
|
||||
return error("仅一级或二级部门可配置火山引擎");
|
||||
}
|
||||
aiDeptArkConfigService.saveOrUpdateForDept(deptId, request.getByteApiKey(), request.getProject(), request.getModelParm());
|
||||
return success();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增部门
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -53,15 +53,6 @@ public class SysDept extends BaseEntity
|
|||
/** 父部门名称 */
|
||||
private String parentName;
|
||||
|
||||
/** Byte API Key */
|
||||
private String byteApiKey;
|
||||
|
||||
/** 门户视频模型列表 JSON:[{"label":"…","value":"ep-…"},…] */
|
||||
private String modelParm;
|
||||
|
||||
/** Byte project */
|
||||
private String project;
|
||||
|
||||
/** 部门余额 */
|
||||
private BigDecimal balance;
|
||||
|
||||
|
|
@ -187,36 +178,6 @@ public class SysDept extends BaseEntity
|
|||
this.parentName = parentName;
|
||||
}
|
||||
|
||||
public String getByteApiKey()
|
||||
{
|
||||
return byteApiKey;
|
||||
}
|
||||
|
||||
public void setByteApiKey(String byteApiKey)
|
||||
{
|
||||
this.byteApiKey = byteApiKey;
|
||||
}
|
||||
|
||||
public String getModelParm()
|
||||
{
|
||||
return modelParm;
|
||||
}
|
||||
|
||||
public void setModelParm(String modelParm)
|
||||
{
|
||||
this.modelParm = modelParm;
|
||||
}
|
||||
|
||||
public String getProject()
|
||||
{
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setProject(String project)
|
||||
{
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public BigDecimal getBalance()
|
||||
{
|
||||
return balance;
|
||||
|
|
@ -258,9 +219,6 @@ public class SysDept extends BaseEntity
|
|||
.append("leader", getLeader())
|
||||
.append("phone", getPhone())
|
||||
.append("email", getEmail())
|
||||
.append("byteApiKey", getByteApiKey())
|
||||
.append("modelParm", getModelParm())
|
||||
.append("project", getProject())
|
||||
.append("balance", getBalance())
|
||||
.append("maxUserCount", getMaxUserCount())
|
||||
.append("status", getStatus())
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package com.ruoyi.common.core.request.system;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
/**
|
||||
* 部门火山引擎配置(保存至 ai_dept_ark_config)
|
||||
*/
|
||||
public class DeptArkConfigRequest {
|
||||
|
||||
@NotNull(message = "部门ID不能为空")
|
||||
private Long deptId;
|
||||
|
||||
/** 明文;空字符串表示清空 */
|
||||
private String byteApiKey;
|
||||
|
||||
/** 明文;空字符串表示清空 */
|
||||
private String project;
|
||||
|
||||
/** 明文 JSON;空字符串表示清空 */
|
||||
private String modelParm;
|
||||
|
||||
public Long getDeptId() {
|
||||
return deptId;
|
||||
}
|
||||
|
||||
public void setDeptId(Long deptId) {
|
||||
this.deptId = deptId;
|
||||
}
|
||||
|
||||
public String getByteApiKey() {
|
||||
return byteApiKey;
|
||||
}
|
||||
|
||||
public void setByteApiKey(String byteApiKey) {
|
||||
this.byteApiKey = byteApiKey;
|
||||
}
|
||||
|
||||
public String getProject() {
|
||||
return project;
|
||||
}
|
||||
|
||||
public void setProject(String project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
public String getModelParm() {
|
||||
return modelParm;
|
||||
}
|
||||
|
||||
public void setModelParm(String modelParm) {
|
||||
this.modelParm = modelParm;
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ public class AiDeptArkConfig extends BaseEntity {
|
|||
|
||||
/** $column.columnComment */
|
||||
@TableId(type = IdType.AUTO)
|
||||
private String id;
|
||||
private Long id;
|
||||
|
||||
/** 部门ID */
|
||||
@Excel(name = "部门ID")
|
||||
|
|
|
|||
|
|
@ -26,6 +26,9 @@ public interface AiUserMapper extends BaseMapper<AiUser> {
|
|||
|
||||
int countAiUserByDeptId(@Param("deptId") Long deptId);
|
||||
|
||||
/** 归属指定部门的门户用户主键(用于火山 API Key 缓存失效) */
|
||||
List<Long> selectAiUserIdsByDeptId(@Param("deptId") Long deptId);
|
||||
|
||||
/**
|
||||
* 部门下启用中的 AI 用户(未删除且 status=0)
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -21,6 +21,21 @@ public interface IAiDeptArkConfigService {
|
|||
*/
|
||||
AiDeptArkConfig selectAiDeptArkConfigById(String id);
|
||||
|
||||
/**
|
||||
* 按部门查询火山配置;byte_api_key、project 为解密后的明文(无行返回 null)。
|
||||
*/
|
||||
AiDeptArkConfig selectDecryptedByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 按部门物理删除配置行(无行时返回 0)。
|
||||
*/
|
||||
int deleteByDeptId(Long deptId);
|
||||
|
||||
/**
|
||||
* 保存或更新指定部门的火山配置;密文字段按与历史 sys_dept 相同规则加密;落库后使该部门下门户用户的 API Key 缓存失效。
|
||||
*/
|
||||
void saveOrUpdateForDept(Long deptId, String byteApiKey, String project, String modelParm);
|
||||
|
||||
/**
|
||||
* 查询团队(部门)对应火山引擎配置列表
|
||||
*
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package com.ruoyi.ai.service;
|
||||
|
||||
/**
|
||||
* 门户用户火山方舟 API Key:取自所属二级部门 {@code sys_dept.byte_api_key},带 Redis 缓存。
|
||||
* 门户用户火山方舟 API Key:取自 {@code ai_dept_ark_config}(与 {@code ai_user.dept_id} 对应),带 Redis 缓存。
|
||||
*/
|
||||
public interface IByteDeptApiKeyService {
|
||||
|
||||
|
|
@ -11,13 +11,18 @@ public interface IByteDeptApiKeyService {
|
|||
String resolveVolcApiKey(Long aiUserId);
|
||||
|
||||
/**
|
||||
* 当前 AI 用户所属二级部门 {@code sys_dept.dept_id}(与 {@link #resolveVolcApiKey(Long)} 中使用的二级节点一致)。
|
||||
* 当前 AI 用户归属部门 {@code ai_user.dept_id}(在仅一级、二级部门树前提下与历史「二级部门」语义对齐;对外方法名保留)。
|
||||
* 用户未分配部门或部门数据缺失时返回 null。
|
||||
*/
|
||||
Long resolveSecondLevelDeptId(Long aiUserId);
|
||||
|
||||
/**
|
||||
* 当前用户所属二级部门 {@code sys_dept.model_parm}(JSON 模型列表);未配置或为空时返回 null。
|
||||
* 当前用户归属部门在 {@code ai_dept_ark_config.model_parm} 的 JSON 模型列表;未配置或为空时返回 null。
|
||||
*/
|
||||
String resolveSecondLevelModelParm(Long aiUserId);
|
||||
|
||||
/**
|
||||
* 使 {@code ai_user.dept_id = deptId} 的门户用户的火山 API Key 缓存失效(配置变更或部门删除后调用)。
|
||||
*/
|
||||
void evictVolcApiKeyCacheForDept(Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,24 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.ai.domain.AiDeptArkConfig;
|
||||
import com.ruoyi.ai.mapper.AiDeptArkConfigMapper;
|
||||
import com.ruoyi.ai.mapper.AiUserMapper;
|
||||
import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
||||
import com.ruoyi.common.EncryptionService;
|
||||
import com.ruoyi.common.core.redis.RedisCache;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import com.ruoyi.ai.mapper.AiDeptArkConfigMapper;
|
||||
import com.ruoyi.ai.domain.AiDeptArkConfig;
|
||||
import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
||||
|
||||
/**
|
||||
* 团队(部门)对应火山引擎配置Service业务层处理
|
||||
|
|
@ -22,92 +29,208 @@ import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
|||
@Service
|
||||
public class AiDeptArkConfigServiceImpl implements IAiDeptArkConfigService {
|
||||
|
||||
private static final String BYTE_API_KEY_CACHE_SUFFIX = "_byte_api_key";
|
||||
|
||||
@Autowired
|
||||
private AiDeptArkConfigMapper aiDeptArkConfigMapper;
|
||||
|
||||
/**
|
||||
* 查询团队(部门)对应火山引擎配置
|
||||
*
|
||||
* @param id 团队(部门)对应火山引擎配置主键
|
||||
* @return 团队(部门)对应火山引擎配置
|
||||
*/
|
||||
@Autowired
|
||||
private EncryptionService encryptionService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
@Autowired
|
||||
private AiUserMapper aiUserMapper;
|
||||
|
||||
@Override
|
||||
public AiDeptArkConfig selectAiDeptArkConfigById(String id) {
|
||||
return aiDeptArkConfigMapper.selectById(id);
|
||||
if (StringUtils.isEmpty(id)) {
|
||||
return null;
|
||||
}
|
||||
AiDeptArkConfig row = aiDeptArkConfigMapper.selectById(Long.parseLong(id.trim()));
|
||||
decryptSensitive(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public AiDeptArkConfig selectDecryptedByDeptId(Long deptId) {
|
||||
if (deptId == null) {
|
||||
return null;
|
||||
}
|
||||
AiDeptArkConfig row = aiDeptArkConfigMapper.selectOne(
|
||||
Wrappers.<AiDeptArkConfig>lambdaQuery().eq(AiDeptArkConfig::getDeptId, deptId));
|
||||
decryptSensitive(row);
|
||||
return row;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteByDeptId(Long deptId) {
|
||||
if (deptId == null) {
|
||||
return 0;
|
||||
}
|
||||
int n = aiDeptArkConfigMapper.delete(
|
||||
Wrappers.<AiDeptArkConfig>lambdaQuery().eq(AiDeptArkConfig::getDeptId, deptId));
|
||||
evictVolcApiKeyCacheForDept(deptId);
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveOrUpdateForDept(Long deptId, String byteApiKey, String project, String modelParm) {
|
||||
if (deptId == null) {
|
||||
return;
|
||||
}
|
||||
String keyPlain = blankToNull(byteApiKey);
|
||||
String projectPlain = blankToNull(project);
|
||||
String modelPlain = blankToNull(modelParm);
|
||||
|
||||
AiDeptArkConfig existing = aiDeptArkConfigMapper.selectOne(
|
||||
Wrappers.<AiDeptArkConfig>lambdaQuery().eq(AiDeptArkConfig::getDeptId, deptId));
|
||||
if (existing == null) {
|
||||
AiDeptArkConfig row = new AiDeptArkConfig();
|
||||
row.setDeptId(deptId);
|
||||
row.setByteApiKey(encodeNullable(keyPlain));
|
||||
row.setProject(encodeNullable(projectPlain));
|
||||
row.setModelParm(modelPlain);
|
||||
row.setCreateBy(SecurityUtils.getUsername());
|
||||
row.setCreateTime(DateUtils.getNowDate());
|
||||
aiDeptArkConfigMapper.insert(row);
|
||||
} else {
|
||||
existing.setByteApiKey(encodeNullable(keyPlain));
|
||||
existing.setProject(encodeNullable(projectPlain));
|
||||
existing.setModelParm(modelPlain);
|
||||
existing.setUpdateBy(SecurityUtils.getUsername());
|
||||
existing.setUpdateTime(DateUtils.getNowDate());
|
||||
aiDeptArkConfigMapper.updateById(existing);
|
||||
}
|
||||
evictVolcApiKeyCacheForDept(deptId);
|
||||
}
|
||||
|
||||
private static String blankToNull(String s) {
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
String t = s.trim();
|
||||
return t.isEmpty() ? null : t;
|
||||
}
|
||||
|
||||
private String encodeNullable(String plain) {
|
||||
if (plain == null) {
|
||||
return null;
|
||||
}
|
||||
return encryptionService.encode(plain);
|
||||
}
|
||||
|
||||
private void decryptSensitive(AiDeptArkConfig row) {
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(row.getByteApiKey())) {
|
||||
row.setByteApiKey(encryptionService.decode(row.getByteApiKey()));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(row.getProject())) {
|
||||
row.setProject(encryptionService.decode(row.getProject()));
|
||||
}
|
||||
}
|
||||
|
||||
private void encryptSensitiveForStore(AiDeptArkConfig row) {
|
||||
if (row == null) {
|
||||
return;
|
||||
}
|
||||
if (StringUtils.isNotEmpty(row.getByteApiKey())) {
|
||||
row.setByteApiKey(encryptionService.encode(row.getByteApiKey()));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(row.getProject())) {
|
||||
row.setProject(encryptionService.encode(row.getProject()));
|
||||
}
|
||||
}
|
||||
|
||||
private void evictVolcApiKeyCacheForDept(Long deptId) {
|
||||
List<Long> userIds = aiUserMapper.selectAiUserIdsByDeptId(deptId);
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Long uid : userIds) {
|
||||
if (uid != null) {
|
||||
redisCache.deleteObject(uid + BYTE_API_KEY_CACHE_SUFFIX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询团队(部门)对应火山引擎配置列表
|
||||
*
|
||||
* @param aiDeptArkConfig 团队(部门)对应火山引擎配置
|
||||
* @return 团队(部门)对应火山引擎配置
|
||||
*/
|
||||
@Override
|
||||
public List<AiDeptArkConfig> selectAiDeptArkConfigList(AiDeptArkConfig aiDeptArkConfig) {
|
||||
LambdaQueryWrapper<AiDeptArkConfig> query = Wrappers.lambdaQuery(aiDeptArkConfig);
|
||||
query.orderByDesc(AiDeptArkConfig::getId);
|
||||
return aiDeptArkConfigMapper.selectList(query);
|
||||
List<AiDeptArkConfig> list = aiDeptArkConfigMapper.selectList(query);
|
||||
for (AiDeptArkConfig row : list) {
|
||||
decryptSensitive(row);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询团队(部门)对应火山引擎配置列表
|
||||
*
|
||||
* @param aiDeptArkConfig 团队(部门)对应火山引擎配置
|
||||
* @return 团队(部门)对应火山引擎配置
|
||||
*/
|
||||
@Override
|
||||
public IPage<AiDeptArkConfig> selectAiDeptArkConfigPage(Page page, AiDeptArkConfig aiDeptArkConfig) {
|
||||
LambdaQueryWrapper<AiDeptArkConfig> query = Wrappers.lambdaQuery(aiDeptArkConfig);
|
||||
return aiDeptArkConfigMapper.selectPage(page, query);
|
||||
IPage<AiDeptArkConfig> result = aiDeptArkConfigMapper.selectPage(page, query);
|
||||
for (AiDeptArkConfig row : result.getRecords()) {
|
||||
decryptSensitive(row);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增团队(部门)对应火山引擎配置
|
||||
*
|
||||
* @param aiDeptArkConfig 团队(部门)对应火山引擎配置
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int insertAiDeptArkConfig(AiDeptArkConfig aiDeptArkConfig) {
|
||||
encryptSensitiveForStore(aiDeptArkConfig);
|
||||
aiDeptArkConfig.setCreateBy(SecurityUtils.getUsername());
|
||||
aiDeptArkConfig.setCreateTime(DateUtils.getNowDate());
|
||||
return aiDeptArkConfigMapper.insert(aiDeptArkConfig);
|
||||
int r = aiDeptArkConfigMapper.insert(aiDeptArkConfig);
|
||||
if (r > 0 && aiDeptArkConfig.getDeptId() != null) {
|
||||
evictVolcApiKeyCacheForDept(aiDeptArkConfig.getDeptId());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改团队(部门)对应火山引擎配置
|
||||
*
|
||||
* @param aiDeptArkConfig 团队(部门)对应火山引擎配置
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int updateAiDeptArkConfig(AiDeptArkConfig aiDeptArkConfig) {
|
||||
encryptSensitiveForStore(aiDeptArkConfig);
|
||||
aiDeptArkConfig.setUpdateBy(SecurityUtils.getUsername());
|
||||
aiDeptArkConfig.setUpdateTime(DateUtils.getNowDate());
|
||||
return aiDeptArkConfigMapper.updateById(aiDeptArkConfig);
|
||||
int r = aiDeptArkConfigMapper.updateById(aiDeptArkConfig);
|
||||
if (r > 0 && aiDeptArkConfig.getDeptId() != null) {
|
||||
evictVolcApiKeyCacheForDept(aiDeptArkConfig.getDeptId());
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除团队(部门)对应火山引擎配置
|
||||
*
|
||||
* @param ids 需要删除的团队(部门)对应火山引擎配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteAiDeptArkConfigByIds(String[] ids)
|
||||
{
|
||||
return aiDeptArkConfigMapper.deleteBatchIds(java.util.Arrays.asList(ids));
|
||||
public int deleteAiDeptArkConfigByIds(String[] ids) {
|
||||
if (ids == null || ids.length == 0) {
|
||||
return 0;
|
||||
}
|
||||
List<Long> longIds = Arrays.stream(ids).map(String::trim).filter(StringUtils::isNotEmpty)
|
||||
.map(Long::parseLong).collect(Collectors.toList());
|
||||
List<AiDeptArkConfig> rows = aiDeptArkConfigMapper.selectBatchIds(longIds);
|
||||
int n = aiDeptArkConfigMapper.deleteBatchIds(longIds);
|
||||
if (n > 0) {
|
||||
for (AiDeptArkConfig row : rows) {
|
||||
if (row != null && row.getDeptId() != null) {
|
||||
evictVolcApiKeyCacheForDept(row.getDeptId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除团队(部门)对应火山引擎配置信息
|
||||
*
|
||||
* @param id 团队(部门)对应火山引擎配置主键
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int deleteAiDeptArkConfigById(String id)
|
||||
{
|
||||
return aiDeptArkConfigMapper.deleteById(id);
|
||||
public int deleteAiDeptArkConfigById(String id) {
|
||||
if (StringUtils.isEmpty(id)) {
|
||||
return 0;
|
||||
}
|
||||
AiDeptArkConfig row = aiDeptArkConfigMapper.selectById(Long.parseLong(id.trim()));
|
||||
int n = aiDeptArkConfigMapper.deleteById(Long.parseLong(id.trim()));
|
||||
if (n > 0 && row != null && row.getDeptId() != null) {
|
||||
evictVolcApiKeyCacheForDept(row.getDeptId());
|
||||
}
|
||||
return n;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ package com.ruoyi.ai.service.impl;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.databind.*;
|
||||
import com.ruoyi.ai.domain.AiDeptArkConfig;
|
||||
import com.ruoyi.ai.mapper.AiUserMapper;
|
||||
import com.ruoyi.common.EncryptionService;
|
||||
import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
||||
import com.ruoyi.common.core.domain.entity.AiUser;
|
||||
import com.ruoyi.common.core.domain.entity.SysDept;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.http.OkHttpUtils;
|
||||
import com.ruoyi.system.mapper.SysDeptMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.*;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
|
@ -34,10 +34,7 @@ public class BaseByteApiService {
|
|||
@Resource
|
||||
private AiUserMapper userMapper;
|
||||
@Resource
|
||||
private SysDeptMapper deptMapper;
|
||||
@Resource
|
||||
private EncryptionService encryptionService;
|
||||
protected String DEPT_ANCESTORS_SPLIT = ",";
|
||||
private IAiDeptArkConfigService aiDeptArkConfigService;
|
||||
@Value("${volcengine.ak}")
|
||||
protected String accessKeyId;
|
||||
@Value("${volcengine.sk}")
|
||||
|
|
@ -70,7 +67,7 @@ public class BaseByteApiService {
|
|||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
|
||||
/**
|
||||
* 根据用户找到对应的project
|
||||
* 根据门户用户 {@code ai_user.dept_id} 对应 {@link AiDeptArkConfig#getProject()}(解密后)解析 project。
|
||||
*/
|
||||
protected String getUserProject() {
|
||||
Long userId = SecurityUtils.getAiUserId();
|
||||
|
|
@ -78,39 +75,14 @@ public class BaseByteApiService {
|
|||
return null;
|
||||
}
|
||||
AiUser user = userMapper.selectAiUserById(userId);
|
||||
if (user == null) {
|
||||
if (user == null || user.getDeptId() == null) {
|
||||
return null;
|
||||
}
|
||||
// 第二层部门ID,API_KEY放在这里
|
||||
Long secondLvDeptId = getSecondLevelDept(user.getDeptId());
|
||||
if (secondLvDeptId == null) {
|
||||
AiDeptArkConfig ark = aiDeptArkConfigService.selectDecryptedByDeptId(user.getDeptId());
|
||||
if (ark == null || StringUtils.isEmpty(ark.getProject())) {
|
||||
return null;
|
||||
}
|
||||
SysDept secondDept = deptMapper.selectDeptById(secondLvDeptId);
|
||||
return encryptionService.decode(secondDept.getProject());
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到当前部门所属第二部门ID
|
||||
*
|
||||
* @param deptId 当前部门
|
||||
*/
|
||||
protected Long getSecondLevelDept(long deptId) {
|
||||
SysDept dept = deptMapper.selectDeptById(deptId);
|
||||
String ancestors = dept.getAncestors();
|
||||
// 判断是第几层
|
||||
if (ancestors == null || ancestors.isEmpty() || "0".equals(ancestors)) {
|
||||
// 第一层
|
||||
return null;
|
||||
}
|
||||
String[] parentDeptArray = ancestors.split(DEPT_ANCESTORS_SPLIT);
|
||||
int length = parentDeptArray.length;
|
||||
if (length == 2) {
|
||||
// 只有一个上级,所以当前节点是第二层,直接返回
|
||||
return deptId;
|
||||
}
|
||||
// 大于二级
|
||||
return Long.parseLong(parentDeptArray[2]);
|
||||
return ark.getProject().trim();
|
||||
}
|
||||
|
||||
public <R> R callApi(String action, Object request, Class<R> responseClass) throws IOException {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
package com.ruoyi.ai.service.impl;
|
||||
|
||||
import com.ruoyi.ai.domain.AiDeptArkConfig;
|
||||
import com.ruoyi.ai.mapper.AiUserMapper;
|
||||
import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
||||
import com.ruoyi.ai.service.IAiUserService;
|
||||
import com.ruoyi.ai.service.IByteDeptApiKeyService;
|
||||
import com.ruoyi.common.EncryptionService;
|
||||
|
|
@ -14,19 +17,22 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService {
|
||||
|
||||
@Resource
|
||||
private EncryptionService encryptionService;
|
||||
private static final String BYTE_API_KEY_CACHE_SUFFIX = "_byte_api_key";
|
||||
private static final String NO_DEPT_MSG = "用户未分配部门:请在后台为门户用户设置 ai_user.dept_id(关联 sys_dept.dept_id)";
|
||||
private static final String NO_DEPT_ROW_MSG = "用户所属部门不存在或已删除,请核对 ai_user.dept_id";
|
||||
private static final String NO_API_KEY_MSG = "部门未配置火山 API Key:请在 sys_dept 为用户所在部门(或其上级二级部门)配置 byte_api_key";
|
||||
private static final String NO_API_KEY_MSG = "部门未配置火山 API Key:请在后台「部门管理」中为该部门配置火山 API Key(ai_dept_ark_config)";
|
||||
private static final int CACHE_HOURS = 1;
|
||||
|
||||
@Resource
|
||||
private EncryptionService encryptionService;
|
||||
|
||||
@Autowired
|
||||
private RedisCache redisCache;
|
||||
|
||||
|
|
@ -36,15 +42,20 @@ public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService {
|
|||
@Autowired
|
||||
private ISysDeptService sysDeptService;
|
||||
|
||||
@Autowired
|
||||
private IAiDeptArkConfigService aiDeptArkConfigService;
|
||||
|
||||
@Autowired
|
||||
private AiUserMapper aiUserMapper;
|
||||
|
||||
@Override
|
||||
public String resolveVolcApiKey(Long aiUserId) {
|
||||
if (aiUserId == null) {
|
||||
throw new ServiceException(NO_DEPT_MSG);
|
||||
}
|
||||
String cacheKey = aiUserId + "_byte_api_key";
|
||||
String cacheKey = aiUserId + BYTE_API_KEY_CACHE_SUFFIX;
|
||||
String cached = redisCache.getCacheObject(cacheKey);
|
||||
if (StringUtils.isNotEmpty(cached)) {
|
||||
// 解密:缓存保存加密后的字符串比较安全
|
||||
return encryptionService.decode(cached);
|
||||
}
|
||||
AiUser aiUser = aiUserService.selectAiUserById(aiUserId);
|
||||
|
|
@ -55,21 +66,11 @@ public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService {
|
|||
if (userDept == null) {
|
||||
throw new ServiceException(NO_DEPT_ROW_MSG);
|
||||
}
|
||||
// 优先使用用户直接归属部门的 Key;多数业务把用户挂在分公司(如 101)并在该节点配 byte_api_key
|
||||
String apiKey = trimKey(userDept.getByteApiKey());
|
||||
if (StringUtils.isEmpty(apiKey)) {
|
||||
Long fallbackDeptId = secondLevelDeptIdFrom(userDept);
|
||||
if (!fallbackDeptId.equals(userDept.getDeptId())) {
|
||||
SysDept keyDept = sysDeptService.selectDeptById(fallbackDeptId);
|
||||
if (keyDept != null) {
|
||||
apiKey = trimKey(keyDept.getByteApiKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
AiDeptArkConfig ark = aiDeptArkConfigService.selectDecryptedByDeptId(aiUser.getDeptId());
|
||||
String apiKey = ark != null ? trimKey(ark.getByteApiKey()) : null;
|
||||
if (StringUtils.isEmpty(apiKey)) {
|
||||
throw new ServiceException(NO_API_KEY_MSG);
|
||||
}
|
||||
// 缓存中保存加密值
|
||||
String encodeApiKey = encryptionService.encode(apiKey);
|
||||
redisCache.setCacheObject(cacheKey, encodeApiKey, CACHE_HOURS, TimeUnit.HOURS);
|
||||
return apiKey;
|
||||
|
|
@ -84,59 +85,46 @@ public class ByteDeptApiKeyServiceImpl implements IByteDeptApiKeyService {
|
|||
if (aiUser == null || aiUser.getDeptId() == null) {
|
||||
return null;
|
||||
}
|
||||
SysDept userDept = sysDeptService.selectDeptById(aiUser.getDeptId());
|
||||
if (userDept == null) {
|
||||
return null;
|
||||
}
|
||||
return secondLevelDeptIdFrom(userDept);
|
||||
return aiUser.getDeptId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String resolveSecondLevelModelParm(Long aiUserId) {
|
||||
Long secondId = resolveSecondLevelDeptId(aiUserId);
|
||||
if (secondId == null) {
|
||||
if (aiUserId == null) {
|
||||
return null;
|
||||
}
|
||||
SysDept second = sysDeptService.selectDeptById(secondId);
|
||||
if (second == null) {
|
||||
AiUser aiUser = aiUserService.selectAiUserById(aiUserId);
|
||||
if (aiUser == null || aiUser.getDeptId() == null) {
|
||||
return null;
|
||||
}
|
||||
String raw = second.getModelParm();
|
||||
AiDeptArkConfig ark = aiDeptArkConfigService.selectDecryptedByDeptId(aiUser.getDeptId());
|
||||
if (ark == null) {
|
||||
return null;
|
||||
}
|
||||
String raw = ark.getModelParm();
|
||||
if (StringUtils.isEmpty(raw)) {
|
||||
return null;
|
||||
}
|
||||
return raw.trim();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evictVolcApiKeyCacheForDept(Long deptId) {
|
||||
if (deptId == null) {
|
||||
return;
|
||||
}
|
||||
List<Long> userIds = aiUserMapper.selectAiUserIdsByDeptId(deptId);
|
||||
if (userIds == null || userIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
for (Long uid : userIds) {
|
||||
if (uid != null) {
|
||||
redisCache.deleteObject(uid + BYTE_API_KEY_CACHE_SUFFIX);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String trimKey(String raw) {
|
||||
return raw == null ? null : raw.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 二级部门:祖级路径中,紧接在「一级」(ancestors 第二段)之下的部门节点;
|
||||
* 深度不足时退回用户当前部门。
|
||||
*/
|
||||
private Long secondLevelDeptIdFrom(SysDept userDept) {
|
||||
String ancestors = userDept.getAncestors();
|
||||
if (StringUtils.isEmpty(ancestors)) {
|
||||
return userDept.getDeptId();
|
||||
}
|
||||
String[] parts = ancestors.split(",");
|
||||
if (parts.length >= 3) {
|
||||
try {
|
||||
return Long.parseLong(parts[2].trim());
|
||||
} catch (NumberFormatException ignored) {
|
||||
return userDept.getDeptId();
|
||||
}
|
||||
}
|
||||
if (parts.length == 2) {
|
||||
try {
|
||||
// return Long.parseLong(parts[1].trim());
|
||||
return userDept.getDeptId();
|
||||
} catch (NumberFormatException ignored) {
|
||||
return userDept.getDeptId();
|
||||
}
|
||||
}
|
||||
return userDept.getDeptId();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,4 +144,9 @@ public interface ISysDeptService
|
|||
* 积分更正:更新部门余额并写入集团流水(手动修改类型),不产生充值/退款订单。
|
||||
*/
|
||||
void editScore(DeptPointsCorrectionRequest request);
|
||||
|
||||
/**
|
||||
* 是否允许配置火山引擎(一级或二级部门;与仅两级部门树一致)。
|
||||
*/
|
||||
boolean isArkConfigurableDept(Long deptId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import java.util.Objects;
|
|||
import java.util.stream.Collectors;
|
||||
|
||||
import com.ruoyi.ai.domain.AiGroupBalanceChangeRecord;
|
||||
import com.ruoyi.ai.service.IAiDeptArkConfigService;
|
||||
import com.ruoyi.ai.service.IAiGroupBalanceChangeRecordService;
|
||||
import com.ruoyi.common.EncryptionService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
|
@ -33,8 +33,6 @@ import com.ruoyi.system.mapper.SysRoleMapper;
|
|||
import com.ruoyi.system.mapper.SysUserMapper;
|
||||
import com.ruoyi.system.service.ISysDeptService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 部门管理 服务实现
|
||||
*
|
||||
|
|
@ -52,12 +50,12 @@ public class SysDeptServiceImpl implements ISysDeptService
|
|||
@Autowired
|
||||
private SysUserMapper userMapper;
|
||||
|
||||
@Resource
|
||||
private EncryptionService encryptionService;
|
||||
|
||||
@Autowired
|
||||
private IAiGroupBalanceChangeRecordService aiGroupBalanceChangeRecordService;
|
||||
|
||||
@Autowired
|
||||
private IAiDeptArkConfigService aiDeptArkConfigService;
|
||||
|
||||
/**
|
||||
* 查询部门管理数据
|
||||
*
|
||||
|
|
@ -146,14 +144,7 @@ public class SysDeptServiceImpl implements ISysDeptService
|
|||
@Override
|
||||
public SysDept selectDeptById(Long deptId)
|
||||
{
|
||||
SysDept sysDept = deptMapper.selectDeptById(deptId);
|
||||
if (sysDept.getByteApiKey() != null && !sysDept.getByteApiKey().isEmpty()) {
|
||||
sysDept.setByteApiKey(encryptionService.decode(sysDept.getByteApiKey()));
|
||||
}
|
||||
if (sysDept.getProject() != null && !sysDept.getProject().isEmpty()) {
|
||||
sysDept.setProject(encryptionService.decode(sysDept.getProject()));
|
||||
}
|
||||
return sysDept;
|
||||
return deptMapper.selectDeptById(deptId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -252,12 +243,6 @@ public class SysDeptServiceImpl implements ISysDeptService
|
|||
throw new ServiceException("部门停用,不允许新增");
|
||||
}
|
||||
dept.setAncestors(info.getAncestors() + "," + dept.getParentId());
|
||||
if (StringUtils.isNotEmpty(dept.getByteApiKey())) {
|
||||
dept.setByteApiKey(encryptionService.encode(dept.getByteApiKey()));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(dept.getProject())) {
|
||||
dept.setProject(encryptionService.encode(dept.getProject()));
|
||||
}
|
||||
return deptMapper.insertDept(dept);
|
||||
}
|
||||
|
||||
|
|
@ -285,12 +270,6 @@ public class SysDeptServiceImpl implements ISysDeptService
|
|||
dept.setAncestors(newAncestors);
|
||||
updateDeptChildren(dept.getDeptId(), newAncestors, oldAncestors);
|
||||
}
|
||||
if (StringUtils.isNotEmpty(dept.getByteApiKey())) {
|
||||
dept.setByteApiKey(encryptionService.encode(dept.getByteApiKey()));
|
||||
}
|
||||
if (StringUtils.isNotEmpty(dept.getProject())) {
|
||||
dept.setProject(encryptionService.encode(dept.getProject()));
|
||||
}
|
||||
int result = deptMapper.updateDept(dept);
|
||||
if (UserConstants.DEPT_NORMAL.equals(dept.getStatus()) && StringUtils.isNotEmpty(dept.getAncestors())
|
||||
&& !StringUtils.equals("0", dept.getAncestors()))
|
||||
|
|
@ -357,9 +336,30 @@ public class SysDeptServiceImpl implements ISysDeptService
|
|||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int deleteDeptById(Long deptId)
|
||||
{
|
||||
return deptMapper.deleteDeptById(deptId);
|
||||
int rows = deptMapper.deleteDeptById(deptId);
|
||||
if (rows > 0)
|
||||
{
|
||||
aiDeptArkConfigService.deleteByDeptId(deptId);
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isArkConfigurableDept(Long deptId)
|
||||
{
|
||||
if (deptId == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
SysDept d = deptMapper.selectDeptById(deptId);
|
||||
if (d == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return isFirstLevelDept(d) || isSecondLevelDept(d);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -518,9 +518,6 @@ public class SysDeptServiceImpl implements ISysDeptService
|
|||
throw new ServiceException("一级部门仅允许修改名称");
|
||||
}
|
||||
newDept.setAncestors(oldDept.getAncestors());
|
||||
newDept.setByteApiKey(oldDept.getByteApiKey());
|
||||
newDept.setProject(oldDept.getProject());
|
||||
newDept.setModelParm(oldDept.getModelParm());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -87,12 +87,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
select count(1) from ai_user where del_flag = '0' and dept_id = #{deptId}
|
||||
</select>
|
||||
|
||||
<<<<<<< HEAD
|
||||
<select id="selectAiUserIdsByDeptId" resultType="java.lang.Long">
|
||||
select id from ai_user where del_flag = '0' and dept_id = #{deptId}
|
||||
=======
|
||||
<select id="countNormalAiUsersByDeptId" resultType="int">
|
||||
select count(1) from ai_user
|
||||
where del_flag = '0' and status = 0 and dept_id = #{deptId}
|
||||
<if test="excludeUserId != null">
|
||||
and id != #{excludeUserId}
|
||||
</if>
|
||||
>>>>>>> origin/seedance_balance
|
||||
</select>
|
||||
<select id="selectPasswordById" resultType="java.lang.String">
|
||||
select password from ai_user where id = #{id}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<result property="leader" column="leader" />
|
||||
<result property="phone" column="phone" />
|
||||
<result property="email" column="email" />
|
||||
<result property="byteApiKey" column="byte_api_key" />
|
||||
<result property="modelParm" column="model_parm" />
|
||||
<result property="status" column="status" />
|
||||
<result property="delFlag" column="del_flag" />
|
||||
<result property="parentName" column="parent_name" />
|
||||
|
|
@ -28,7 +26,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</resultMap>
|
||||
|
||||
<sql id="selectDeptVo">
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.byte_api_key, d.status, d.del_flag, d.create_by, d.create_time, d.project, d.balance, d.max_user_count
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time, d.balance, d.max_user_count
|
||||
from sys_dept d
|
||||
</sql>
|
||||
|
||||
|
|
@ -64,7 +62,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
</select>
|
||||
|
||||
<select id="selectDeptById" parameterType="Long" resultMap="SysDeptResult">
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.byte_api_key, d.model_parm, d.status, d.project, d.balance, d.max_user_count,
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.balance, d.max_user_count,
|
||||
(select dept_name from sys_dept where dept_id = d.parent_id) parent_name
|
||||
from sys_dept d
|
||||
where d.dept_id = #{deptId}
|
||||
|
|
@ -79,8 +77,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
where del_flag = '0' and parent_id = #{deptId} limit 1
|
||||
</select>
|
||||
|
||||
<select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult">
|
||||
select * from sys_dept where find_in_set(#{deptId}, ancestors)
|
||||
<select id="selectChildrenDeptById" parameterType="Long" resultMap="SysDeptResult">
|
||||
select d.dept_id, d.parent_id, d.ancestors, d.dept_name, d.order_num, d.leader, d.phone, d.email, d.status, d.del_flag, d.create_by, d.create_time, d.update_by, d.update_time, d.balance, d.max_user_count
|
||||
from sys_dept d where find_in_set(#{deptId}, d.ancestors)
|
||||
</select>
|
||||
|
||||
<select id="selectNormalChildrenDeptById" parameterType="Long" resultType="int">
|
||||
|
|
@ -106,9 +105,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="leader != null and leader != ''">leader,</if>
|
||||
<if test="phone != null and phone != ''">phone,</if>
|
||||
<if test="email != null and email != ''">email,</if>
|
||||
<if test="byteApiKey != null">byte_api_key,</if>
|
||||
<if test="modelParm != null">model_parm,</if>
|
||||
<if test="project != null">project,</if>
|
||||
<if test="balance != null">balance,</if>
|
||||
<if test="maxUserCount != null">max_user_count,</if>
|
||||
<if test="status != null">status,</if>
|
||||
|
|
@ -123,9 +119,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="leader != null and leader != ''">#{leader},</if>
|
||||
<if test="phone != null and phone != ''">#{phone},</if>
|
||||
<if test="email != null and email != ''">#{email},</if>
|
||||
<if test="byteApiKey != null">#{byteApiKey},</if>
|
||||
<if test="modelParm != null">#{modelParm},</if>
|
||||
<if test="project != null">#{project},</if>
|
||||
<if test="balance != null">#{balance},</if>
|
||||
<if test="maxUserCount != null">#{maxUserCount},</if>
|
||||
<if test="status != null">#{status},</if>
|
||||
|
|
@ -144,9 +137,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
|||
<if test="leader != null">leader = #{leader},</if>
|
||||
<if test="phone != null">phone = #{phone},</if>
|
||||
<if test="email != null">email = #{email},</if>
|
||||
<if test="byteApiKey != null">byte_api_key = #{byteApiKey},</if>
|
||||
<if test="modelParm != null">model_parm = #{modelParm},</if>
|
||||
<if test="project != null">project = #{project},</if>
|
||||
<if test="balance != null">balance = #{balance},</if>
|
||||
<if test="maxUserCount != null">max_user_count = #{maxUserCount},</if>
|
||||
<if test="status != null and status != ''">status = #{status},</if>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,39 @@
|
|||
-- =============================================================================
|
||||
-- 部门火山配置迁表:ai_dept_ark_config 唯一约束、数据搬迁、sys_dept 删除三列
|
||||
-- 部署顺序:在已有 ai_dept_ark_config 与 sys_dept 火山列的前提下执行;
|
||||
-- 可重复执行 INSERT 段(NOT EXISTS 跳过已搬迁行)。
|
||||
-- =============================================================================
|
||||
|
||||
-- 1) 一部门一行(若已存在同名索引请跳过本步)
|
||||
ALTER TABLE `ai_dept_ark_config`
|
||||
ADD UNIQUE INDEX `uk_dept_id` (`dept_id`);
|
||||
|
||||
-- 2) 从 sys_dept 搬迁至 ai_dept_ark_config(仅一级、二级部门,且三字段至少其一非空)
|
||||
INSERT INTO `ai_dept_ark_config` (`dept_id`, `model_parm`, `project`, `byte_api_key`, `create_time`)
|
||||
SELECT
|
||||
d.`dept_id`,
|
||||
d.`model_parm`,
|
||||
d.`project`,
|
||||
d.`byte_api_key`,
|
||||
NOW()
|
||||
FROM `sys_dept` d
|
||||
WHERE d.`del_flag` = '0'
|
||||
AND d.`ancestors` IS NOT NULL
|
||||
AND (
|
||||
(d.`parent_id` = 0 AND d.`ancestors` = '0')
|
||||
OR (CHAR_LENGTH(d.`ancestors`) - CHAR_LENGTH(REPLACE(d.`ancestors`, ',', ''))) = 1
|
||||
)
|
||||
AND (
|
||||
NULLIF(TRIM(d.`byte_api_key`), '') IS NOT NULL
|
||||
OR NULLIF(TRIM(d.`project`), '') IS NOT NULL
|
||||
OR NULLIF(TRIM(d.`model_parm`), '') IS NOT NULL
|
||||
)
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM `ai_dept_ark_config` a WHERE a.`dept_id` = d.`dept_id`
|
||||
);
|
||||
|
||||
-- 3) 删除 sys_dept 上已迁移的火山列(搬迁完成后再执行)
|
||||
ALTER TABLE `sys_dept`
|
||||
DROP COLUMN `byte_api_key`,
|
||||
DROP COLUMN `project`,
|
||||
DROP COLUMN `model_parm`;
|
||||
Loading…
Reference in New Issue