初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
package tech.easyflow.admin.controller.ai;
import tech.easyflow.ai.entity.BotCategory;
import tech.easyflow.ai.service.BotCategoryService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* bot分类 控制层。
*
* @author ArkLight
* @since 2025-12-18
*/
@RestController
@RequestMapping("/api/v1/botCategory")
@UsePermission(moduleName = "/api/v1/bot")
public class BotCategoryController extends BaseCurdController<BotCategoryService, BotCategory> {
public BotCategoryController(BotCategoryService service) {
super(service);
}
}

View File

@@ -0,0 +1,372 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaIgnore;
import com.easyagents.core.model.chat.ChatModel;
import com.easyagents.core.model.chat.ChatOptions;
import com.alicp.jetcache.Cache;
import com.mybatisflex.core.keygen.impl.SnowFlakeIDKeyGenerator;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import tech.easyflow.ai.easyagents.listener.PromptChoreChatStreamListener;
import tech.easyflow.ai.entity.*;
import tech.easyflow.ai.service.*;
import tech.easyflow.ai.service.impl.BotServiceImpl;
import tech.easyflow.common.audio.core.AudioServiceManager;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.core.chat.protocol.sse.ChatSseEmitter;
import tech.easyflow.core.chat.protocol.sse.ChatSseUtil;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/bot")
public class BotController extends BaseCurdController<BotService, Bot> {
private final ModelService modelService;
private final BotWorkflowService botWorkflowService;
private final BotDocumentCollectionService botDocumentCollectionService;
private final BotMessageService botMessageService;
@Resource
private BotService botService;
@Autowired
@Qualifier("defaultCache") // 指定 Bean 名称
private Cache<String, Object> cache;
@Resource
private AudioServiceManager audioServiceManager;
public BotController(BotService service, ModelService modelService, BotWorkflowService botWorkflowService,
BotDocumentCollectionService botDocumentCollectionService, BotMessageService botMessageService) {
super(service);
this.modelService = modelService;
this.botWorkflowService = botWorkflowService;
this.botDocumentCollectionService = botDocumentCollectionService;
this.botMessageService = botMessageService;
}
@Resource
private BotPluginService botPluginService;
@GetMapping("/generateConversationId")
public Result<Long> generateConversationId() {
long nextId = new SnowFlakeIDKeyGenerator().nextId();
return Result.ok(nextId);
}
@PostMapping("updateOptions")
@SaCheckPermission("/api/v1/bot/save")
public Result<Void> updateOptions(@JsonBody("id") BigInteger id,
@JsonBody("options") Map<String, Object> options) {
Bot aiBot = service.getById(id);
Map<String, Object> existOptions = aiBot.getOptions();
if (existOptions == null) {
existOptions = new HashMap<>();
}
if (options != null) {
existOptions.putAll(options);
}
aiBot.setOptions(existOptions);
service.updateById(aiBot);
return Result.ok();
}
@PostMapping("updateLlmOptions")
@SaCheckPermission("/api/v1/bot/save")
public Result<Void> updateLlmOptions(@JsonBody("id")
BigInteger id, @JsonBody("llmOptions")
Map<String, Object> llmOptions) {
Bot aiBot = service.getById(id);
Map<String, Object> existLlmOptions = aiBot.getModelOptions();
if (existLlmOptions == null) {
existLlmOptions = new HashMap<>();
}
if (llmOptions != null) {
existLlmOptions.putAll(llmOptions);
}
aiBot.setModelOptions(existLlmOptions);
service.updateById(aiBot);
return Result.ok();
}
@PostMapping("voiceInput")
@SaIgnore
public Result<String> voiceInput(@RequestParam("audio")
MultipartFile audioFile) {
String recognize = null;
try {
recognize = audioServiceManager.audioToText(audioFile.getInputStream());
} catch (Exception e) {
throw new RuntimeException(e);
}
return Result.ok("", recognize);
}
/**
* 处理聊天请求的接口方法
*
* @param prompt 用户输入的聊天内容,必须提供
* @param botId 聊天机器人的唯一标识符,必须提供
* @param conversationId 会话ID用于标识当前对话会话必须提供
* @param messages 历史消息,用于提供上下文,可选
* @return 返回SseEmitter对象用于服务器向客户端推送聊天响应数据
*/
@PostMapping("chat")
@SaIgnore
public SseEmitter chat(
@JsonBody(value = "prompt", required = true) String prompt,
@JsonBody(value = "botId", required = true) BigInteger botId,
@JsonBody(value = "conversationId", required = true) BigInteger conversationId,
@JsonBody(value = "messages") List<Map<String, String>> messages,
@JsonBody(value = "attachments") List<String> attachments
) {
BotServiceImpl.ChatCheckResult chatCheckResult = new BotServiceImpl.ChatCheckResult();
// 前置校验失败则直接返回错误SseEmitter
SseEmitter errorEmitter = botService.checkChatBeforeStart(botId, prompt, conversationId.toString(), chatCheckResult);
if (errorEmitter != null) {
return errorEmitter;
}
return botService.startChat(botId, prompt, conversationId, messages, chatCheckResult, attachments);
}
@PostMapping("updateLlmId")
@SaCheckPermission("/api/v1/bot/save")
public Result<Void> updateBotLlmId(@RequestBody
Bot aiBot) {
service.updateBotLlmId(aiBot);
return Result.ok();
}
@GetMapping("getDetail")
@SaIgnore
public Result<Bot> getDetail(String id) {
return Result.ok(botService.getDetail(id));
}
@Override
@SaIgnore
public Result<Bot> detail(String id) {
Bot data = botService.getDetail(id);
if (data == null) {
return Result.ok(data);
}
Map<String, Object> llmOptions = data.getModelOptions();
if (llmOptions == null) {
llmOptions = new HashMap<>();
}
if (data.getModelId() == null) {
return Result.ok(data);
}
BigInteger llmId = data.getModelId();
Model llm = modelService.getById(llmId);
if (llm == null) {
data.setModelId(null);
return Result.ok(data);
}
Map<String, Object> options = llm.getOptions();
if (options != null && !options.isEmpty()) {
// 获取是否多模态
Boolean multimodal = (Boolean) options.get("multimodal");
llmOptions.put("multimodal", multimodal != null && multimodal);
}
return Result.ok(data);
}
@Override
protected Result<?> onSaveOrUpdateBefore(Bot entity, boolean isSave) {
String alias = entity.getAlias();
if (StringUtils.hasLength(alias)) {
Bot aiBot = service.getByAlias(alias);
if (aiBot != null && isSave) {
throw new BusinessException("别名已存在!");
}
if (aiBot != null && aiBot.getId().compareTo(entity.getId()) != 0) {
throw new BusinessException("别名已存在!");
}
}
if (isSave) {
// 设置默认值
entity.setModelOptions(getDefaultLlmOptions());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
private ChatOptions getChatOptions(Map<String, Object> llmOptions) {
ChatOptions defaultOptions = new ChatOptions();
if (llmOptions != null) {
Object topK = llmOptions.get("topK");
Object maxReplyLength = llmOptions.get("maxReplyLength");
Object temperature = llmOptions.get("temperature");
Object topP = llmOptions.get("topP");
Object thinkingEnabled = llmOptions.get("thinkingEnabled");
if (topK != null) {
defaultOptions.setTopK(Integer.parseInt(String.valueOf(topK)));
}
if (maxReplyLength != null) {
defaultOptions.setMaxTokens(Integer.parseInt(String.valueOf(maxReplyLength)));
}
if (temperature != null) {
defaultOptions.setTemperature(Float.parseFloat(String.valueOf(temperature)));
}
if (topP != null) {
defaultOptions.setTopP(Float.parseFloat(String.valueOf(topP)));
}
if (thinkingEnabled != null) {
defaultOptions.setThinkingEnabled(Boolean.parseBoolean(String.valueOf(thinkingEnabled)));
}
}
return defaultOptions;
}
private Map<String, Object> getDefaultLlmOptions() {
Map<String, Object> defaultLlmOptions = new HashMap<>();
defaultLlmOptions.put("temperature", 0.7);
defaultLlmOptions.put("topK", 4);
defaultLlmOptions.put("maxReplyLength", 2048);
defaultLlmOptions.put("topP", 0.7);
defaultLlmOptions.put("maxMessageCount", 10);
return defaultLlmOptions;
}
private Map<String, Object> errorRespnseMsg(int errorCode, String message) {
HashMap<String, Object> result = new HashMap<>();
result.put("error", errorCode);
result.put("message", message);
return result;
}
@Override
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
QueryWrapper queryWrapperKnowledge = QueryWrapper.create().in(BotDocumentCollection::getBotId, ids);
botDocumentCollectionService.remove(queryWrapperKnowledge);
QueryWrapper queryWrapperBotWorkflow = QueryWrapper.create().in(BotWorkflow::getBotId, ids);
botWorkflowService.remove(queryWrapperBotWorkflow);
QueryWrapper queryWrapperBotPlugins = QueryWrapper.create().in(BotPlugin::getBotId, ids);
botPluginService.remove(queryWrapperBotPlugins);
return super.onRemoveBefore(ids);
}
/**
* 系统提示词优化
*
* @param prompt
* @return
*/
@PostMapping("prompt/chore/chat")
@SaIgnore
public SseEmitter chat(
@JsonBody(value = "prompt", required = true) String prompt,
@JsonBody(value = "botId", required = true) BigInteger botId
){
if (!StringUtils.hasLength(prompt)) {
throw new BusinessException("提示词不能为空!");
}
String promptChore = "# 角色与目标\n" +
"\n" +
"你是一位专业的提示词工程师Prompt Engineer。你的任务是分析我提供的“用户原始提示词”并将其优化成一个结构清晰、指令明确、效果最优的“系统提示词System Prompt”。\n" +
"\n" +
"这个优化后的系统提示词将直接用于引导一个AI助手使其能够精准、高效地完成用户的请求。\n" +
"\n" +
"# 优化指南 (请严格遵循)\n" +
"\n" +
"在优化过程中,请遵循以下原则,以确保最终提示词的质量:\n" +
"\n" +
"1. **角色定义 (Role Definition)**\n" +
" * 为AI助手明确一个具体、专业的角色。这个角色应该与任务高度相关。\n" +
" * 例如:“你是一位资深的软件架构师”、“你是一位经验丰富的产品经理”。\n" +
"\n" +
"2. **任务与目标 (Task & Goal)**\n" +
" * 清晰、具体地描述AI需要完成的任务。\n" +
" * 明确指出期望的最终输出是什么,以及输出的目标和用途。\n" +
" * 避免模糊不清的指令。\n" +
"\n" +
"3. **输入输出格式 (Input/Output Format)**\n" +
" * 如果任务涉及到处理特定格式的数据,请明确说明输入数据的格式。\n" +
" * **至关重要**请为AI的输出指定一个清晰、结构化的格式。这能极大地提升结果的可用性。\n" +
" * 例如“请以Markdown表格的形式输出”、“请分点列出每点不超过20字”。\n" +
"\n" +
"4. **背景与上下文 (Context & Background)**\n" +
" * 提供完成任务所必需的背景信息。\n" +
" * 例如:项目的阶段、目标用户、使用的技术栈、相关的约束条件等。\n" +
"\n" +
"5. **语气与风格 (Tone & Style)**\n" +
" * 指定AI回答时应采用的语气和风格。\n" +
" * 例如:“专业且简洁”、“通俗易懂,避免使用专业术语”。\n" +
"\n" +
"6. **约束与规则 (Constraints & Rules)**\n" +
" * 设定AI在回答时必须遵守的规则和限制。\n" +
" * 例如:“回答必须基于提供的文档”、“禁止猜测用户未提及的信息”。\n" +
"\n" +
"7. **思考过程 (Chain-of-Thought)**\n" +
" * 对于复杂的推理任务可以引导AI展示其思考过程。\n" +
" * 例如:“请先分析问题的关键点,然后给出解决方案,并解释你的推理步骤。”\n" +
"\n" +
"# 输出要求\n" +
"\n" +
"请你直接输出优化后的完整系统提示词。不要包含任何额外的解释或说明。\n" +
"\n" +
"---\n" +
"\n" +
"## 用户原始提示词\n" +
"[" + prompt + "]\n";
Bot aiBot = service.getById(botId);
if (aiBot == null) {
return ChatSseUtil.sendSystemError(null, "聊天助手不存在");
}
ChatSseEmitter sseEmitter = new ChatSseEmitter();
Model model = modelService.getModelInstance(aiBot.getModelId());
if (model == null) {
return ChatSseUtil.sendSystemError(null, "模型不存在");
}
ChatModel chatModel = model.toChatModel();
if (chatModel == null) {
return ChatSseUtil.sendSystemError(null, "模型不存在");
}
PromptChoreChatStreamListener promptChoreChatStreamListener = new PromptChoreChatStreamListener(sseEmitter);
chatModel.chatStream(promptChore, promptChoreChatStreamListener);
return sseEmitter.getEmitter();
}
}

View File

@@ -0,0 +1,46 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.ai.entity.BotDocumentCollection;
import tech.easyflow.ai.service.BotDocumentCollectionService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.math.BigInteger;
import java.util.List;
/**
* 控制层。
*
* @author michael
* @since 2024-08-28
*/
@RestController
@RequestMapping("/api/v1/botKnowledge")
@UsePermission(moduleName = "/api/v1/bot")
public class BotDocumentCollectionController extends BaseCurdController<BotDocumentCollectionService, BotDocumentCollection> {
public BotDocumentCollectionController(BotDocumentCollectionService service) {
super(service);
}
@GetMapping("list")
@Override
public Result<List<BotDocumentCollection>> list(BotDocumentCollection entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<BotDocumentCollection> botDocumentCollections = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
return Result.ok(botDocumentCollections);
}
@PostMapping("updateBotKnowledgeIds")
public Result<?> save(@JsonBody("botId") BigInteger botId, @JsonBody("knowledgeIds") BigInteger [] knowledgeIds) {
service.saveBotAndKnowledge(botId, knowledgeIds);
return Result.ok();
}
}

View File

@@ -0,0 +1,40 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.ai.entity.BotMcp;
import tech.easyflow.ai.service.BotMcpService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.math.BigInteger;
import java.util.List;
import java.util.Map;
/**
* 控制层。
*
* @author wangGangQiang
* @since 2026-01-05
*/
@RestController
@RequestMapping("/api/v1/botMcp")
@UsePermission(moduleName = "/api/v1/bot")
public class BotMcpController extends BaseCurdController<BotMcpService, BotMcp> {
public BotMcpController(BotMcpService service) {
super(service);
}
@PostMapping("updateBotMcpToolIds")
public Result<?> save(@JsonBody("botId") BigInteger botId,
@JsonBody("mcpSelectedData") List<Map<String, List<List<String>>>> mcpSelectedData) {
service.updateBotMcpToolIds(botId, mcpSelectedData);
return Result.ok();
}
}

View File

@@ -0,0 +1,35 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.BotMessage;
import tech.easyflow.ai.service.BotMessageService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
/**
* Bot 消息记录表 控制层。
*
* @author michael
* @since 2024-11-04
*/
@RestController
@RequestMapping("/api/v1/botMessage")
@UsePermission(moduleName = "/api/v1/bot")
public class BotMessageController extends BaseCurdController<BotMessageService, BotMessage> {
private final BotMessageService botMessageService;
public BotMessageController(BotMessageService service, BotMessageService botMessageService) {
super(service);
this.botMessageService = botMessageService;
}
@Override
protected Result<?> onSaveOrUpdateBefore(BotMessage entity, boolean isSave) {
entity.setAccountId(SaTokenUtil.getLoginAccount().getId());
return super.onSaveOrUpdateBefore(entity, isSave);
}
}

View File

@@ -0,0 +1,23 @@
package tech.easyflow.admin.controller.ai;
import tech.easyflow.ai.entity.BotModel;
import tech.easyflow.ai.service.BotModelService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制层。
*
* @author michael
* @since 2024-08-28
*/
@RestController
@RequestMapping("/api/v1/botLlm")
@UsePermission(moduleName = "/api/v1/bot")
public class BotModelController extends BaseCurdController<BotModelService, BotModel> {
public BotModelController(BotModelService service) {
super(service);
}
}

View File

@@ -0,0 +1,74 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.ai.entity.BotPlugin;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.ai.service.BotPluginService;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.List;
/**
* 控制层。
*
* @author michael
* @since 2025-04-07
*/
@RestController
@RequestMapping("/api/v1/botPlugins")
@UsePermission(moduleName = "/api/v1/bot")
public class BotPluginController extends BaseCurdController<BotPluginService, BotPlugin> {
public BotPluginController(BotPluginService service) {
super(service);
}
@Resource
private BotPluginService botPluginService;
@GetMapping("list")
public Result<List<BotPlugin>> list(BotPlugin entity, Boolean asTree, String sortKey, String sortType){
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<BotPlugin> botPlugins = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
List<BotPlugin> list = Tree.tryToTree(botPlugins, asTree);
return Result.ok(list);
}
@PostMapping("/getList")
public Result<List<Plugin>> getList(@JsonBody(value = "botId", required = true) String botId){
return Result.ok(botPluginService.getList(botId));
}
@PostMapping("/getBotPluginToolIds")
public Result<List<BigInteger>> getBotPluginToolIds(@JsonBody(value = "botId", required = true) String botId){
return Result.ok(botPluginService.getBotPluginToolIds(botId));
}
@PostMapping("/doRemove")
public Result<Boolean> doRemove(@JsonBody(value = "botId", required = true) String botId,
@JsonBody(value = "pluginToolId", required = true) String pluginToolId){
return Result.ok(botPluginService.doRemove(botId, pluginToolId));
}
@PostMapping("updateBotPluginToolIds")
public Result<?> save(@JsonBody("botId") BigInteger botId, @JsonBody("pluginToolIds") BigInteger [] pluginToolIds) {
service.saveBotAndPluginTool(botId, pluginToolIds);
return Result.ok();
}
}

View File

@@ -0,0 +1,23 @@
package tech.easyflow.admin.controller.ai;
import tech.easyflow.ai.entity.BotRecentlyUsed;
import tech.easyflow.ai.service.BotRecentlyUsedService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 最近使用 控制层。
*
* @author ArkLight
* @since 2025-12-18
*/
@RestController
@RequestMapping("/api/v1/botRecentlyUsed")
@UsePermission(moduleName = "/api/v1/bot")
public class BotRecentlyUsedController extends BaseCurdController<BotRecentlyUsedService, BotRecentlyUsed> {
public BotRecentlyUsedController(BotRecentlyUsedService service) {
super(service);
}
}

View File

@@ -0,0 +1,48 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.ai.entity.BotWorkflow;
import tech.easyflow.ai.service.BotWorkflowService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.web.controller.BaseCurdController;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.math.BigInteger;
import java.util.List;
/**
* 控制层。
*
* @author michael
* @since 2024-08-28
*/
@RestController
@RequestMapping("/api/v1/botWorkflow")
@UsePermission(moduleName = "/api/v1/bot")
public class BotWorkflowController extends BaseCurdController<BotWorkflowService, BotWorkflow> {
public BotWorkflowController(BotWorkflowService service) {
super(service);
}
@GetMapping("list")
@Override
public Result<List<BotWorkflow>> list(BotWorkflow entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<BotWorkflow> botWorkflows = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
List<BotWorkflow> list = Tree.tryToTree(botWorkflows, asTree);
return Result.ok(list);
}
@PostMapping("updateBotWorkflowIds")
public Result<?> save(@JsonBody("botId") BigInteger botId, @JsonBody("workflowIds") BigInteger [] workflowIds) {
service.saveBotAndWorkflowTool(botId, workflowIds);
return Result.ok();
}
}

View File

@@ -0,0 +1,118 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.easyagents.core.model.embedding.EmbeddingModel;
import tech.easyflow.ai.entity.DocumentChunk;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import com.easyagents.core.document.Document;
import com.easyagents.core.store.DocumentStore;
import com.easyagents.core.store.StoreOptions;
import com.easyagents.core.store.StoreResult;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/documentChunk")
@UsePermission(moduleName = "/api/v1/documentCollection")
public class DocumentChunkController extends BaseCurdController<DocumentChunkService, DocumentChunk> {
@Resource
DocumentCollectionService documentCollectionService;
@Resource
ModelService modelService;
@Resource
DocumentChunkService documentChunkService;
public DocumentChunkController(DocumentChunkService service) {
super(service);
}
@PostMapping("update")
@SaCheckPermission("/api/v1/documentCollection/save")
public Result<?> update(@JsonBody DocumentChunk documentChunk) {
boolean success = service.updateById(documentChunk);
if (success){
DocumentChunk record = documentChunkService.getById(documentChunk.getId());
DocumentCollection knowledge = documentCollectionService.getById(record.getDocumentCollectionId());
if (knowledge == null) {
return Result.fail(1, "知识库不存在");
}
DocumentStore documentStore = knowledge.toDocumentStore();
if (documentStore == null) {
return Result.fail(2, "知识库没有配置向量库");
}
// 设置向量模型
Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId());
if (model == null) {
return Result.fail(3, "知识库没有配置向量模型");
}
EmbeddingModel embeddingModel = model.toEmbeddingModel();
documentStore.setEmbeddingModel(embeddingModel);
StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection());
Document document = Document.of(documentChunk.getContent());
document.setId(documentChunk.getId());
Map<String, Object> metadata = new HashMap<>();
metadata.put("keywords", documentChunk.getMetadataKeyWords());
metadata.put("questions", documentChunk.getMetadataQuestions());
document.setMetadataMap(metadata);
StoreResult result = documentStore.update(document, options); // 更新已有记录
return Result.ok(result);
}
return Result.ok(false);
}
@PostMapping("removeChunk")
@SaCheckPermission("/api/v1/documentCollection/remove")
public Result<?> remove(@JsonBody(value = "id", required = true) BigInteger chunkId) {
DocumentChunk docChunk = documentChunkService.getById(chunkId);
if (docChunk == null) {
return Result.fail(1, "记录不存在");
}
DocumentCollection knowledge = documentCollectionService.getById(docChunk.getDocumentCollectionId());
if (knowledge == null) {
return Result.fail(2, "知识库不存在");
}
DocumentStore documentStore = knowledge.toDocumentStore();
if (documentStore == null) {
return Result.fail(3, "知识库没有配置向量库");
}
// 设置向量模型
Model model = modelService.getModelInstance(knowledge.getVectorEmbedModelId());
if (model == null) {
return Result.fail(4, "知识库没有配置向量模型");
}
EmbeddingModel embeddingModel = model.toEmbeddingModel();
documentStore.setEmbeddingModel(embeddingModel);
StoreOptions options = StoreOptions.ofCollectionName(knowledge.getVectorStoreCollection());
List<BigInteger> deleteList = new ArrayList<>();
deleteList.add(chunkId);
documentStore.delete(deleteList, options);
documentChunkService.removeChunk(knowledge, chunkId);
return super.remove(chunkId);
}
}

View File

@@ -0,0 +1,54 @@
package tech.easyflow.admin.controller.ai;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.DocumentCollectionCategory;
import tech.easyflow.ai.entity.WorkflowCategory;
import tech.easyflow.ai.mapper.DocumentCollectionMapper;
import tech.easyflow.ai.service.DocumentCollectionCategoryService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.WorkflowCategoryService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import javax.annotation.Resource;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* 控制层。
*
* @author wangGangQiang
* @since 2026-01-23
*/
@RestController
@RequestMapping("/api/v1/documentCollectionCategory")
@UsePermission(moduleName = "/api/v1/documentCollection")
public class DocumentCollectionCategoryController extends BaseCurdController<DocumentCollectionCategoryService, DocumentCollectionCategory> {
@Resource
private DocumentCollectionMapper documentCollectionMapper;
public DocumentCollectionCategoryController(DocumentCollectionCategoryService service) {
super(service);
}
@Override
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
ids.forEach(id -> {
QueryWrapper queryWrapper = QueryWrapper.create().eq(DocumentCollection::getCategoryId, id);
List<DocumentCollection> documentCollections = documentCollectionMapper.selectListByQuery(queryWrapper);
if (!documentCollections.isEmpty()) {
throw new BusinessException("请先删除该分类下的所有知识库");
}
});
return super.onRemoveBefore(ids);
}
}

View File

@@ -0,0 +1,108 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.easyagents.core.document.Document;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.util.StringUtils;
import tech.easyflow.ai.entity.BotDocumentCollection;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.service.BotDocumentCollectionService;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.web.exceptions.BusinessException;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/documentCollection")
public class DocumentCollectionController extends BaseCurdController<DocumentCollectionService, DocumentCollection> {
private final DocumentChunkService chunkService;
private final ModelService llmService;
@Resource
private BotDocumentCollectionService botDocumentCollectionService;
public DocumentCollectionController(DocumentCollectionService service, DocumentChunkService chunkService, ModelService llmService) {
super(service);
this.chunkService = chunkService;
this.llmService = llmService;
}
@Override
protected Result<?> onSaveOrUpdateBefore(DocumentCollection entity, boolean isSave) {
String alias = entity.getAlias();
if (StringUtils.hasLength(alias)){
DocumentCollection knowledge = service.getByAlias(alias);
if (knowledge != null && isSave){
throw new BusinessException("别名已存在!");
}
if (knowledge != null && knowledge.getId().compareTo(entity.getId()) != 0){
throw new BusinessException("别名已存在!");
}
} else {
entity.setAlias(null);
}
if (isSave){
Map<String, Object> options = new HashMap<>();
if (entity.getSearchEngineEnable() == null){
entity.setSearchEngineEnable(false);
}
options.put("canUpdateEmbeddingModel", true);
entity.setOptions(options);
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
@GetMapping("search")
@SaCheckPermission("/api/v1/documentCollection/query")
public Result<List<Document>> search(@RequestParam BigInteger knowledgeId, @RequestParam String keyword) {
return Result.ok(service.search(knowledgeId, keyword));
}
@Override
protected Result<Void> onRemoveBefore(Collection<Serializable> ids) {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.in(BotDocumentCollection::getId, ids);
boolean exists = botDocumentCollectionService.exists(queryWrapper);
if (exists){
throw new BusinessException("此知识库还关联着bot请先取消关联");
}
return null;
}
@Override
public Result<DocumentCollection> detail(String id) {
return Result.ok(service.getDetail(id));
}
}

View File

@@ -0,0 +1,201 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.MediaType;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.ai.entity.Document;
import tech.easyflow.ai.entity.DocumentCollection;
import tech.easyflow.ai.entity.DocumentCollectionSplitParams;
import tech.easyflow.ai.service.DocumentChunkService;
import tech.easyflow.ai.service.DocumentCollectionService;
import tech.easyflow.ai.service.DocumentService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.util.RequestUtil;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/document")
@UsePermission(moduleName = "/api/v1/documentCollection")
public class DocumentController extends BaseCurdController<DocumentService, Document> {
private final DocumentCollectionService knowledgeService;
@Autowired
private DocumentService documentService;
@Value("${easyflow.storage.local.root}")
private String fileUploadPath;
public DocumentController(DocumentService service,
DocumentCollectionService knowledgeService,
DocumentChunkService documentChunkService, ModelService modelService) {
super(service);
this.knowledgeService = knowledgeService;
}
@PostMapping("removeDoc")
@Transactional
@SaCheckPermission("/api/v1/documentCollection/remove")
public Result<?> remove(@JsonBody(value = "id", required = true) String id) {
List<Serializable> ids = Collections.singletonList(id);
Result<?> result = onRemoveBefore(ids);
if (result != null) return result;
boolean isSuccess = documentService.removeDoc(id);
if (!isSuccess){
return Result.ok(false);
}
boolean success = service.removeById(id);
onRemoveAfter(ids);
return Result.ok(success);
}
/**
* 查询所有所有数据
*
* @param entity
* @param asTree
* @param sortKey
* @param sortType
* @return 所有数据
*/
@GetMapping("list")
@Override
@SaCheckPermission("/api/v1/documentCollection/query")
public Result<List<Document>> list(Document entity, Boolean asTree, String sortKey, String sortType) {
String kbSlug = RequestUtil.getParamAsString("id");
if (StringUtil.noText(kbSlug)) {
throw new BusinessException("知识库id不能为空");
}
DocumentCollection knowledge = StringUtil.isNumeric(kbSlug)
? knowledgeService.getById(kbSlug)
: knowledgeService.getOne(QueryWrapper.create().eq(DocumentCollection::getSlug, kbSlug));
if (knowledge == null) {
throw new BusinessException("知识库不存在");
}
QueryWrapper queryWrapper = QueryWrapper.create()
.eq(Document::getCollectionId, knowledge.getId());
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<Document> documents = service.list(queryWrapper);
List<Document> list = Tree.tryToTree(documents, asTree);
return Result.ok(list);
}
@GetMapping("documentList")
@SaCheckPermission("/api/v1/documentCollection/query")
public Result<Page<Document>> documentList(@RequestParam(name="title", required = false) String fileName, @RequestParam(name="pageSize") int pageSize, @RequestParam(name = "pageNumber") int pageNumber) {
String kbSlug = RequestUtil.getParamAsString("id");
if (StringUtil.noText(kbSlug)) {
throw new BusinessException("知识库id不能为空");
}
Page<Document> documentList = documentService.getDocumentList(kbSlug, pageSize, pageNumber,fileName);
return Result.ok(documentList);
}
@Override
protected String getDefaultOrderBy() {
return "order_no asc";
}
@PostMapping("update")
@Override
@SaCheckPermission("/api/v1/documentCollection/save")
public Result<Boolean> update(@JsonBody Document entity) {
super.update(entity);
return Result.ok(updatePosition(entity));
}
/**
* 文本拆分/保存
*
*/
@PostMapping(value = {"textSplit", "/saveText"})
@SaCheckPermission("/api/v1/documentCollection/save")
public Result<?> textSplit(@JsonBody DocumentCollectionSplitParams documentCollectionSplitParams) {
return documentService.textSplit(documentCollectionSplitParams);
}
/**
* 更新 entity
*
* @param entity
* @return Result
*/
private boolean updatePosition(Document entity) {
Integer orderNo = entity.getOrderNo();
if (orderNo != null) {
if (orderNo <= 0) orderNo = 0;
BigInteger knowledgeId = service.getById(entity.getId()).getCollectionId();
List<Document> list = service.list(QueryWrapper.create()
.eq(Document::getCollectionId, knowledgeId)
.orderBy(getDefaultOrderBy())
);
list.removeIf(item -> item.getId().equals(entity.getId()));
if (orderNo >= list.size()) {
list.add(entity);
} else {
list.add(orderNo, entity);
}
List<Document> updateList = new ArrayList<>();
for (int i = 0; i < list.size(); i++) {
Document updateItem = new Document();
updateItem.setId(list.get(i).getId());
updateItem.setOrderNo(i);
updateList.add(updateItem);
}
service.updateBatch(updateList);
}
return true;
}
public String getRootPath() {
if (StringUtil.hasText(this.fileUploadPath)) {
return this.fileUploadPath;
}
ClassPathResource fileResource = new ClassPathResource("/");
try {
return new File(fileResource.getFile(), "/public").getAbsolutePath();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,21 @@
package tech.easyflow.admin.controller.ai;
import tech.easyflow.ai.entity.DocumentHistory;
import tech.easyflow.ai.service.DocumentHistoryService;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/documentHistory")
public class DocumentHistoryController extends BaseCurdController<DocumentHistoryService, DocumentHistory> {
public DocumentHistoryController(DocumentHistoryService service) {
super(service);
}
}

View File

@@ -0,0 +1,73 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaIgnore;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.HandlerMapping;
import tech.easyflow.common.util.StringUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@RestController
public class FilePreviewController {
// 定义图片存储的基础路径
@Value("${easyflow.storage.local.root}")
private String fileUploadPath;
@SaIgnore
@GetMapping("/api/images/**")
public ResponseEntity<byte[]> getImage(HttpServletRequest request) throws IOException {
// 获取完整的路径
String fullPath = (String) request.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
String basePath = "/api/images/";
String filePath = fullPath.substring(basePath.length()-1);
String imagePath = getRootPath() + filePath;
File imageFile = new File(imagePath);
if (!imageFile.exists()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
// 读取文件内容
byte[] fileContent = Files.readAllBytes(imageFile.toPath());
// 返回文件内容
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, getContentType(imageFile))
.body(fileContent);
}
// 根据文件扩展名获取 MIME 类型
private String getContentType(File file) {
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg")) {
return "image/jpeg";
} else if (fileName.endsWith(".png")) {
return "image/png";
} else if (fileName.endsWith(".gif")) {
return "image/gif";
}
return "application/octet-stream"; // 默认类型
}
public String getRootPath() {
if (StringUtil.hasText(this.fileUploadPath)) {
return this.fileUploadPath;
}
ClassPathResource fileResource = new ClassPathResource("/");
try {
return new File(fileResource.getFile(), "/public").getAbsolutePath();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,83 @@
package tech.easyflow.admin.controller.ai;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.BotMcp;
import tech.easyflow.ai.entity.Mcp;
import tech.easyflow.ai.service.BotMcpService;
import tech.easyflow.ai.service.McpService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.io.Serializable;
/**
* 控制层。
*
* @author wangGangQiang
* @since 2026-01-04
*/
@RestController
@RequestMapping("/api/v1/mcp")
public class McpController extends BaseCurdController<McpService, Mcp> {
public McpController(McpService service) {
super(service);
}
@Resource
private BotMcpService botMcpService;
@Override
public Result<?> save(Mcp entity) {
return service.saveMcp(entity);
}
@Override
public Result<?> update(Mcp entity) {
return service.updateMcp(entity);
}
@Override
@Transactional
public Result<?> remove(Serializable id) {
service.removeMcp(id);
botMcpService.remove(QueryWrapper.create().eq(BotMcp::getMcpId, id));
return Result.ok();
}
@Override
public Result<Page<Mcp>> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
Result<Page<Mcp>> page = super.page(request, sortKey, sortType, pageNumber, pageSize);
return service.pageMcp(page);
}
@PostMapping("/getMcpTools")
public Result<Mcp> getMcpTools(@JsonBody("id") String id) {
return Result.ok(service.getMcpTools(id));
}
@GetMapping("pageTools")
public Result<Page<Mcp>> pageTools(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
if (pageNumber == null || pageNumber < 1) {
pageNumber = 1L;
}
if (pageSize == null || pageSize < 1) {
pageSize = 10L;
}
QueryWrapper queryWrapper = buildQueryWrapper(request);
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
Page<Mcp> mcpPage = queryPage(new Page<>(pageNumber, pageSize), queryWrapper);
return Result.ok(service.pageTools(mcpPage));
}
}

View File

@@ -0,0 +1,174 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.BaseMapper;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.ai.entity.table.ModelTableDef;
import tech.easyflow.ai.mapper.ModelMapper;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/model")
public class ModelController extends BaseCurdController<ModelService, Model> {
public ModelController(ModelService service) {
super(service);
}
@Autowired
ModelService modelService;
@Resource
ModelMapper modelMapper;
@GetMapping("list")
@SaCheckPermission("/api/v1/model/query")
public Result<List<Model>> list(Model entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<Model> list = Tree.tryToTree(modelMapper.selectListWithRelationsByQuery(queryWrapper), asTree);
list.forEach(item -> {
String providerName = Optional.ofNullable(item.getModelProvider())
.map(ModelProvider::getProviderName)
.orElse("-");
item.setTitle(providerName + "/" + item.getTitle());
});
return Result.ok(list);
}
@GetMapping("getList")
@SaCheckPermission("/api/v1/model/query")
public Result<Map<String, Map<String, List<Model>>>> getList(Model entity) {
return Result.ok(modelService.getList(entity));
}
@PostMapping("/addAiLlm")
@SaCheckPermission("/api/v1/model/save")
public Result<Boolean> addAiLlm(Model entity) {
LoginAccount account = SaTokenUtil.getLoginAccount();
commonFiled(entity, account.getId(), account.getTenantId(), account.getDeptId());
return Result.ok(modelService.addAiLlm(entity));
}
@GetMapping("verifyLlmConfig")
@SaCheckPermission("/api/v1/model/save")
public Result<?> verifyLlmConfig(@RequestParam BigInteger id) {
Model model = service.getModelInstance(id);
return Result.ok(modelService.verifyModelConfig(model));
}
@PostMapping("/removeByEntity")
@SaCheckPermission("/api/v1/model/remove")
public Result<?> removeByEntity(@RequestBody Model entity) {
modelService.removeByEntity(entity);
return Result.ok();
}
@PostMapping("/updateByEntity")
@SaCheckPermission("/api/v1/model/save")
public Result<?> updateByEntity(@RequestBody Model entity) {
modelService.updateByEntity(entity);
return Result.ok();
}
@GetMapping("/selectLlmByProviderCategory")
@SaCheckPermission("/api/v1/model/query")
public Result<Map<String, List<Model>>> selectLlmByProviderCategory(Model entity, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
BaseMapper<Model> mapper = service.getMapper();
List<Model> totalList = mapper.selectListWithRelationsByQuery(queryWrapper);
Map<String, List<Model>> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName));
return Result.ok(groupList);
}
@GetMapping("/selectLlmByProviderAndModelType")
@SaCheckPermission("/api/v1/model/query")
public Result<Map<String, List<Model>>> selectLlmByProviderAndModelType(
@RequestParam String modelType,
@RequestParam BigInteger providerId,
@RequestParam(required = false) String selectText
) {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.eq(Model::getProviderId, providerId);
queryWrapper.eq(Model::getModelType, modelType);
if (StringUtils.hasLength(selectText)) {
queryWrapper.and(ModelTableDef.MODEL.TITLE.like(selectText).or(ModelTableDef.MODEL.MODEL_NAME.like(selectText)));
}
List<Model> totalList = service.getMapper().selectListWithRelationsByQuery(queryWrapper);
Map<String, List<Model>> groupList = totalList.stream().collect(Collectors.groupingBy(Model::getGroupName));
return Result.ok(groupList);
}
/**
* 添加所有模型
*
* @param entity
* @return
*/
@PostMapping("addAllLlm")
public Result<?> addAllLlm(@JsonBody Model entity) {
QueryWrapper queryWrapper = QueryWrapper.create().eq(Model::getProviderId, entity.getProviderId());
service.update(entity, queryWrapper);
return Result.ok();
}
@GetMapping("/selectLlmList")
@SaCheckPermission("/api/v1/model/query")
public Result<List<Model>> selectLlmList(Model entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<Model> totalList = Tree.tryToTree(modelMapper.selectListWithRelationsByQuery(queryWrapper), asTree);
totalList.forEach(aiLlm -> {
aiLlm.setTitle(aiLlm.getModelProvider().getProviderName() + "/" + aiLlm.getTitle());
});
return Result.ok(totalList);
}
@Override
protected Result<?> onSaveOrUpdateBefore(Model entity, boolean isSave) {
if (isSave) {
entity.setWithUsed(true);
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
@PostMapping("removeLlmByIds")
@Transactional
public Result<?> removeLlm(@JsonBody(value = "id", required = true) Serializable id) {
List<Serializable> ids = Collections.singletonList(id);
QueryWrapper queryWrapper = QueryWrapper.create().in(Model::getId, ids);
service.remove(queryWrapper);
return Result.ok();
}
}

View File

@@ -0,0 +1,48 @@
package tech.easyflow.admin.controller.ai;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.Model;
import tech.easyflow.ai.entity.ModelProvider;
import tech.easyflow.ai.service.ModelProviderService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import java.io.Serializable;
/**
* 控制层。
*
* @author 12076
* @since 2025-12-16
*/
@RestController
@RequestMapping("/api/v1/modelProvider")
@UsePermission(moduleName = "/api/v1/model")
public class ModelProviderController extends BaseCurdController<ModelProviderService, ModelProvider> {
private final ModelService modelService;
public ModelProviderController(ModelProviderService service, ModelService modelService) {
super(service);
this.modelService = modelService;
}
@Override
@PostMapping("remove")
@Transactional
public Result<?> remove(@JsonBody(value = "id", required = true) Serializable id) {
QueryWrapper modelQueryWrapper = QueryWrapper.create()
.in(Model::getProviderId, id);
if (modelService.count(modelQueryWrapper) > 0) {
throw new BusinessException("Delete all child models before removing this Model Provider");
}
return Result.ok(service.removeById(id));
}
}

View File

@@ -0,0 +1,40 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.PluginCategory;
import tech.easyflow.ai.service.PluginCategoryService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import javax.annotation.Resource;
import java.math.BigInteger;
/**
* 控制层。
*
* @author wangGangQiang
* @since 2025-05-21
*/
@RestController
@RequestMapping("/api/v1/pluginCategory")
@UsePermission(moduleName = "/api/v1/plugin")
public class PluginCategoryController extends BaseCurdController<PluginCategoryService, PluginCategory> {
public PluginCategoryController(PluginCategoryService service) {
super(service);
}
@Resource
private PluginCategoryService pluginCategoryService;
@GetMapping("/doRemoveCategory")
@SaCheckPermission("/api/v1/plugin/remove")
public Result<Boolean> doRemoveCategory(@RequestParam("id") BigInteger id){
return Result.ok(pluginCategoryService.doRemoveCategory(id));
}
}

View File

@@ -0,0 +1,45 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.ai.entity.PluginCategory;
import tech.easyflow.ai.entity.PluginCategoryMapping;
import tech.easyflow.ai.service.PluginCategoryMappingService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
/**
* 控制层。
*
* @author wangGangQiang
* @since 2025-05-21
*/
@RestController
@RequestMapping("/api/v1/pluginCategoryMapping")
public class PluginCategoryMappingController extends BaseCurdController<PluginCategoryMappingService, PluginCategoryMapping> {
public PluginCategoryMappingController(PluginCategoryMappingService service) {
super(service);
}
@Resource
private PluginCategoryMappingService relationService;
@PostMapping("/updateRelation")
public Result<Boolean> updateRelation(
@JsonBody(value="pluginId") BigInteger pluginId,
@JsonBody(value="categoryIds") ArrayList<BigInteger> categoryIds
){
return Result.ok(relationService.updateRelation(pluginId, categoryIds));
}
@GetMapping("/getPluginCategories")
public Result<List<PluginCategory>> getPluginCategories(@RequestParam(value="pluginId") BigInteger pluginId
){
return Result.ok(relationService.getPluginCategories(pluginId));
}
}

View File

@@ -0,0 +1,90 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import tech.easyflow.ai.entity.Plugin;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.ai.service.PluginService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.util.List;
/**
* 控制层。
*
* @author Administrator
* @since 2025-04-25
*/
@RestController
@RequestMapping("/api/v1/plugin")
public class PluginController extends BaseCurdController<PluginService, Plugin> {
public PluginController(PluginService service) {
super(service);
}
@Resource
PluginService pluginService;
@Override
protected Result<?> onSaveOrUpdateBefore(Plugin entity, boolean isSave) {
return super.onSaveOrUpdateBefore(entity, isSave);
}
@PostMapping("/plugin/save")
@SaCheckPermission("/api/v1/plugin/save")
public Result<Boolean> savePlugin(@JsonBody Plugin plugin){
return Result.ok(pluginService.savePlugin(plugin));
}
@PostMapping("/plugin/update")
@SaCheckPermission("/api/v1/plugin/save")
public Result<Boolean> updatePlugin(@JsonBody Plugin plugin){
return Result.ok(pluginService.updatePlugin(plugin));
}
@PostMapping("/plugin/remove")
@SaCheckPermission("/api/v1/plugin/remove")
public Result<Boolean> removePlugin(@JsonBody(value = "id", required = true) String id){
return Result.ok(pluginService.removePlugin(id));
}
@PostMapping("/getList")
@SaCheckPermission("/api/v1/plugin/query")
public Result<List<Plugin>> getList(){
return Result.ok(pluginService.getList());
}
@GetMapping("/pageByCategory")
@SaCheckPermission("/api/v1/plugin/query")
public Result<Page<Plugin>> pageByCategory(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize, int category) {
if (pageNumber == null || pageNumber < 1) {
pageNumber = 1L;
}
if (pageSize == null || pageSize < 1) {
pageSize = 10L;
}
if (category == 0){
QueryWrapper queryWrapper = buildQueryWrapper(request);
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
return Result.ok(queryPage(new Page<>(pageNumber, pageSize), queryWrapper));
} else {
return pluginService.pageByCategory(pageNumber, pageSize, category);
}
}
@Override
protected Page<Plugin> queryPage(Page<Plugin> page, QueryWrapper queryWrapper) {
return service.getMapper().paginateWithRelations(page, queryWrapper);
}
}

View File

@@ -0,0 +1,150 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.BotPlugin;
import tech.easyflow.ai.entity.PluginItem;
import tech.easyflow.ai.service.BotPluginService;
import tech.easyflow.ai.service.PluginItemService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.List;
/**
* 控制层。
*
* @author WangGangqiang
* @since 2025-04-27
*/
@RestController
@RequestMapping("/api/v1/pluginItem")
@UsePermission(moduleName = "/api/v1/plugin")
public class PluginItemController extends BaseCurdController<PluginItemService, PluginItem> {
public PluginItemController(PluginItemService service) {
super(service);
}
@Resource
private PluginItemService pluginItemService;
@Resource
private BotPluginService botPluginService;
@PostMapping("/tool/save")
@SaCheckPermission("/api/v1/plugin/save")
public Result<Boolean> savePlugin(@JsonBody PluginItem pluginItem){
return Result.ok(pluginItemService.savePluginTool(pluginItem));
}
// 插件工具修改页面查询
@PostMapping("/tool/search")
@SaCheckPermission("/api/v1/plugin/query")
public Result<?> searchPlugin(@JsonBody(value = "aiPluginToolId", required = true) BigInteger aiPluginToolId){
return pluginItemService.searchPlugin(aiPluginToolId);
}
@PostMapping("/toolsList")
@SaCheckPermission("/api/v1/plugin/query")
public Result<List<PluginItem>> searchPluginToolByPluginId(@JsonBody(value = "pluginId", required = true) BigInteger pluginId,
@JsonBody(value = "botId", required = false) BigInteger botId){
return Result.ok(pluginItemService.searchPluginToolByPluginId(pluginId, botId));
}
@PostMapping("/tool/update")
@SaCheckPermission("/api/v1/plugin/save")
public Result<Boolean> updatePlugin(@JsonBody PluginItem pluginItem){
return Result.ok(pluginItemService.updatePlugin(pluginItem));
}
@PostMapping("/tool/list")
@SaCheckPermission("/api/v1/plugin/query")
public Result<List<PluginItem>> getPluginToolList(@JsonBody(value = "botId", required = true) BigInteger botId){
return Result.ok(pluginItemService.getPluginToolList(botId));
}
@GetMapping("/getTinyFlowData")
@SaCheckPermission("/api/v1/plugin/query")
public Result<?> getTinyFlowData(BigInteger id) {
JSONObject nodeData = new JSONObject();
PluginItem record = pluginItemService.getById(id);
if (record == null) {
return Result.ok(nodeData);
}
nodeData.put("pluginId", record.getId().toString());
nodeData.put("pluginName", record.getName());
JSONArray parameters = new JSONArray();
JSONArray outputDefs = new JSONArray();
String inputData = record.getInputData();
if (StrUtil.isNotEmpty(inputData)) {
JSONArray array = JSON.parseArray(inputData);
handleArray(array);
parameters = array;
}
String outputData = record.getOutputData();
if (StrUtil.isNotEmpty(outputData)) {
JSONArray array = JSON.parseArray(outputData);
handleArray(array);
outputDefs = array;
}
nodeData.put("parameters", parameters);
nodeData.put("outputDefs", outputDefs);
return Result.ok(nodeData);
}
/**
* 插件试运行接口
* @return
*/
@PostMapping("/test")
public Result<?> pluginToolTest(@JsonBody(value = "inputData", required = true) String inputData,
@JsonBody(value = "pluginToolId", required = true) BigInteger pluginToolId){
return pluginItemService.pluginToolTest(inputData, pluginToolId);
}
private void handleArray(JSONArray array) {
for (Object o : array) {
JSONObject obj = (JSONObject) o;
obj.put("id", IdUtil.simpleUUID());
obj.put("nameDisabled", true);
obj.put("dataTypeDisabled", true);
obj.put("deleteDisabled", true);
obj.put("addChildDisabled", true);
JSONArray children = obj.getJSONArray("children");
if (children != null) {
handleArray(children);
}
}
}
@Override
protected Result<?> onRemoveBefore(Collection<Serializable> ids) {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.in(BotPlugin::getPluginItemId, ids);
boolean exists = botPluginService.exists(queryWrapper);
if (exists){
return Result.fail(1, "此工具还关联着bot请先取消关联");
}
return null;
}
}

View File

@@ -0,0 +1,22 @@
package tech.easyflow.admin.controller.ai;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.ResourceCategory;
import tech.easyflow.ai.service.ResourceCategoryService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
/**
* 素材分类
*/
@RestController
@RequestMapping("/api/v1/resourceCategory")
@UsePermission(moduleName = "/api/v1/resource")
public class ResourceCategoryController extends BaseCurdController<ResourceCategoryService, ResourceCategory> {
public ResourceCategoryController(ResourceCategoryService service) {
super(service);
}
}

View File

@@ -0,0 +1,56 @@
package tech.easyflow.admin.controller.ai;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.http.HttpUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.Resource;
import tech.easyflow.ai.service.ResourceService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.util.Date;
/**
* 素材库
*
* @author ArkLight
* @since 2025-06-27
*/
@RestController
@RequestMapping("/api/v1/resource")
public class ResourceController extends BaseCurdController<ResourceService, Resource> {
public ResourceController(ResourceService service) {
super(service);
}
@Override
protected Result<?> onSaveOrUpdateBefore(Resource entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
String resourceUrl = entity.getResourceUrl();
byte[] bytes = HttpUtil.downloadBytes(resourceUrl);
ByteArrayInputStream stream = new ByteArrayInputStream(bytes);
String suffix = FileTypeUtil.getType(stream, resourceUrl);
entity.setSuffix(suffix);
entity.setFileSize(BigInteger.valueOf(bytes.length));
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
@Override
protected Page<Resource> queryPage(Page<Resource> page, QueryWrapper queryWrapper) {
queryWrapper.eq(Resource::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString());
return super.queryPage(page, queryWrapper);
}
}

View File

@@ -0,0 +1,85 @@
package tech.easyflow.admin.controller.ai;
import cn.hutool.core.util.IdUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.easyagents.flow.core.chain.ChainDefinition;
import com.easyagents.flow.core.chain.Node;
import com.easyagents.flow.core.node.ConfirmNode;
import com.easyagents.flow.core.node.EndNode;
import com.easyagents.flow.core.node.StartNode;
import com.easyagents.flow.core.parser.ChainParser;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.exceptions.BusinessException;
import javax.annotation.Resource;
import java.util.List;
@RequestMapping("/api/v1/workflowNode")
@RestController
public class WorkFlowNodeController {
@Resource
private WorkflowService workflowService;
@Resource
private ChainParser chainParser;
@GetMapping("/getChainParams")
public Result<?> getChainParams(String currentId, String workflowId) {
if (workflowId.equals(currentId)) {
throw new BusinessException("工作流不能作为自身子节点");
}
JSONObject nodeData = new JSONObject();
Workflow workflow = workflowService.getById(workflowId);
if (workflow == null) {
throw new BusinessException("工作流不存在: " + workflowId);
}
nodeData.put("workflowId", workflow.getId());
nodeData.put("workflowName", workflow.getTitle());
ChainDefinition definition = chainParser.parse(workflow.getContent());
List<Node> nodes = definition.getNodes();
JSONArray inputs = new JSONArray();
JSONArray outputs = new JSONArray();
for (Node node : nodes) {
if (node instanceof StartNode) {
inputs = JSON.parseArray(JSON.toJSONString(node.getParameters()));
handleArray(inputs);
}
if (node instanceof EndNode) {
outputs = JSON.parseArray(JSON.toJSONString(((EndNode) node).getOutputDefs()));
handleArray(outputs);
}
if (node instanceof ConfirmNode) {
throw new BusinessException("工作流存在【确认节点】,暂不支持作为子节点");
}
}
nodeData.put("parameters", inputs);
nodeData.put("outputDefs", outputs);
return Result.ok(nodeData);
}
private void handleArray(JSONArray array) {
if (array != null) {
for (Object o : array) {
JSONObject obj = (JSONObject) o;
obj.put("id", IdUtil.simpleUUID());
obj.put("nameDisabled", true);
obj.put("dataTypeDisabled", true);
obj.put("deleteDisabled", true);
obj.put("addChildDisabled", true);
obj.put("refType", "ref");
JSONArray children = obj.getJSONArray("children");
if (children != null) {
handleArray(children);
}
}
}
}
}

View File

@@ -0,0 +1,25 @@
package tech.easyflow.admin.controller.ai;
import tech.easyflow.ai.entity.WorkflowCategory;
import tech.easyflow.ai.service.WorkflowCategoryService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 控制层。
*
* @author ArkLight
* @since 2025-12-11
*/
@RestController
@RequestMapping("/api/v1/workflowCategory")
@UsePermission(moduleName = "/api/v1/workflow")
public class WorkflowCategoryController extends BaseCurdController<WorkflowCategoryService, WorkflowCategory> {
public WorkflowCategoryController(WorkflowCategoryService service) {
super(service);
}
}

View File

@@ -0,0 +1,213 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.mybatisflex.core.query.QueryWrapper;
import com.easyagents.flow.core.chain.*;
import com.easyagents.flow.core.chain.runtime.ChainExecutor;
import com.easyagents.flow.core.parser.ChainParser;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.ai.entity.Workflow;
import tech.easyflow.ai.service.BotWorkflowService;
import tech.easyflow.ai.service.ModelService;
import tech.easyflow.ai.service.WorkflowService;
import tech.easyflow.ai.easyagentsflow.entity.ChainInfo;
import tech.easyflow.ai.easyagentsflow.entity.NodeInfo;
import tech.easyflow.ai.easyagentsflow.service.TinyFlowService;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.service.SysApiKeyService;
import javax.annotation.Resource;
import java.io.InputStream;
import java.io.Serializable;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* 控制层。
*
* @author michael
* @since 2024-08-23
*/
@RestController
@RequestMapping("/api/v1/workflow")
public class WorkflowController extends BaseCurdController<WorkflowService, Workflow> {
private final ModelService modelService;
@Resource
private SysApiKeyService apiKeyService;
@Resource
private BotWorkflowService botWorkflowService;
@Resource
private ChainExecutor chainExecutor;
@Resource
private ChainParser chainParser;
@Resource
private TinyFlowService tinyFlowService;
public WorkflowController(WorkflowService service, ModelService modelService) {
super(service);
this.modelService = modelService;
}
/**
* 节点单独运行
*/
@PostMapping("/singleRun")
@SaCheckPermission("/api/v1/workflow/save")
public Result<?> singleRun(
@JsonBody(value = "workflowId", required = true) BigInteger workflowId,
@JsonBody(value = "nodeId", required = true) String nodeId,
@JsonBody("variables") Map<String, Object> variables) {
Workflow workflow = service.getById(workflowId);
if (workflow == null) {
return Result.fail(1, "工作流不存在");
}
Map<String, Object> res = chainExecutor.executeNode(workflowId.toString(), nodeId, variables);
return Result.ok(res);
}
/**
* 运行工作流 - v2
*/
@PostMapping("/runAsync")
@SaCheckPermission("/api/v1/workflow/save")
public Result<String> runAsync(@JsonBody(value = "id", required = true) BigInteger id,
@JsonBody("variables") Map<String, Object> variables) {
if (variables == null) {
variables = new HashMap<>();
}
Workflow workflow = service.getById(id);
if (workflow == null) {
throw new RuntimeException("工作流不存在");
}
if (StpUtil.isLogin()) {
variables.put(Constants.LOGIN_USER_KEY, SaTokenUtil.getLoginAccount());
}
String executeId = chainExecutor.executeAsync(id.toString(), variables);
return Result.ok(executeId);
}
/**
* 获取工作流运行状态 - v2
*/
@PostMapping("/getChainStatus")
public Result<ChainInfo> getChainStatus(@JsonBody(value = "executeId") String executeId,
@JsonBody("nodes") List<NodeInfo> nodes) {
ChainInfo res = tinyFlowService.getChainStatus(executeId, nodes);
return Result.ok(res);
}
/**
* 恢复工作流运行 - v2
*/
@PostMapping("/resume")
@SaCheckPermission("/api/v1/workflow/save")
public Result<Void> resume(@JsonBody(value = "executeId", required = true) String executeId,
@JsonBody("confirmParams") Map<String, Object> confirmParams) {
chainExecutor.resumeAsync(executeId, confirmParams);
return Result.ok();
}
@PostMapping("/importWorkFlow")
@SaCheckPermission("/api/v1/workflow/save")
public Result<Void> importWorkFlow(Workflow workflow, MultipartFile jsonFile) throws Exception {
InputStream is = jsonFile.getInputStream();
String content = IoUtil.read(is, StandardCharsets.UTF_8);
workflow.setContent(content);
save(workflow);
return Result.ok();
}
@GetMapping("/exportWorkFlow")
@SaCheckPermission("/api/v1/workflow/save")
public Result<String> exportWorkFlow(BigInteger id) {
Workflow workflow = service.getById(id);
return Result.ok("", workflow.getContent());
}
@GetMapping("getRunningParameters")
@SaCheckPermission("/api/v1/workflow/query")
public Result<?> getRunningParameters(@RequestParam BigInteger id) {
Workflow workflow = service.getById(id);
if (workflow == null) {
return Result.fail(1, "can not find the workflow by id: " + id);
}
ChainDefinition definition = chainParser.parse(workflow.getContent());
if (definition == null) {
return Result.fail(2, "节点配置错误,请检查! ");
}
List<Parameter> chainParameters = definition.getStartParameters();
Map<String, Object> res = new HashMap<>();
res.put("parameters", chainParameters);
res.put("title", workflow.getTitle());
res.put("description", workflow.getDescription());
res.put("icon", workflow.getIcon());
return Result.ok(res);
}
@Override
public Result<Workflow> detail(String id) {
Workflow workflow = service.getDetail(id);
return Result.ok(workflow);
}
@GetMapping("/copy")
@SaCheckPermission("/api/v1/workflow/save")
public Result<Void> copy(BigInteger id) {
LoginAccount account = SaTokenUtil.getLoginAccount();
Workflow workflow = service.getById(id);
workflow.setId(null);
workflow.setAlias(IdUtil.fastSimpleUUID());
commonFiled(workflow, account.getId(), account.getTenantId(), account.getDeptId());
service.save(workflow);
return Result.ok();
}
@Override
protected Result onSaveOrUpdateBefore(Workflow entity, boolean isSave) {
String alias = entity.getAlias();
if (StringUtils.hasLength(alias)) {
Workflow workflow = service.getByAlias(alias);
if (workflow == null) {
return null;
}
if (isSave) {
throw new BusinessException("别名已存在!");
}
BigInteger id = entity.getId();
if (id.compareTo(workflow.getId()) != 0) {
throw new BusinessException("别名已存在!");
}
} else {
entity.setAlias(null);
}
return null;
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
QueryWrapper queryWrapper = QueryWrapper.create();
queryWrapper.in("workflow_id", ids);
boolean exists = botWorkflowService.exists(queryWrapper);
if (exists) {
return Result.fail(1, "此工作流还关联有bot请先取消关联后再删除");
}
return null;
}
}

View File

@@ -0,0 +1,64 @@
package tech.easyflow.admin.controller.ai;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.WorkflowExecResult;
import tech.easyflow.ai.entity.WorkflowExecStep;
import tech.easyflow.ai.service.WorkflowExecResultService;
import tech.easyflow.ai.service.WorkflowExecStepService;
import tech.easyflow.ai.utils.WorkFlowUtil;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import javax.annotation.Resource;
import java.math.BigInteger;
/**
* 工作流执行记录
*/
@RestController
@RequestMapping("/api/v1/workflowExecResult")
@UsePermission(moduleName = "/api/v1/workflow")
public class WorkflowExecResultController extends BaseCurdController<WorkflowExecResultService, WorkflowExecResult> {
@Resource
private WorkflowExecStepService recordStepService;
public WorkflowExecResultController(WorkflowExecResultService service) {
super(service);
}
@GetMapping("/del")
@Transactional(rollbackFor = Exception.class)
@SaCheckPermission("/api/v1/workflow/remove")
public Result<Void> del(BigInteger id) {
LoginAccount account = SaTokenUtil.getLoginAccount();
WorkflowExecResult record = service.getById(id);
if (!account.getId().toString().equals(record.getCreatedBy())) {
return Result.fail(1, "非法请求");
}
service.removeById(id);
QueryWrapper w = QueryWrapper.create();
w.eq(WorkflowExecStep::getRecordId, id);
recordStepService.remove(w);
return Result.ok();
}
@Override
protected Page<WorkflowExecResult> queryPage(Page<WorkflowExecResult> page, QueryWrapper queryWrapper) {
queryWrapper.eq(WorkflowExecResult::getCreatedBy, SaTokenUtil.getLoginAccount().getId().toString());
Page<WorkflowExecResult> res = super.queryPage(page, queryWrapper);
for (WorkflowExecResult record : res.getRecords()) {
record.setWorkflowJson(WorkFlowUtil.removeSensitiveInfo(record.getWorkflowJson()));
}
return res;
}
}

View File

@@ -0,0 +1,63 @@
package tech.easyflow.admin.controller.ai;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.ai.entity.WorkflowExecResult;
import tech.easyflow.ai.entity.WorkflowExecStep;
import tech.easyflow.ai.service.WorkflowExecResultService;
import tech.easyflow.ai.service.WorkflowExecStepService;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.exceptions.BusinessException;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 执行记录步骤
*/
@RestController
@RequestMapping("/api/v1/workflowExecStep")
@UsePermission(moduleName = "/api/v1/workflow")
public class WorkflowExecStepController extends BaseCurdController<WorkflowExecStepService, WorkflowExecStep> {
@Resource
private WorkflowExecResultService execRecordService;
public WorkflowExecStepController(WorkflowExecStepService service) {
super(service);
}
@GetMapping("/getListByRecordId")
public Result<List<WorkflowExecStep>> getListByRecordId(BigInteger recordId) {
if (recordId == null) {
throw new BusinessException("recordId不能为空");
}
WorkflowExecResult record = execRecordService.getById(recordId);
String workflowJson = record.getWorkflowJson();
JSONObject workflow = JSON.parseObject(workflowJson);
Map<String, String> idTypeMap = new HashMap<>();
JSONArray nodes = workflow.getJSONArray("nodes");
for (Object node : nodes) {
JSONObject nodeObj = (JSONObject) node;
idTypeMap.put(nodeObj.getString("id"), nodeObj.getString("type"));
}
QueryWrapper w = QueryWrapper.create();
w.eq(WorkflowExecStep::getRecordId, recordId);
List<WorkflowExecStep> list = service.list(w);
for (WorkflowExecStep step : list) {
step.setNodeData(null);
step.setNodeType(idTypeMap.get(step.getNodeId()));
}
return Result.ok(list);
}
}

View File

@@ -0,0 +1,41 @@
package tech.easyflow.admin.controller.auth;
import org.springframework.web.bind.annotation.GetMapping;
import tech.easyflow.auth.entity.LoginDTO;
import tech.easyflow.auth.entity.LoginVO;
import tech.easyflow.auth.service.AuthService;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.jsonbody.JsonBody;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/api/v1/auth/")
public class AuthController {
@Resource
private AuthService authService;
@PostMapping("login")
public Result<LoginVO> login(@JsonBody LoginDTO loginDTO) {
LoginVO res = authService.login(loginDTO);
return Result.ok(res);
}
@PostMapping("logout")
public Result<Void> logout() {
StpUtil.logout();
return Result.ok();
}
@GetMapping("getPermissions")
public Result<List<String>> getPermissions() {
List<String> permissionList = StpUtil.getPermissionList();
return Result.ok(permissionList);
}
}

View File

@@ -0,0 +1,40 @@
package tech.easyflow.admin.controller.common;
import jakarta.servlet.http.HttpServletRequest;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.dict.Dict;
import tech.easyflow.common.dict.DictItem;
import tech.easyflow.common.dict.DictLoader;
import tech.easyflow.common.dict.DictManager;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@RestController
@RequestMapping("/api/v1/dict/")
public class DictController {
@Resource
DictManager dictManager;
@GetMapping("/items/{code}")
public Result<List<DictItem>> items(@PathVariable("code") String code, String keyword, HttpServletRequest request) {
DictLoader loader = dictManager.getLoader(code);
if (loader == null) {
return Result.ok(Collections.emptyList());
}
Map<String, String[]> parameterMap = request.getParameterMap();
Dict dict = loader.load(keyword, parameterMap);
if (dict == null) {
return Result.ok(Collections.emptyList());
}
return Result.ok(dict.getItems());
}
}

View File

@@ -0,0 +1,49 @@
package tech.easyflow.admin.controller.common;
import cloud.tianai.captcha.application.ImageCaptchaApplication;
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
import cloud.tianai.captcha.common.constant.CaptchaTypeConstant;
import cloud.tianai.captcha.common.response.ApiResponse;
import com.alibaba.fastjson2.JSON;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.captcha.tainai.CaptchaData;
import tech.easyflow.system.entity.SysOption;
import tech.easyflow.system.service.SysOptionService;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 公共接口
*/
@RestController
@RequestMapping("/api/v1/public")
public class PublicController {
@Resource
private ImageCaptchaApplication application;
@Resource
private SysOptionService sysOptionService;
/**
* 获取验证码
*/
@RequestMapping(value = "/getCaptcha", produces = "application/json")
public ApiResponse<ImageCaptchaVO> getCaptcha() {
return application.generateCaptcha(CaptchaTypeConstant.SLIDER);
}
/**
* 验证码校验
*/
@PostMapping(value = "/check", produces = "application/json")
public ApiResponse<String> checkCaptcha(@RequestBody CaptchaData data) {
ApiResponse<?> response = application.matching(data.getId(), data.getData());
if (!response.isSuccess()) {
return ApiResponse.ofError("验证码错误");
}
return ApiResponse.ofSuccess(data.getId());
}
}

View File

@@ -0,0 +1,47 @@
package tech.easyflow.admin.controller.common;
import cn.dev33.satoken.annotation.SaIgnore;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.vo.UploadResVo;
import tech.easyflow.common.filestorage.FileStorageService;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
@RestController
@RequestMapping("/api/v1/commons/")
public class UploadController {
@Resource(name = "default")
FileStorageService storageService;
@PostMapping(value = "/upload", produces = MediaType.APPLICATION_JSON_VALUE)
public Result<UploadResVo> upload(MultipartFile file) {
String path = storageService.save(file);
UploadResVo resVo = new UploadResVo();
resVo.setPath(path);
return Result.ok(resVo);
}
@PostMapping(value = "/uploadAntd", produces = MediaType.APPLICATION_JSON_VALUE)
public Result<UploadResVo> uploadAntd(MultipartFile file) {
String path = storageService.save(file);
UploadResVo resVo = new UploadResVo();
resVo.setPath(path);
return Result.ok(resVo);
}
@PostMapping(value = "/uploadPrePath",produces = MediaType.APPLICATION_JSON_VALUE)
@SaIgnore
public Result<UploadResVo> uploadPrePath(MultipartFile file, String prePath) {
String path = storageService.save(file,prePath);
UploadResVo resVo = new UploadResVo();
resVo.setPath(path);
return Result.ok(resVo);
}
}

View File

@@ -0,0 +1,195 @@
package tech.easyflow.admin.controller.datacenter;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.collection.CollectionUtil;
import cn.idev.excel.EasyExcel;
import cn.idev.excel.FastExcel;
import com.alibaba.fastjson2.JSONObject;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.row.Row;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.DatacenterQuery;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.datacenter.entity.DatacenterTable;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.entity.vo.HeaderVo;
import tech.easyflow.datacenter.excel.ReadDataListener;
import tech.easyflow.datacenter.excel.ReadResVo;
import tech.easyflow.datacenter.service.DatacenterTableFieldService;
import tech.easyflow.datacenter.service.DatacenterTableService;
import javax.annotation.Resource;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 数据中枢表 控制层。
*
* @author ArkLight
* @since 2025-07-10
*/
@RestController
@RequestMapping("/api/v1/datacenterTable")
public class DatacenterTableController extends BaseCurdController<DatacenterTableService, DatacenterTable> {
@Resource
private DatacenterTableFieldService fieldsService;
public DatacenterTableController(DatacenterTableService service) {
super(service);
}
@Override
protected Result onSaveOrUpdateBefore(DatacenterTable entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModifiedBy(loginUser.getId());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
@PostMapping("/saveTable")
@SaCheckPermission("/api/v1/datacenterTable/save")
public Result<Void> saveTable(@RequestBody DatacenterTable entity) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
List<DatacenterTableField> fields = entity.getFields();
if (CollectionUtil.isEmpty(fields)) {
return Result.fail(99, "字段不能为空");
}
BigInteger id = entity.getId();
if (id == null) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
service.saveTable(entity, loginUser);
return Result.ok();
}
@GetMapping("/detailInfo")
@SaCheckPermission("/api/v1/datacenterTable/query")
public Result<DatacenterTable> detailInfo(BigInteger tableId) {
DatacenterTable table = service.getById(tableId);
QueryWrapper wrapper = QueryWrapper.create();
wrapper.eq(DatacenterTableField::getTableId, tableId);
wrapper.orderBy("id");
List<DatacenterTableField> fields = fieldsService.list(wrapper);
table.setFields(fields);
return Result.ok(table);
}
@GetMapping("/removeTable")
@SaCheckPermission("/api/v1/datacenterTable/remove")
public Result<Void> removeTable(BigInteger tableId) {
service.removeTable(tableId);
return Result.ok();
}
@GetMapping("/getHeaders")
@SaCheckPermission("/api/v1/datacenterTable/query")
public Result<List<HeaderVo>> getHeaders(BigInteger tableId) {
List<HeaderVo> res = service.getHeaders(tableId);
return Result.ok(res);
}
@GetMapping("/getPageData")
@SaCheckPermission("/api/v1/datacenterTable/query")
public Result<Page<Row>> getPageData(DatacenterQuery where) {
Page<Row> res = service.getPageData(where);
return Result.ok(res);
}
@PostMapping("/saveValue")
@SaCheckPermission("/api/v1/datacenterTable/save")
public Result<Void> saveValue(@RequestParam Map<String, Object> map) {
JSONObject object = new JSONObject(map);
BigInteger tableId = object.getBigInteger("tableId");
LoginAccount account = SaTokenUtil.getLoginAccount();
if (tableId == null) {
return Result.fail(99, "参数错误");
}
service.saveValue(tableId, object, account);
return Result.ok();
}
@PostMapping("/removeValue")
@SaCheckPermission("/api/v1/datacenterTable/remove")
public Result<Void> removeValue(@RequestParam Map<String, Object> map) {
JSONObject object = new JSONObject(map);
BigInteger tableId = object.getBigInteger("tableId");
BigInteger id = object.getBigInteger("id");
if (tableId == null || id == null) {
return Result.fail(99, "参数错误");
}
LoginAccount account = SaTokenUtil.getLoginAccount();
service.removeValue(tableId, id, account);
return Result.ok();
}
/**
* 导入数据
*/
@PostMapping("/importData")
@SaCheckPermission("/api/v1/datacenterTable/save")
public Result<ReadResVo> importData(MultipartFile file, @RequestParam Map<String, Object> map) throws Exception {
Object tableId = map.get("tableId");
DatacenterTable record = service.getById(tableId.toString());
if (record == null) {
throw new RuntimeException("数据表不存在");
}
InputStream is = file.getInputStream();
List<DatacenterTableField> fields = service.getFields(record.getId());
LoginAccount account = SaTokenUtil.getLoginAccount();
ReadDataListener listener = new ReadDataListener(record.getId(), fields, account);
FastExcel.read(is, listener)
.sheet()
.doRead();
int totalCount = listener.getTotalCount();
int errorCount = listener.getErrorCount();
int successCount = listener.getSuccessCount();
List<JSONObject> errorRows = listener.getErrorRows();
return Result.ok(new ReadResVo(successCount, errorCount, totalCount, errorRows));
}
@GetMapping("/getTemplate")
public void getTemplate(BigInteger tableId, HttpServletResponse response) throws Exception {
List<DatacenterTableField> fields = service.getFields(tableId);
// 设置响应内容类型
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
// 设置文件名
String fileName = URLEncoder.encode("导入模板", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
// 动态表头数据
List<List<String>> headList = new ArrayList<>();
for (DatacenterTableField field : fields) {
List<String> head = new ArrayList<>();
head.add(field.getFieldName());
headList.add(head);
}
// 写入Excel
EasyExcel.write(response.getOutputStream())
.head(headList)
.sheet("模板")
.doWrite(new ArrayList<>()); // 写入空数据,只生成模板
}
}

View File

@@ -0,0 +1,41 @@
package tech.easyflow.admin.controller.datacenter;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.datacenter.entity.DatacenterTableField;
import tech.easyflow.datacenter.service.DatacenterTableFieldService;
import java.util.Date;
/**
* 控制层。
*
* @author ArkLight
* @since 2025-07-10
*/
@RestController
@RequestMapping("/api/v1/datacenterTableFields")
@UsePermission(moduleName = "/api/v1/datacenterTable")
public class DatacenterTableFieldsController extends BaseCurdController<DatacenterTableFieldService, DatacenterTableField> {
public DatacenterTableFieldsController(DatacenterTableFieldService service) {
super(service);
}
@Override
protected Result onSaveOrUpdateBefore(DatacenterTableField entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
}

View File

@@ -0,0 +1,91 @@
package tech.easyflow.admin.controller.job;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.date.DateUtil;
import org.quartz.CronExpression;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.constant.enums.EnumJobStatus;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.job.entity.SysJob;
import tech.easyflow.job.service.SysJobService;
import tech.easyflow.common.entity.LoginAccount;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 系统任务表 控制层。
*
* @author xiaoma
* @since 2025-05-20
*/
@RestController
@RequestMapping("/api/v1/sysJob")
public class SysJobController extends BaseCurdController<SysJobService, SysJob> {
public SysJobController(SysJobService service) {
super(service);
}
@GetMapping("/start")
@SaCheckPermission("/api/v1/sysJob/save")
public Result<Void> start(BigInteger id) {
SysJob sysJob = service.getById(id);
sysJob.setStatus(EnumJobStatus.RUNNING.getCode());
service.addJob(sysJob);
service.updateById(sysJob);
return Result.ok();
}
@GetMapping("/stop")
@SaCheckPermission("/api/v1/sysJob/save")
public Result<Void> stop(BigInteger id) {
SysJob sysJob = new SysJob();
sysJob.setId(id);
sysJob.setStatus(EnumJobStatus.STOP.getCode());
ArrayList<Serializable> ids = new ArrayList<>();
ids.add(id);
service.deleteJob(ids);
service.updateById(sysJob);
return Result.ok();
}
@GetMapping("/getNextTimes")
public Result<List<String>> getNextTimes(String cronExpression) throws Exception{
CronExpression ex = new CronExpression(cronExpression);
List<String> times = new ArrayList<>();
Date date = new Date();
for (int i = 0; i < 5; i++) {
Date next = ex.getNextValidTimeAfter(date);
times.add(DateUtil.formatDateTime(next));
date = next;
}
return Result.ok(times);
}
@Override
protected Result onSaveOrUpdateBefore(SysJob entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
service.deleteJob(ids);
return super.onRemoveBefore(ids);
}
}

View File

@@ -0,0 +1,35 @@
package tech.easyflow.admin.controller.job;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.job.entity.SysJobLog;
import tech.easyflow.job.service.SysJobLogService;
/**
* 系统任务日志 控制层。
*
* @author xiaoma
* @since 2025-05-20
*/
@RestController
@RequestMapping("/api/v1/sysJobLog")
@UsePermission(moduleName = "/api/v1/sysJob")
public class SysJobLogController extends BaseCurdController<SysJobLogService, SysJobLog> {
public SysJobLogController(SysJobLogService service) {
super(service);
}
@Override
protected Result onSaveOrUpdateBefore(SysJobLog entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
}

View File

@@ -0,0 +1,159 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.constant.enums.EnumAccountType;
import tech.easyflow.common.constant.enums.EnumDataStatus;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.util.StringUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.log.annotation.LogRecord;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.crypto.digest.BCrypt;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 用户表 控制层。
*
* @author ArkLight
* @since 2025-03-14
*/
@RestController("sysAccountController")
@RequestMapping("/api/v1/sysAccount")
public class SysAccountController extends BaseCurdController<SysAccountService, SysAccount> {
public SysAccountController(SysAccountService service) {
super(service);
}
@Override
@LogRecord("分页查询")
protected Page<SysAccount> queryPage(Page<SysAccount> page, QueryWrapper queryWrapper) {
return service.getMapper().paginateWithRelations(page, queryWrapper);
}
@Override
protected Result onSaveOrUpdateBefore(SysAccount entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
BigInteger tenantId = loginUser.getTenantId();
if (isSave) {
commonFiled(entity, loginUser.getId(), tenantId, loginUser.getDeptId());
// 查询用户名是否存在
// long count = Db.selectCount(SqlPrepare.COUNT_ACCOUNT_BY_UNI_KEY, entity.getLoginName(), tenantId);
QueryWrapper w = QueryWrapper.create();
w.eq(SysAccount::getLoginName, entity.getLoginName());
long count = service.count(w);
if (count > 0) {
return Result.fail(1, "用户名已存在");
}
String password = entity.getPassword();
if (StringUtil.hasText(password)) {
entity.setPassword(BCrypt.hashpw(password));
}
Integer status = entity.getStatus();
if (status == null) {
entity.setStatus(EnumDataStatus.AVAILABLE.getCode());
}
} else {
SysAccount record = service.getById(entity.getId());
// 如果修改了部门,就将用户踢下线,避免用户操作数据造成数据错误
if (record.getDeptId() != null && !record.getDeptId().equals(entity.getDeptId())) {
StpUtil.kickout(record.getId());
}
// 不让修改用户名/密码,浏览器记住密码有可能会带上来
entity.setLoginName(null);
entity.setPassword(null);
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
@Override
protected void onSaveOrUpdateAfter(SysAccount entity, boolean isSave) {
service.syncRelations(entity);
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
List<SysAccount> sysAccounts = service.listByIds(ids);
for (SysAccount account : sysAccounts) {
Integer accountType = account.getAccountType();
if (EnumAccountType.SUPER_ADMIN.getCode().equals(accountType)) {
return Result.fail(1, "不能删除超级管理员");
}
if (EnumAccountType.TENANT_ADMIN.getCode().equals(accountType)) {
return Result.fail(1, "不能删除租户管理员");
}
}
return super.onRemoveBefore(ids);
}
@GetMapping("/myProfile")
public Result<SysAccount> myProfile() {
LoginAccount account = SaTokenUtil.getLoginAccount();
SysAccount sysAccount = service.getById(account.getId());
return Result.ok(sysAccount);
}
@PostMapping("/updateProfile")
public Result<Void> updateProfile(@JsonBody SysAccount account) {
BigInteger loginAccountId = SaTokenUtil.getLoginAccount().getId();
SysAccount update = new SysAccount();
update.setId(loginAccountId);
update.setNickname(account.getNickname());
update.setMobile(account.getMobile());
update.setEmail(account.getEmail());
update.setAvatar(account.getAvatar());
update.setModified(new Date());
update.setModifiedBy(loginAccountId);
service.updateById(update);
return Result.ok();
}
/**
* 修改密码,用于修改用户自己的密码
*
* @param password 用户的旧密码
* @param newPassword 新密码
* @param confirmPassword 确认密码
*/
@PostMapping("/updatePassword")
public Result<Void> updatePassword(@JsonBody(value = "password", required = true) String password,
@JsonBody(value = "newPassword", required = true) String newPassword,
@JsonBody(value = "confirmPassword", required = true) String confirmPassword) {
BigInteger loginAccountId = SaTokenUtil.getLoginAccount().getId();
SysAccount record = service.getById(loginAccountId);
if (record == null) {
return Result.fail("修改失败");
}
String pwdDb = record.getPassword();
if (!BCrypt.checkpw(password, pwdDb)) {
return Result.fail(1, "密码不正确");
}
if (!newPassword.equals(confirmPassword)) {
return Result.fail(2, "两次密码不一致");
}
SysAccount update = new SysAccount();
update.setId(loginAccountId);
update.setPassword(BCrypt.hashpw(newPassword));
update.setModified(new Date());
update.setModifiedBy(loginAccountId);
service.updateById(update);
return Result.ok();
}
}

View File

@@ -0,0 +1,101 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.table.TableInfo;
import com.mybatisflex.core.table.TableInfoFactory;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.util.IdUtil;
import tech.easyflow.common.vo.PkVo;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.system.entity.SysApiKey;
import tech.easyflow.system.entity.SysApiKeyResourceMapping;
import tech.easyflow.system.service.SysApiKeyResourceMappingService;
import tech.easyflow.system.service.SysApiKeyService;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.List;
/**
* 控制层。
*
* @author wangGangQiang
* @since 2025-04-18
*/
@RestController
@RequestMapping("/api/v1/sysApiKey")
public class SysApiKeyController extends BaseCurdController<SysApiKeyService, SysApiKey> {
public SysApiKeyController(SysApiKeyService service) {
super(service);
}
@Resource
private SysApiKeyResourceMappingService sysApiKeyResourceMappingService;
/**
* 添加(保存)数据
*
* @return {@code Result.errorCode == 0} 添加成功,否则添加失败
*/
@PostMapping("/key/save")
@SaCheckPermission("/api/v1/sysApiKey/save")
public Result<PkVo> save() {
String apiKey = IdUtil.generateUUID();
SysApiKey entity = new SysApiKey();
entity.setApiKey(apiKey);
entity.setCreated(new Date());
entity.setStatus(1);
// 将Date转换为LocalDate
LocalDate localDate = new Date().toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
// 添加30天
LocalDate newLocalDate = localDate.plusDays(30);
// 转换回Date
Date expireDate = Date.from(newLocalDate.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
entity.setExpiredAt(expireDate);
LoginAccount loginAccount = SaTokenUtil.getLoginAccount();
commonFiled(entity,loginAccount.getId(),loginAccount.getTenantId(),loginAccount.getDeptId());
service.save(entity);
onSaveOrUpdateAfter(entity, true);
TableInfo tableInfo = TableInfoFactory.ofEntityClass(entity.getClass());
Object[] pkArgs = tableInfo.buildPkSqlArgs(entity);
return Result.ok(new PkVo(pkArgs));
}
@Override
protected void onSaveOrUpdateAfter(SysApiKey entity, boolean isSave) {
if (!isSave && entity.getPermissionIds() != null && !entity.getPermissionIds().isEmpty()) {
// 修改的时候绑定授权接口
sysApiKeyResourceMappingService.authInterface(entity);
}
}
@Override
@GetMapping("/page")
public Result<Page<SysApiKey>> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
Result<Page<SysApiKey>> pageResult = (Result<Page<SysApiKey>>) super.page(request, sortKey, sortType, pageNumber, pageSize);
Page<SysApiKey> data = pageResult.getData();
List<SysApiKey> records = data.getRecords();
records.forEach(record -> {
QueryWrapper queryWrapper = QueryWrapper.create().select(SysApiKeyResourceMapping::getApiKeyResourceId).eq(SysApiKeyResourceMapping::getApiKeyId, record.getId());
List<BigInteger> resourceIds = sysApiKeyResourceMappingService.listAs(queryWrapper, BigInteger.class);
record.setPermissionIds(resourceIds);
});
return pageResult;
}
}

View File

@@ -0,0 +1,23 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.system.entity.SysApiKeyResource;
import tech.easyflow.system.service.SysApiKeyResourceService;
/**
* 请求接口表 控制层。
*
* @author 12076
* @since 2025-12-01
*/
@RestController
@RequestMapping("/api/v1/sysApiKeyResourcePermission")
@UsePermission(moduleName = "/api/v1/sysApiKey")
public class SysApiKeyResourceController extends BaseCurdController<SysApiKeyResourceService, SysApiKeyResource> {
public SysApiKeyResourceController(SysApiKeyResourceService service) {
super(service);
}
}

View File

@@ -0,0 +1,23 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.system.entity.SysApiKeyResourceMapping;
import tech.easyflow.system.service.SysApiKeyResourceMappingService;
/**
* apikey-请求接口表 控制层。
*
* @author 12076
* @since 2025-12-01
*/
@RestController
@RequestMapping("/api/v1/sysApiKeyResourcePermissionRelationship")
@UsePermission(moduleName = "/api/v1/sysApiKey")
public class SysApiKeyResourceMappingController extends BaseCurdController<SysApiKeyResourceMappingService, SysApiKeyResourceMapping> {
public SysApiKeyResourceMappingController(SysApiKeyResourceMappingService service) {
super(service);
}
}

View File

@@ -0,0 +1,92 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.system.entity.SysAccount;
import tech.easyflow.system.entity.SysDept;
import tech.easyflow.system.service.SysAccountService;
import tech.easyflow.system.service.SysDeptService;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 部门表 控制层。
*
* @author ArkLight
* @since 2025-03-14
*/
@RestController("sysDeptController")
@RequestMapping("/api/v1/sysDept")
public class SysDeptController extends BaseCurdController<SysDeptService, SysDept> {
@Resource
private SysAccountService sysAccountService;
public SysDeptController(SysDeptService service) {
super(service);
}
@Override
protected String getDefaultOrderBy() {
return "sort_no asc";
}
@Override
@GetMapping("list")
public Result<List<SysDept>> list(SysDept entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<SysDept> sysMenus = service.list(queryWrapper);
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
}
@Override
protected Result onSaveOrUpdateBefore(SysDept entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
BigInteger parentId = entity.getParentId();
if (parentId.equals(BigInteger.ZERO)) {
entity.setAncestors(parentId.toString());
} else {
SysDept parent = service.getById(parentId);
entity.setAncestors(parent.getAncestors() + "," + parentId);
}
if (isSave) {
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
List<SysDept> records = service.listByIds(ids);
for (SysDept dept : records) {
if (Constants.ROOT_DEPT.equals(dept.getDeptCode())) {
return Result.fail(1, "无法删除根部门");
}
}
QueryWrapper w = QueryWrapper.create();
w.in(SysAccount::getDeptId, ids);
long count = sysAccountService.count(w);
if (count > 0) {
return Result.fail(1, "该部门下有员工,不能删除");
}
return super.onRemoveBefore(ids);
}
}

View File

@@ -0,0 +1,47 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.util.SpringContextUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.dict.DictManager;
import tech.easyflow.system.entity.SysDict;
import tech.easyflow.system.service.SysDictService;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.Serializable;
import java.util.Collection;
import java.util.List;
/**
* 系统配置表 控制层。
*
* @author michael
* @since 2024-03-05
*/
@RestController
@RequestMapping("/api/v1/sysDict")
public class SysDictController extends BaseCurdController<SysDictService, SysDict> {
public SysDictController(SysDictService service) {
super(service);
}
@Override
protected void onSaveOrUpdateAfter(SysDict entity, boolean isSave) {
DictManager dictManager = SpringContextUtil.getBean(DictManager.class);
dictManager.putLoader(entity.buildLoader());
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
List<SysDict> sysDicts = service.list(QueryWrapper.create().in("id", ids));
if (sysDicts != null) {
DictManager dictManager = SpringContextUtil.getBean(DictManager.class);
sysDicts.forEach(sysDict -> dictManager.removeLoader(sysDict.getCode()));
}
return null;
}
}

View File

@@ -0,0 +1,23 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.annotation.UsePermission;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.system.entity.SysDictItem;
import tech.easyflow.system.service.SysDictItemService;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 数据字典内容 控制层。
*
* @author michael
* @since 2024-03-06
*/
@RestController
@RequestMapping("/api/v1/sysDictItem")
@UsePermission(moduleName = "/api/v1/sysDict")
public class SysDictItemController extends BaseCurdController<SysDictItemService, SysDictItem> {
public SysDictItemController(SysDictItemService service) {
super(service);
}
}

View File

@@ -0,0 +1,27 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.util.IdUtil;
@RestController
@RequestMapping("/api/v1/token")
public class SysGenerateTokenController {
// 手动生成 Token 并绑定账号
@GetMapping("/generateToken")
public SaResult generateToken() {
long loginId = StpUtil.getLoginIdAsLong(); // 假设这是你要绑定的账号ID
String customToken = IdUtil.generateUUID();; // 自定义的 Token 字符串
SaLoginModel saLoginModel = new SaLoginModel();
saLoginModel.setToken(customToken);
saLoginModel.setTimeout(-1);
// 将 loginId 与 customToken 关联,并设置有效期(单位:秒)
StpUtil.createLoginSession(loginId, saLoginModel); // 24小时有效期
System.out.println("生成了token: " + customToken);
return SaResult.ok("Token 已生成").setData(customToken);
}
}

View File

@@ -0,0 +1,34 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.log.annotation.LogRecord;
import tech.easyflow.system.entity.SysLog;
import tech.easyflow.system.service.SysLogService;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.relation.RelationManager;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
/**
* 操作日志表 控制层。
*
* @author michael
* @since 2024-03-06
*/
@RestController
@RequestMapping("/api/v1/sysLog")
public class SysLogController extends BaseCurdController<SysLogService, SysLog> {
public SysLogController(SysLogService service) {
super(service);
}
@Override
@LogRecord("分页查询")
protected Page<SysLog> queryPage(Page<SysLog> page, QueryWrapper queryWrapper) {
RelationManager.setQueryRelations(Collections.singleton("account"));
return service.getMapper().paginateWithRelations(page, queryWrapper);
}
}

View File

@@ -0,0 +1,161 @@
package tech.easyflow.admin.controller.system;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.tree.Tree;
import tech.easyflow.common.vo.MenuVo;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.system.entity.SysMenu;
import tech.easyflow.system.entity.SysRoleMenu;
import tech.easyflow.system.service.SysAccountRoleService;
import tech.easyflow.system.service.SysMenuService;
import tech.easyflow.system.service.SysRoleMenuService;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 菜单表 控制层。
*
* @author ArkLight
* @since 2025-03-14
*/
@RestController("sysMenuController")
@RequestMapping("/api/v1/sysMenu")
public class SysMenuController extends BaseCurdController<SysMenuService, SysMenu> {
@Resource
private SysRoleMenuService sysRoleMenuService;
@Resource
private SysAccountRoleService sysAccountRoleService;
public SysMenuController(SysMenuService service) {
super(service);
}
@Override
protected String getDefaultOrderBy() {
return "sort_no asc";
}
@Override
@GetMapping("list")
public Result<List<SysMenu>> list(SysMenu entity, Boolean asTree, String sortKey, String sortType) {
QueryWrapper queryWrapper = QueryWrapper.create(entity, buildOperators(entity));
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
List<SysMenu> sysMenus = service.list(queryWrapper);
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
}
@GetMapping("tree")
public Result<List<SysMenu>> tree(SysMenu entity) {
LoginAccount account = SaTokenUtil.getLoginAccount();
BigInteger accountId = account.getId();
List<SysMenu> sysMenus = service.getMenusByAccountId(entity,accountId);
return Result.ok(Tree.tryToTree(sysMenus, "id", "parentId"));
}
@GetMapping("treeV2")
public Result<List<MenuVo>> treeV2(SysMenu entity) {
LoginAccount account = SaTokenUtil.getLoginAccount();
BigInteger accountId = account.getId();
List<SysMenu> sysMenus = service.getMenusByAccountId(entity,accountId);
List<MenuVo> menuVos = buildMenuVos(sysMenus);
return Result.ok(Tree.tryToTree(menuVos, "id", "parentId"));
}
/**
* 根据角色id获取菜单树
*/
@GetMapping("getCheckedByRoleId/{roleId}")
public Result<List<BigInteger>> getCheckedByRoleId(@PathVariable BigInteger roleId) {
QueryWrapper rmWrapper = QueryWrapper.create();
rmWrapper.eq("role_id", roleId);
List<SysRoleMenu> list = sysRoleMenuService.list(rmWrapper);
List<BigInteger> menuIds = list.stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
if (CollectionUtil.isEmpty(menuIds)) {
return Result.ok(new ArrayList<>());
}
QueryWrapper wrapper = QueryWrapper.create();
wrapper.in(SysMenu::getId,menuIds);
List<SysMenu> sysMenus = service.list(wrapper);
return Result.ok(sysMenus.stream().map(SysMenu::getId).collect(Collectors.toList()));
}
/**
* 保存后,给超级管理员角色添加权限
*/
@Override
protected void onSaveOrUpdateAfter(SysMenu entity, boolean isSave) {
if (isSave) {
SysRoleMenu admin = new SysRoleMenu();
admin.setRoleId(Constants.SUPER_ADMIN_ROLE_ID);
admin.setMenuId(entity.getId());
sysRoleMenuService.save(admin);
}
}
/**
* 删除后,删除角色菜单对应关系
*/
@Override
protected void onRemoveAfter(Collection<Serializable> ids) {
QueryWrapper w = QueryWrapper.create();
w.in(SysRoleMenu::getMenuId, ids);
sysRoleMenuService.remove(w);
}
@Override
protected Result onSaveOrUpdateBefore(SysMenu entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity,loginUser.getId(),loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
private List<MenuVo> buildMenuVos(List<SysMenu> sysMenus) {
List<MenuVo> menuVos = new ArrayList<>();
for (SysMenu sysMenu : sysMenus) {
if (sysMenu.getIsShow() != 1) {
continue;
}
if (sysMenu.getMenuType() == 1) {
continue;
}
MenuVo menuVo = new MenuVo();
menuVo.setId(sysMenu.getId());
menuVo.setParentId(sysMenu.getParentId());
MenuVo.MetaVo metaVo = new MenuVo.MetaVo();
metaVo.setTitle(sysMenu.getMenuTitle());
metaVo.setIcon(sysMenu.getMenuIcon());
metaVo.setOrder(sysMenu.getSortNo());
menuVo.setMeta(metaVo);
menuVo.setName(sysMenu.getId().toString());
menuVo.setPath(sysMenu.getMenuUrl());
menuVo.setComponent(sysMenu.getComponent());
menuVos.add(menuVo);
}
return menuVos;
}
}

View File

@@ -0,0 +1,84 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.options.SysOptions;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseController;
import tech.easyflow.common.web.exceptions.BusinessException;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.entity.SysOption;
import tech.easyflow.system.service.SysOptionService;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 系统配置信息表。 控制层。
*
* @author michael
* @since 2024-03-13
*/
@RestController
@RequestMapping("/api/v1/sysOption")
public class SysOptionController extends BaseController {
@Resource
private SysOptionService service;
@GetMapping("/list")
public Result<Map<String, Object>> list(String[] keys) {
Map<String, Object> data = new HashMap<>();
if (keys == null || keys.length == 0) {
return Result.ok(data);
}
List<SysOption> list = service.list(QueryWrapper.create().in(SysOption::getKey, (Object[]) keys));
for (SysOption sysOption : list) {
data.put(sysOption.getKey(), sysOption.getValue());
}
return Result.ok(data);
}
@PostMapping("/save")
public Result<Void> save(@JsonBody Map<String, String> map) {
if (map == null || map.isEmpty()) {
return Result.ok();
}
map.forEach(SysOptions::set);
return Result.ok();
}
@PostMapping("/saveOption")
@SaCheckPermission("/api/v1/sysOption/save")
public Result<Void> saveOption(@JsonBody SysOption sysOption) {
String key = sysOption.getKey();
if (key == null || key.isEmpty()) {
throw new BusinessException("key is empty");
}
sysOption.setTenantId(SaTokenUtil.getLoginAccount().getTenantId());
SysOption record = service.getByOptionKey(key);
if (record == null) {
service.save(sysOption);
} else {
QueryWrapper w = QueryWrapper.create();
w.eq(SysOption::getKey, key);
service.update(sysOption, w);
}
return Result.ok();
}
@GetMapping("/getByKey")
public Result<SysOption> getByKey(String key) {
if (key == null || key.isEmpty()) {
throw new BusinessException("key is empty");
}
return Result.ok(service.getByOptionKey(key));
}
}

View File

@@ -0,0 +1,132 @@
package tech.easyflow.admin.controller.system;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.util.StringUtil;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.entity.SysPosition;
import tech.easyflow.system.service.SysPositionService;
import java.util.Date;
import java.util.Map;
import static tech.easyflow.system.entity.table.SysPositionTableDef.SYS_POSITION;
/**
* 职位表 控制层。
* <p>
* 提供岗位的增删改查及状态管理功能。
* </p>
*
* @author ArkLight
* @since 2025-03-14
*/
@RestController("sysPositionController")
@RequestMapping("/api/v1/sysPosition")
public class SysPositionController extends BaseCurdController<SysPositionService, SysPosition> {
public SysPositionController(SysPositionService service) {
super(service);
}
/**
* 分页查询岗位列表
* <p>
* 支持按岗位名称模糊查询,按状态、编码精确查询。
* </p>
*
* @param request 请求对象
* @param sortKey 排序字段
* @param sortType 排序类型 (asc/desc)
* @param pageNumber 当前页码
* @param pageSize 每页条数
* @return 分页结果
*/
@Override
@GetMapping("page")
public Result<Page<SysPosition>> page(HttpServletRequest request, String sortKey, String sortType, Long pageNumber, Long pageSize) {
if (pageNumber == null || pageNumber < 1) {
pageNumber = 1L;
}
if (pageSize == null || pageSize < 1) {
pageSize = 10L;
}
// 构建自定义查询条件
QueryWrapper queryWrapper = QueryWrapper.create()
.select(SYS_POSITION.ALL_COLUMNS)
.from(SYS_POSITION);
// 获取查询参数
String positionName = request.getParameter("positionName");
String positionCode = request.getParameter("positionCode");
String status = request.getParameter("status");
// 岗位名称 - 模糊查询
if (StringUtil.hasText(positionName)) {
queryWrapper.where(SYS_POSITION.POSITION_NAME.like(positionName));
}
// 岗位编码 - 精确查询
if (StringUtil.hasText(positionCode)) {
queryWrapper.where(SYS_POSITION.POSITION_CODE.eq(positionCode));
}
// 状态 - 精确查询
if (StringUtil.hasText(status)) {
queryWrapper.where(SYS_POSITION.STATUS.eq(status));
}
// 处理排序
queryWrapper.orderBy(buildOrderBy(sortKey, sortType, getDefaultOrderBy()));
return Result.ok(service.page(new Page<>(pageNumber, pageSize), queryWrapper));
}
/**
* 修改岗位状态(启用/禁用)
*
* @param body 包含 id 和 status 的 JSON 对象
* @return 操作结果
*/
@PostMapping("changeStatus")
public Result<?> changeStatus(@RequestBody Map<String, Object> body) {
String idStr = (String) body.get("id");
Integer status = (Integer) body.get("status");
if (StringUtil.noText(idStr) || status == null) {
return Result.fail("参数错误id 和 status 不能为空");
}
SysPosition position = new SysPosition();
position.setId(new java.math.BigInteger(idStr));
position.setStatus(status);
// 设置修改信息
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
position.setModified(new Date());
position.setModifiedBy(loginUser.getId());
boolean success = service.updateById(position);
return success ? Result.ok() : Result.fail("状态修改失败");
}
@Override
protected Result onSaveOrUpdateBefore(SysPosition entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), entity.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
}

View File

@@ -0,0 +1,118 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.web.bind.annotation.*;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.common.web.jsonbody.JsonBody;
import tech.easyflow.system.entity.SysRole;
import tech.easyflow.system.entity.SysRoleDept;
import tech.easyflow.system.entity.SysRoleMenu;
import tech.easyflow.system.service.SysRoleDeptService;
import tech.easyflow.system.service.SysRoleMenuService;
import tech.easyflow.system.service.SysRoleService;
import javax.annotation.Resource;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* 系统角色 控制层。
*
* @author ArkLight
* @since 2025-03-14
*/
@RestController("sysRoleController")
@RequestMapping("/api/v1/sysRole/")
public class SysRoleController extends BaseCurdController<SysRoleService, SysRole> {
@Resource
private SysRoleMenuService sysRoleMenuService;
@Resource
private SysRoleDeptService sysRoleDeptService;
public SysRoleController(SysRoleService service) {
super(service);
}
@PostMapping("saveRoleMenu/{roleId}")
@SaCheckPermission("/api/v1/sysRole/save")
@Deprecated
public Result<Void> saveRoleMenu(@PathVariable("roleId") BigInteger roleId, @JsonBody List<String> keys) {
service.saveRoleMenu(roleId, keys);
return Result.ok();
}
/**
* 获取角色菜单id
*/
@GetMapping("/getRoleMenuIds")
@SaCheckPermission("/api/v1/sysRole/query")
public Result<List<BigInteger>> getRoleMenuIds(BigInteger roleId) {
QueryWrapper w = QueryWrapper.create();
w.eq("role_id", roleId);
List<BigInteger> res = sysRoleMenuService.list(w).stream().map(SysRoleMenu::getMenuId).collect(Collectors.toList());
return Result.ok(res);
}
/**
* 获取角色部门id
*/
@GetMapping("/getRoleDeptIds")
@SaCheckPermission("/api/v1/sysRole/query")
public Result<List<BigInteger>> getRoleDeptIds(BigInteger roleId) {
QueryWrapper w = QueryWrapper.create();
w.eq("role_id", roleId);
List<BigInteger> res = sysRoleDeptService.list(w).stream().map(SysRoleDept::getDeptId).collect(Collectors.toList());
return Result.ok(res);
}
/**
* 保存角色
*/
@PostMapping("saveRole")
@SaCheckPermission("/api/v1/sysRole/save")
public Result<Void> saveRole(@JsonBody SysRole entity) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (entity.getId() == null) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
}
service.saveRole(entity);
return Result.ok();
}
@Override
protected Result onSaveOrUpdateBefore(SysRole entity, boolean isSave) {
LoginAccount loginUser = SaTokenUtil.getLoginAccount();
if (isSave) {
commonFiled(entity, loginUser.getId(), loginUser.getTenantId(), loginUser.getDeptId());
} else {
entity.setModified(new Date());
entity.setModifiedBy(loginUser.getId());
}
return null;
}
@Override
protected Result onRemoveBefore(Collection<Serializable> ids) {
List<SysRole> sysRoles = service.listByIds(ids);
for (SysRole sysRole : sysRoles) {
String roleKey = sysRole.getRoleKey();
if (Constants.SUPER_ADMIN_ROLE_CODE.equals(roleKey)) {
return Result.fail(1, "超级管理员角色不能删除");
}
if (Constants.TENANT_ADMIN_ROLE_CODE.equals(roleKey)) {
return Result.fail(1, "租户管理员角色不能删除");
}
}
return super.onRemoveBefore(ids);
}
}

View File

@@ -0,0 +1,31 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.constant.Constants;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.entity.LoginAccount;
import java.math.BigInteger;
@RestController
@RequestMapping("/api/temp-token")
public class SysTempTokenController {
@GetMapping("/create")
@SaIgnore
public Result<String> createTempToken() {
StpUtil.login(0);
String tokenValue = StpUtil.getTokenValue();
LoginAccount loginAccount = new LoginAccount();
loginAccount.setId(BigInteger.valueOf(0));
loginAccount.setLoginName("匿名用户");
StpUtil.getSession().set(Constants.LOGIN_USER_KEY, loginAccount);
return Result.ok("", tokenValue);
}
}

View File

@@ -0,0 +1,36 @@
package tech.easyflow.admin.controller.system;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.easyflow.common.domain.Result;
import tech.easyflow.common.web.controller.BaseCurdController;
import tech.easyflow.system.entity.SysUserFeedback;
import tech.easyflow.system.service.SysUserFeedbackService;
import java.math.BigInteger;
import java.util.Date;
/**
* 控制层。
*
* @author 12076
* @since 2025-12-30
*/
@RestController
@RequestMapping("/api/v1/sysUserFeedback")
public class SysUserFeedbackController extends BaseCurdController<SysUserFeedbackService, SysUserFeedback> {
public SysUserFeedbackController(SysUserFeedbackService service) {
super(service);
}
@Override
protected Result<?> onSaveOrUpdateBefore(SysUserFeedback entity, boolean isSave) {
if (!isSave) {
entity.setHandlerId(new BigInteger(StpUtil.getLoginIdAsString()));
entity.setModified(new Date());
entity.setHandleTime(new Date());
}
return super.onSaveOrUpdateBefore(entity, isSave);
}
}