init
This commit is contained in:
27
ruoyi-modules/pom.xml
Normal file
27
ruoyi-modules/pom.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>bird_monitoring</artifactId>
|
||||
<groupId>org.dromara</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<description>
|
||||
ruoyi-modules 业务模块
|
||||
</description>
|
||||
|
||||
<modules>
|
||||
<module>ruoyi-demo</module>
|
||||
<module>ruoyi-gen</module>
|
||||
<module>ruoyi-job</module>
|
||||
<module>ruoyi-system</module>
|
||||
<module>ruoyi-workflow</module>
|
||||
<module>ruoyi-ai</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
||||
48
ruoyi-modules/ruoyi-ai/pom.xml
Normal file
48
ruoyi-modules/ruoyi-ai/pom.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>ruoyi-ai</artifactId>
|
||||
|
||||
<description>
|
||||
ai模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 核心模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
<!-- api模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-api</artifactId>
|
||||
</dependency>
|
||||
<!-- ai模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-ai</artifactId>
|
||||
</dependency>
|
||||
<!-- satoken -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-satoken</artifactId>
|
||||
</dependency>
|
||||
<!-- web服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
package org.dromara.ai.controller;
|
||||
|
||||
import com.aizuda.snail.ai.common.execption.SnailAiException;
|
||||
import com.aizuda.snail.ai.common.model.Result;
|
||||
import com.aizuda.snail.ai.common.openapi.dto.OpenApiUserRegisterRequest;
|
||||
import com.aizuda.snail.ai.common.openapi.dto.OpenApiUserVO;
|
||||
import com.aizuda.snail.ai.openapi.client.core.api.OpenApiUserClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.satoken.utils.LoginHelper;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.system.api.model.LoginUser;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Snail AI OpenAPI 控制器
|
||||
*
|
||||
* @author opensnail
|
||||
* @date 2026-04-25
|
||||
*/
|
||||
@Slf4j
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/snail-ai")
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(prefix = "snail-ai.open-api", name = "enabled", havingValue = "true")
|
||||
public class SnailAiController extends BaseController {
|
||||
|
||||
/**
|
||||
* Snail AI 成功状态码。
|
||||
*/
|
||||
private static final int SNAIL_AI_SUCCESS = 1;
|
||||
private final OpenApiUserClient userClient;
|
||||
|
||||
/**
|
||||
* 注册当前登录用户并返回 OpenAPI 用户信息。
|
||||
*/
|
||||
@PostMapping("/user/register")
|
||||
public R<OpenApiUserVO> registerCurrentUser() {
|
||||
return R.ok(ensureOpenApiUser());
|
||||
}
|
||||
|
||||
/**
|
||||
* 确保当前登录用户已注册为 OpenAPI 用户。
|
||||
*/
|
||||
private OpenApiUserVO ensureOpenApiUser() {
|
||||
Long userId = LoginHelper.getUserId();
|
||||
LoginUser loginUser = LoginHelper.getLoginUser();
|
||||
if (loginUser == null || userId == null) {
|
||||
throw new SnailAiException("当前登录用户为空");
|
||||
}
|
||||
|
||||
OpenApiUserRegisterRequest registerRequest = new OpenApiUserRegisterRequest();
|
||||
registerRequest.setExternalId(String.valueOf(userId));
|
||||
registerRequest.setNickname(loginUser.getNickname());
|
||||
Result<OpenApiUserVO> registerResult = userClient.register(registerRequest);
|
||||
if (registerResult == null) {
|
||||
throw new SnailAiException("注册 OpenAPI 用户失败,返回为空");
|
||||
}
|
||||
if (registerResult.getStatus() != SNAIL_AI_SUCCESS) {
|
||||
throw new SnailAiException(registerResult.getMessage());
|
||||
}
|
||||
if (registerResult.getData() == null) {
|
||||
throw new SnailAiException("注册 OpenAPI 用户失败,返回为空");
|
||||
}
|
||||
return registerResult.getData();
|
||||
}
|
||||
}
|
||||
130
ruoyi-modules/ruoyi-demo/pom.xml
Normal file
130
ruoyi-modules/ruoyi-demo/pom.xml
Normal file
@@ -0,0 +1,130 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-demo</artifactId>
|
||||
|
||||
<description>
|
||||
demo模块
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- api模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-api</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 接口模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 短信模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-sms</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 邮件服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mail</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 缓存服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志记录 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- excel -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-excel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 安全模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- web服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 翻译模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-translation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 脱敏模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-sensitive</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库加解密模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-encrypt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- ES搜索引擎服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-elasticsearch</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 消息推送模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-push</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- mqtt模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mqtt</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- mcp模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mcp</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.demo.domain.Document;
|
||||
import org.dromara.demo.esmapper.DocumentMapper;
|
||||
import org.dromara.easyes.core.conditions.select.LambdaEsQueryWrapper;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 搜索引擎 crud 演示案例
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@ConditionalOnProperty(value = "easy-es.enable", havingValue = "true")
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/es")
|
||||
public class EsCrudController {
|
||||
|
||||
private final DocumentMapper documentMapper;
|
||||
|
||||
/**
|
||||
* 查询(指定)
|
||||
*
|
||||
* @param title 标题
|
||||
*/
|
||||
@GetMapping("/select")
|
||||
public Document select(String title) {
|
||||
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
|
||||
wrapper.eq(Document::getTitle, title);
|
||||
return documentMapper.selectOne(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 搜索(模糊)
|
||||
*
|
||||
* @param key 搜索关键字
|
||||
*/
|
||||
@GetMapping("/search")
|
||||
public List<Document> search(String key) {
|
||||
LambdaEsQueryWrapper<Document> wrapper = new LambdaEsQueryWrapper<>();
|
||||
wrapper.like(Document::getTitle, key);
|
||||
return documentMapper.selectList(wrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 插入
|
||||
*/
|
||||
@PostMapping("/insert")
|
||||
public Integer insert(@RequestBody Document document) {
|
||||
return documentMapper.insert(document);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
*/
|
||||
@PutMapping("/update")
|
||||
public R<Void> update(@RequestBody Document document) {
|
||||
// 测试更新 更新有两种情况 分别演示如下:
|
||||
// case1: 已知id, 根据id更新 (为了演示方便,此id是从上一步查询中复制过来的,实际业务可以自行查询)
|
||||
documentMapper.updateById(document);
|
||||
|
||||
// case2: id未知, 根据条件更新
|
||||
// LambdaEsUpdateWrapper<Document> wrapper = new LambdaEsUpdateWrapper<>();
|
||||
// wrapper.like(Document::getTitle, document.getTitle());
|
||||
// Document document2 = new Document();
|
||||
// document2.setTitle(document.getTitle());
|
||||
// document2.setContent(document.getContent());
|
||||
// documentMapper.update(document2, wrapper);
|
||||
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
*
|
||||
* @param id 主键
|
||||
*/
|
||||
@DeleteMapping("/delete/{id}")
|
||||
public R<Integer> delete(@PathVariable String id) {
|
||||
// 测试删除数据 删除有两种情况:根据id删或根据条件删
|
||||
return R.ok(documentMapper.deleteById(id));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.mail.core.MailBuilder;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
|
||||
|
||||
/**
|
||||
* 邮件发送案例
|
||||
*
|
||||
* @author Michelle.Chung
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/mail")
|
||||
public class MailSendController {
|
||||
|
||||
/**
|
||||
* 发送邮件
|
||||
*
|
||||
* @param to 接收人
|
||||
* @param subject 标题
|
||||
* @param text 内容
|
||||
*/
|
||||
@GetMapping("/sendSimpleMessage")
|
||||
public R<Void> sendSimpleMessage(String to, String subject, String text) {
|
||||
MailBuilder.of().to(to).subject(subject).text(text).send();
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件(带附件)
|
||||
*
|
||||
* @param to 接收人
|
||||
* @param subject 标题
|
||||
* @param text 内容
|
||||
*/
|
||||
@GetMapping("/sendMessageWithAttachment")
|
||||
public R<Void> sendMessageWithAttachment(String to, String subject, String text) {
|
||||
// 附件路径 禁止前端传递 有任意读取系统文件风险
|
||||
MailBuilder.of().to(to).subject(subject).text(text).files(new File("/xxx/xxx")).send();
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件(多附件)
|
||||
*
|
||||
* @param to 接收人
|
||||
* @param subject 标题
|
||||
* @param text 内容
|
||||
*/
|
||||
@GetMapping("/sendMessageWithAttachments")
|
||||
public R<Void> sendMessageWithAttachments(String to, String subject, String text) {
|
||||
// 附件路径 禁止前端传递 有任意读取系统文件风险
|
||||
String[] paths = new String[]{"/xxx/xxx", "/xxx/xxx"};
|
||||
File[] array = Arrays.stream(paths).map(File::new).toArray(File[]::new);
|
||||
MailBuilder.of().to(to).subject(subject).text(text).files(array).send();
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.mcp.core.McpResourceReadResult;
|
||||
import org.dromara.demo.mcp.McpDemoClientService;
|
||||
import org.dromara.demo.mcp.McpDemoClientService.McpDemoHandleResult;
|
||||
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 java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP Client 演示案例。
|
||||
* <p>
|
||||
* 这些接口不是 MCP 协议接口,只是为了在后台系统中手动触发 MCP Client 调用。
|
||||
* 真实 MCP Server 入口由 Spring AI 挂载到 `/mcp`。
|
||||
* <p>
|
||||
* 调用示例:
|
||||
* <ul>
|
||||
* <li>`GET /demo/mcp/tools`:查看已连接 MCP Server 的工具列表</li>
|
||||
* <li>`GET /demo/mcp/receive?toolName=demo_get_data&id=1`:调用外部工具并模拟业务处理</li>
|
||||
* <li>`GET /demo/mcp/resource?uri=demo://summary`:读取外部 MCP 资源</li>
|
||||
* </ul>
|
||||
* 当 `spring.ai.mcp.client.enabled=false` 时,接口会返回提示,不影响应用启动。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/mcp")
|
||||
public class McpDemoController {
|
||||
|
||||
private final McpDemoClientService mcpDemoClientService;
|
||||
|
||||
/**
|
||||
* 查询外部 MCP Server 工具列表。
|
||||
*/
|
||||
@GetMapping("/tools")
|
||||
public R<Map<String, List<String>>> tools() {
|
||||
try {
|
||||
return R.ok(mcpDemoClientService.listRemoteTools());
|
||||
} catch (IllegalStateException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用外部 MCP 工具并模拟业务处理。
|
||||
*/
|
||||
@GetMapping("/receive")
|
||||
public R<McpDemoHandleResult> receive(@RequestParam(defaultValue = "demo_get_data") String toolName,
|
||||
@RequestParam(defaultValue = "1") String id) {
|
||||
try {
|
||||
return R.ok(mcpDemoClientService.receiveAndHandle(toolName, Map.of("id", id)));
|
||||
} catch (IllegalStateException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取外部 MCP 资源。
|
||||
*/
|
||||
@GetMapping("/resource")
|
||||
public R<Map<String, McpResourceReadResult>> resource(@RequestParam(defaultValue = "demo://summary") String uri) {
|
||||
try {
|
||||
return R.ok(mcpDemoClientService.readRemoteResource(uri));
|
||||
} catch (IllegalStateException e) {
|
||||
return R.fail(e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.mica.mqtt.codec.MqttQoS;
|
||||
import org.dromara.mica.mqtt.codec.message.MqttPublishMessage;
|
||||
import org.dromara.mica.mqtt.core.annotation.MqttClientSubscribe;
|
||||
import org.dromara.mica.mqtt.core.deserialize.MqttJsonDeserializer;
|
||||
import org.dromara.mica.mqtt.spring.client.MqttClientTemplate;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* mqtt 演示案例
|
||||
* <p>
|
||||
* 用法文档 <a href="https://mica-mqtt.dreamlu.net/guide/spring/client.html">...</a>
|
||||
* 测试server搭建:
|
||||
* 可执行下载其他mqtt服务端搭建
|
||||
* 也可使用 mica自带的server搭建 <a href="https://mica-mqtt.dreamlu.net/guide/spring/server.html">...</a>
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/mqtt")
|
||||
@Slf4j
|
||||
public class MqttController {
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private MqttClientTemplate client;
|
||||
|
||||
/**
|
||||
* 发布一条 MQTT 测试消息。
|
||||
*
|
||||
* @return 是否发送成功
|
||||
*/
|
||||
@GetMapping("/send")
|
||||
public boolean send() {
|
||||
client.publish("/test/client", "测试测试".getBytes(StandardCharsets.UTF_8));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅 QoS0 测试主题并打印原始消息。
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param payload 消息内容
|
||||
*/
|
||||
@MqttClientSubscribe("/test/#")
|
||||
public void subQos0(String topic, byte[] payload) {
|
||||
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅 QoS1 测试主题并打印原始消息。
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param payload 消息内容
|
||||
*/
|
||||
@MqttClientSubscribe(value = "/qos1/#", qos = MqttQoS.QOS1)
|
||||
public void subQos1(String topic, byte[] payload) {
|
||||
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅带产品和设备占位符的注册主题。
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param payload 消息内容
|
||||
*/
|
||||
@MqttClientSubscribe("/sys/${productKey}/${deviceName}/thing/sub/register")
|
||||
public void thingSubRegister(String topic, byte[] payload) {
|
||||
// 1.3.8 开始支持,@MqttClientSubscribe 注解支持 ${} 变量替换,会默认替换成 +
|
||||
// 注意:mica-mqtt 会先从 Spring boot 配置中替换参数 ${},如果存在配置会优先被替换。
|
||||
log.info("topic:{} payload:{}", topic, new String(payload, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅 JSON 测试主题并演示自定义反序列化。
|
||||
*
|
||||
* @param topic 消息主题
|
||||
* @param message MQTT 原始消息
|
||||
* @param data 反序列化后的演示数据
|
||||
*/
|
||||
@MqttClientSubscribe(
|
||||
value = "/test/json",
|
||||
deserialize = MqttJsonDeserializer.class // 2.4.5 开始支持 自定义序列化,默认 json 序列化
|
||||
)
|
||||
public void testJson(String topic, MqttPublishMessage message, TestDemo data) {
|
||||
// 2.4.5 开始支持,支持 2 到 3 个参数,字段类型映射规则如下
|
||||
// String 字符串会默认映射到 topic,
|
||||
// MqttPublishMessage 会默认映射到 原始的消息,可以拿到 mqtt5 的 props 参数
|
||||
// byte[] 会映射到 mqtt 消息内容 payload
|
||||
// ByteBuffer 会映射到 mqtt 消息内容 payload
|
||||
// 其他类型会走序列化,确保消息能够序列化,默认为 json 序列化
|
||||
log.info("topic:{} json data:{}", topic, data);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.constant.CacheNames;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.CachePut;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* spring-cache 演示案例
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
// 类级别 缓存统一配置
|
||||
//@CacheConfig(cacheNames = CacheNames.DEMO_CACHE)
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/cache")
|
||||
public class RedisCacheController {
|
||||
|
||||
/**
|
||||
* 测试 @Cacheable
|
||||
* <p>
|
||||
* 表示这个方法有了缓存的功能,方法的返回值会被缓存下来
|
||||
* 下一次调用该方法前,会去检查是否缓存中已经有值
|
||||
* 如果有就直接返回,不调用方法
|
||||
* 如果没有,就调用方法,然后把结果缓存起来
|
||||
* 这个注解「一般用在查询方法上」
|
||||
* <p>
|
||||
* 重点说明: 缓存注解严谨与其他筛选数据功能一起使用
|
||||
* 例如: 数据权限注解 会造成 缓存击穿 与 数据不一致问题
|
||||
* <p>
|
||||
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
||||
*/
|
||||
@Cacheable(cacheNames = "demo:cache#60s#10m#20#1", key = "#key", condition = "#key != null")
|
||||
@GetMapping("/test1")
|
||||
public R<String> test1(String key, String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 @CachePut
|
||||
* <p>
|
||||
* 加了@CachePut注解的方法,会把方法的返回值put到缓存里面缓存起来,供其它地方使用
|
||||
* 它「通常用在新增或者实时更新方法上」
|
||||
* <p>
|
||||
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
||||
*/
|
||||
@CachePut(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null")
|
||||
@GetMapping("/test2")
|
||||
public R<String> test2(String key, String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试 @CacheEvict
|
||||
* <p>
|
||||
* 使用了CacheEvict注解的方法,会清空指定缓存
|
||||
* 「一般用在删除的方法上」
|
||||
* <p>
|
||||
* cacheNames 命名规则 查看 {@link CacheNames} 注释 支持多参数
|
||||
*/
|
||||
@CacheEvict(cacheNames = CacheNames.DEMO_CACHE, key = "#key", condition = "#key != null")
|
||||
@GetMapping("/test3")
|
||||
public R<String> test3(String key, String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试设置过期时间
|
||||
* 手动设置过期时间10秒
|
||||
* 11秒后获取 判断是否相等
|
||||
*/
|
||||
@GetMapping("/test6")
|
||||
public R<Boolean> test6(String key, String value) {
|
||||
RedisUtils.setCacheObject(key, value);
|
||||
boolean flag = RedisUtils.expire(key, Duration.ofSeconds(10));
|
||||
System.out.println("***********" + flag);
|
||||
ThreadUtil.sleep(11 * 1000);
|
||||
Object obj = RedisUtils.getCacheObject(key);
|
||||
return R.ok(value.equals(obj));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import com.baomidou.lock.LockInfo;
|
||||
import com.baomidou.lock.LockTemplate;
|
||||
import com.baomidou.lock.annotation.Lock4j;
|
||||
import com.baomidou.lock.executor.RedissonLockExecutor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.LocalTime;
|
||||
|
||||
|
||||
/**
|
||||
* 测试分布式锁的样例
|
||||
*
|
||||
* @author shenxinquan
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/demo/redisLock")
|
||||
public class RedisLockController {
|
||||
|
||||
@Autowired
|
||||
private LockTemplate lockTemplate;
|
||||
|
||||
/**
|
||||
* 测试lock4j 注解
|
||||
*/
|
||||
@Lock4j(keys = {"#key"})
|
||||
@GetMapping("/testLock4j")
|
||||
public R<String> testLock4j(String key, String value) {
|
||||
System.out.println("start:" + key + ",time:" + LocalTime.now());
|
||||
ThreadUtil.sleep(10000);
|
||||
System.out.println("end :" + key + ",time:" + LocalTime.now());
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试lock4j 工具
|
||||
*/
|
||||
@GetMapping("/testLock4jLockTemplate")
|
||||
public R<String> testLock4jLockTemplate(String key, String value) {
|
||||
final LockInfo lockInfo = lockTemplate.lock(key, 30000L, 5000L, RedissonLockExecutor.class);
|
||||
if (null == lockInfo) {
|
||||
throw new RuntimeException("业务处理中,请稍后再试");
|
||||
}
|
||||
// 获取锁成功,处理业务
|
||||
try {
|
||||
ThreadUtil.sleep(8000);
|
||||
System.out.println("执行简单方法1 , 当前线程:" + Thread.currentThread().getName());
|
||||
} finally {
|
||||
//释放锁
|
||||
lockTemplate.releaseLock(lockInfo);
|
||||
}
|
||||
//结束
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* Redis 发布订阅 演示案例
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/redis/pubsub")
|
||||
public class RedisPubSubController {
|
||||
|
||||
/**
|
||||
* 发布消息
|
||||
*
|
||||
* @param key 通道Key
|
||||
* @param value 发送内容
|
||||
*/
|
||||
@GetMapping("/pub")
|
||||
public R<Void> pub(String key, String value) {
|
||||
RedisUtils.publish(key, value, consumer -> {
|
||||
System.out.println("发布通道 => " + key + ", 发送值 => " + value);
|
||||
});
|
||||
return R.ok("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 订阅消息
|
||||
*
|
||||
* @param key 通道Key
|
||||
*/
|
||||
@GetMapping("/sub")
|
||||
public R<Void> sub(String key) {
|
||||
RedisUtils.subscribe(key, String.class, msg -> {
|
||||
System.out.println("订阅通道 => " + key + ", 接收值 => " + msg);
|
||||
});
|
||||
return R.ok("操作成功");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.redis.annotation.RateLimiter;
|
||||
import org.dromara.common.redis.enums.LimitType;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* 测试分布式限流样例
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/demo/rateLimiter")
|
||||
public class RedisRateLimiterController {
|
||||
|
||||
/**
|
||||
* 测试全局限流
|
||||
* 全局影响
|
||||
*/
|
||||
@RateLimiter(count = 2, time = 10)
|
||||
@GetMapping("/test")
|
||||
public R<String> test(String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试请求IP限流
|
||||
* 同一IP请求受影响
|
||||
*/
|
||||
@RateLimiter(count = 2, time = 10, limitType = LimitType.IP)
|
||||
@GetMapping("/testip")
|
||||
public R<String> testip(String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试集群实例限流
|
||||
* 启动两个后端服务互不影响
|
||||
*/
|
||||
@RateLimiter(count = 2, time = 10, limitType = LimitType.CLUSTER)
|
||||
@GetMapping("/testcluster")
|
||||
public R<String> testcluster(String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试请求IP限流(key基于参数获取)
|
||||
* 同一IP请求受影响
|
||||
* <p>
|
||||
* 简单变量获取 #变量 复杂表达式 #{#变量 != 1 ? 1 : 0}
|
||||
*/
|
||||
@RateLimiter(count = 2, time = 10, limitType = LimitType.IP, key = "#value")
|
||||
@GetMapping("/testObj")
|
||||
public R<String> testObj(String value) {
|
||||
return R.data(value);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.*;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* SaToken 权限测试 接口文档输出测试
|
||||
*
|
||||
* @author AprilWind
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/demo/saTokenDoc")
|
||||
public class SaTokenTestController {
|
||||
|
||||
// ====================== 基础场景:单一校验规则 ======================
|
||||
|
||||
/**
|
||||
* 场景1:仅登录校验(无角色/权限限制,只需登录态)
|
||||
*/
|
||||
@SaCheckLogin
|
||||
@GetMapping("/basic/loginOnly")
|
||||
public R<Void> loginOnly() {
|
||||
log.info("【场景1】仅登录校验通过");
|
||||
return R.ok("仅登录校验通过,无需角色/权限");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景2:单一角色校验(AND模式,默认)
|
||||
*/
|
||||
@SaCheckRole("admin")
|
||||
@GetMapping("/basic/singleRole")
|
||||
public R<Void> singleRole() {
|
||||
log.info("【场景2】单一角色(admin)校验通过");
|
||||
return R.ok("拥有admin角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景3:单一权限校验(AND模式,默认)
|
||||
*/
|
||||
@SaCheckPermission("system:user:view")
|
||||
@GetMapping("/basic/singlePermission")
|
||||
public R<Void> singlePermission() {
|
||||
log.info("【场景3】单一权限(system:user:view)校验通过");
|
||||
return R.ok("拥有system:user:view权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景4:忽略所有权限校验(SaIgnore优先级最高)
|
||||
*/
|
||||
@SaIgnore
|
||||
@SaCheckRole("none_exist") // 该注解会被忽略
|
||||
@GetMapping("/basic/ignoreAll")
|
||||
public R<Void> ignoreAll() {
|
||||
log.info("【场景4】SaIgnore忽略所有权限校验");
|
||||
return R.ok("SaIgnore生效,所有权限校验被忽略");
|
||||
}
|
||||
|
||||
// ====================== 进阶场景:多条件组合(AND/OR) ======================
|
||||
|
||||
/**
|
||||
* 场景5:多角色AND模式(必须同时拥有所有角色)
|
||||
*/
|
||||
@SaCheckRole(value = {"admin", "operator"}, mode = SaMode.AND)
|
||||
@GetMapping("/advance/multiRoleAnd")
|
||||
public R<Void> multiRoleAnd() {
|
||||
log.info("【场景5】多角色AND模式(admin+operator)校验通过");
|
||||
return R.ok("同时拥有admin和operator角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景6:多角色OR模式(拥有任一角色即可)
|
||||
*/
|
||||
@SaCheckRole(value = {"admin", "test"}, mode = SaMode.OR)
|
||||
@GetMapping("/advance/multiRoleOr")
|
||||
public R<Void> multiRoleOr() {
|
||||
log.info("【场景6】多角色OR模式(admin|test)校验通过");
|
||||
return R.ok("拥有admin或test角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景7:多权限AND模式(必须同时拥有所有权限)
|
||||
*/
|
||||
@SaCheckPermission(value = {"system:user:edit", "system:log:view"}, mode = SaMode.AND)
|
||||
@GetMapping("/advance/multiPermAnd")
|
||||
public R<Void> multiPermAnd() {
|
||||
log.info("【场景7】多权限AND模式(system:user:edit+system:log:view)校验通过");
|
||||
return R.ok("同时拥有system:user:edit和system:log:view权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景8:多权限OR模式(拥有任一权限即可)
|
||||
*/
|
||||
@SaCheckPermission(value = {"system:user:add", "system:user:delete"}, mode = SaMode.OR)
|
||||
@GetMapping("/advance/multiPermOr")
|
||||
public R<Void> multiPermOr() {
|
||||
log.info("【场景8】多权限OR模式(system:user:add|system:user:delete)校验通过");
|
||||
return R.ok("拥有system:user:add或system:user:delete权限,校验通过");
|
||||
}
|
||||
|
||||
// ====================== 高级场景:通配符/混合组合 ======================
|
||||
|
||||
/**
|
||||
* 场景9:权限通配符匹配(前缀匹配)
|
||||
* 拥有system:user:* 即可匹配所有用户模块权限
|
||||
*/
|
||||
@SaCheckPermission("system:user:*")
|
||||
@GetMapping("/advanced/permWildcardPrefix")
|
||||
public R<Void> permWildcardPrefix() {
|
||||
log.info("【场景9】权限通配符(system:user:*)校验通过");
|
||||
return R.ok("拥有system:user:*前缀权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景10:角色通配符匹配(前缀匹配)
|
||||
* 拥有admin_* 即可匹配所有admin开头的角色
|
||||
*/
|
||||
@SaCheckRole("admin_*")
|
||||
@GetMapping("/advanced/roleWildcardPrefix")
|
||||
public R<Void> roleWildcardPrefix() {
|
||||
log.info("【场景10】角色通配符(admin_*)校验通过");
|
||||
return R.ok("拥有admin_*前缀角色,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景11:权限+角色混合AND模式(所有条件必须满足)
|
||||
* 需同时满足:拥有admin角色 + 拥有system:user:all权限
|
||||
*/
|
||||
@SaCheckRole("admin")
|
||||
@SaCheckPermission("system:user:all")
|
||||
@GetMapping("/advanced/mixRolePermAnd")
|
||||
public R<Void> mixRolePermAnd() {
|
||||
log.info("【场景11】角色+权限混合AND(admin+system:user:all)校验通过");
|
||||
return R.ok("拥有admin角色且拥有system:user:all权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景12:权限+角色混合OR模式(任一条件满足即可)
|
||||
* 满足任一:拥有super_admin角色 | 拥有system:manage权限
|
||||
*/
|
||||
@SaCheckRole(value = {"super_admin"}, mode = SaMode.OR)
|
||||
@SaCheckPermission(value = {"system:manage"}, mode = SaMode.OR)
|
||||
@GetMapping("/advanced/mixRolePermOr")
|
||||
public R<Void> mixRolePermOr() {
|
||||
log.info("【场景12】角色+权限混合OR(super_admin|system:manage)校验通过");
|
||||
return R.ok("拥有super_admin角色或system:manage权限,校验通过");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景13:orRole参数(权限校验失败时,兜底角色校验)
|
||||
* 核心逻辑:无system:user:export权限时,检查是否有admin/operator角色
|
||||
*/
|
||||
@SaCheckPermission(value = "system:user:export", orRole = {"admin", "operator"})
|
||||
@GetMapping("/advanced/permWithOrRole")
|
||||
public R<Void> permWithOrRole() {
|
||||
log.info("【场景13】权限+orRole兜底校验通过");
|
||||
return R.ok("拥有system:user:export权限,或拥有admin/operator角色,校验通过");
|
||||
}
|
||||
|
||||
// ====================== 特殊场景:临时权限/注解覆盖 ======================
|
||||
|
||||
/**
|
||||
* 场景14:SaIgnore局部覆盖(方法注解覆盖类注解,若有)
|
||||
* 假设类上有@SaCheckLogin,方法上@SaIgnore会覆盖
|
||||
*/
|
||||
@SaIgnore
|
||||
@GetMapping("/special/ignoreOverride")
|
||||
public R<Void> ignoreOverride() {
|
||||
log.info("【场景14】SaIgnore覆盖类级别权限注解");
|
||||
return R.ok("方法级SaIgnore覆盖类级别权限校验");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景15:临时权限校验(SaCheckPermission逻辑:临时权限>永久权限)
|
||||
* 注:临时权限需通过SaToken API手动设置,如 SaHolder.getStpLogic().setTempPermission("system:temp:test")
|
||||
*/
|
||||
@SaCheckPermission("system:temp:test")
|
||||
@GetMapping("/special/tempPermission")
|
||||
public R<Void> tempPermission() {
|
||||
log.info("【场景15】临时权限(system:temp:test)校验通过");
|
||||
return R.ok("临时权限校验通过(需先通过API设置临时权限)");
|
||||
}
|
||||
|
||||
/**
|
||||
* 场景16:登录类型指定(多端登录场景,如PC/APP/小程序)
|
||||
* 注:需配合SaToken多账号体系配置
|
||||
*/
|
||||
@SaCheckLogin(type = "PC") // 仅校验PC端的登录态
|
||||
@GetMapping("/special/loginTypeSpecify")
|
||||
public R<Void> loginTypeSpecify() {
|
||||
log.info("【场景16】指定登录类型(PC)校验通过");
|
||||
return R.ok("仅PC端登录态校验通过");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.sms4j.api.SmsBlend;
|
||||
import org.dromara.sms4j.api.entity.SmsResponse;
|
||||
import org.dromara.sms4j.core.factory.SmsFactory;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
/**
|
||||
* 短信演示案例
|
||||
* 请先阅读文档 否则无法使用
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 4.2.0
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/sms")
|
||||
public class SmsController {
|
||||
/**
|
||||
* 发送短信Aliyun
|
||||
*
|
||||
* @param phones 电话号
|
||||
* @param templateId 模板ID
|
||||
*/
|
||||
@GetMapping("/sendAliyun")
|
||||
public R<Object> sendAliyun(String phones, String templateId) {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
map.put("code", "1234");
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
|
||||
return R.ok(smsResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送短信Tencent
|
||||
*
|
||||
* @param phones 电话号
|
||||
* @param templateId 模板ID
|
||||
*/
|
||||
@GetMapping("/sendTencent")
|
||||
public R<Object> sendTencent(String phones, String templateId) {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
|
||||
// map.put("2", "测试测试");
|
||||
map.put("1", "1234");
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config2");
|
||||
SmsResponse smsResponse = smsBlend.sendMessage(phones, templateId, map);
|
||||
return R.ok(smsResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加黑名单
|
||||
*
|
||||
* @param phone 手机号
|
||||
*/
|
||||
@GetMapping("/addBlacklist")
|
||||
public R<Object> addBlacklist(String phone) {
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
smsBlend.joinInBlacklist(phone);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除黑名单
|
||||
*
|
||||
* @param phone 手机号
|
||||
*/
|
||||
@GetMapping("/removeBlacklist")
|
||||
public R<Object> removeBlacklist(String phone) {
|
||||
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
|
||||
smsBlend.removeFromBlacklist(phone);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import org.dromara.common.core.domain.R;
|
||||
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.RequestPart;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* swagger3 用法示例
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/swagger/demo")
|
||||
public class Swagger3DemoController {
|
||||
|
||||
/**
|
||||
* 上传请求
|
||||
* 必须使用 @RequestPart 注解标注为文件
|
||||
*
|
||||
* @param file 文件
|
||||
*/
|
||||
@PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public R<String> upload(@RequestPart("file") MultipartFile file) {
|
||||
return R.data(file.getOriginalFilename());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.demo.mapper.TestDemoMapper;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试批量方法
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-05-30
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/batch")
|
||||
public class TestBatchController extends BaseController {
|
||||
|
||||
/**
|
||||
* 为了便于测试 直接引入mapper
|
||||
*/
|
||||
private final TestDemoMapper testDemoMapper;
|
||||
|
||||
/**
|
||||
* 新增批量方法 可完美替代 saveBatch 秒级插入上万数据 (对mysql负荷较大)
|
||||
* <p>
|
||||
* 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度
|
||||
*/
|
||||
@PostMapping("/add")
|
||||
// @DS("slave")
|
||||
public R<Void> add() {
|
||||
List<TestDemo> list = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
TestDemo testDemo = new TestDemo();
|
||||
testDemo.setOrderNum(-1);
|
||||
testDemo.setTestKey("批量新增");
|
||||
testDemo.setValue("测试新增");
|
||||
list.add(testDemo);
|
||||
}
|
||||
return toAjax(testDemoMapper.insertBatch(list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增或更新 可完美替代 saveOrUpdateBatch 高性能
|
||||
* <p>
|
||||
* 3.5.0 版本 增加 rewriteBatchedStatements=true 批处理参数 使 MP 原生批处理可以达到同样的速度
|
||||
*/
|
||||
@PostMapping("/addOrUpdate")
|
||||
// @DS("slave")
|
||||
public R<Void> addOrUpdate() {
|
||||
List<TestDemo> list = new ArrayList<>();
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
TestDemo testDemo = new TestDemo();
|
||||
testDemo.setOrderNum(-1);
|
||||
testDemo.setTestKey("批量新增");
|
||||
testDemo.setValue("测试新增");
|
||||
list.add(testDemo);
|
||||
}
|
||||
testDemoMapper.insertBatch(list);
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
TestDemo testDemo = list.get(i);
|
||||
testDemo.setTestKey("批量新增或修改");
|
||||
testDemo.setValue("批量新增或修改");
|
||||
if (i % 2 == 0) {
|
||||
testDemo.setId(null);
|
||||
}
|
||||
}
|
||||
return toAjax(testDemoMapper.insertOrUpdateBatch(list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除批量方法
|
||||
*/
|
||||
@DeleteMapping()
|
||||
// @DS("slave")
|
||||
public R<Void> remove() {
|
||||
return toAjax(testDemoMapper.lambda()
|
||||
.eq(TestDemo::getOrderNum, -1L)
|
||||
.deleteCount());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.core.validate.QueryGroup;
|
||||
import org.dromara.common.excel.core.ExcelResult;
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.demo.domain.bo.TestDemoBo;
|
||||
import org.dromara.demo.domain.vo.TestDemoImportVo;
|
||||
import org.dromara.demo.domain.vo.TestDemoVo;
|
||||
import org.dromara.demo.service.ITestDemoService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 测试单表Controller
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/demo")
|
||||
public class TestDemoController extends BaseController {
|
||||
|
||||
private final ITestDemoService testDemoService;
|
||||
|
||||
/**
|
||||
* 查询测试单表列表
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:list")
|
||||
@GetMapping("/list")
|
||||
public R<PageResult<TestDemoVo>> list(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
|
||||
return R.ok(testDemoService.queryPageList(bo, pageQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义分页查询
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:list")
|
||||
@GetMapping("/page")
|
||||
public R<PageResult<TestDemoVo>> page(@Validated(QueryGroup.class) TestDemoBo bo, PageQuery pageQuery) {
|
||||
return R.ok(testDemoService.customPageList(bo, pageQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入数据
|
||||
*
|
||||
* @param file 导入文件
|
||||
*/
|
||||
@Log(title = "测试单表", businessType = BusinessType.IMPORT)
|
||||
@SaCheckPermission("demo:demo:import")
|
||||
@PostMapping(value = "/importData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public R<Void> importData(@RequestPart("file") MultipartFile file) throws Exception {
|
||||
ExcelResult<TestDemoImportVo> excelResult = ExcelBuilder.read(file.getInputStream(), TestDemoImportVo.class)
|
||||
.validate(true)
|
||||
.doRead();
|
||||
List<TestDemo> list = MapstructUtils.convert(excelResult.getList(), TestDemo.class);
|
||||
testDemoService.saveBatch(list);
|
||||
return R.ok(excelResult.getAnalysis());
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出测试单表列表
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:export")
|
||||
@Log(title = "测试单表", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(@Validated TestDemoBo bo, HttpServletResponse response) {
|
||||
List<TestDemoVo> list = testDemoService.queryList(bo);
|
||||
// 测试雪花id导出
|
||||
// for (TestDemoVo vo : list) {
|
||||
// vo.setId(1234567891234567893L);
|
||||
// }
|
||||
ExcelBuilder.of(list, TestDemoVo.class).sheetName("测试单表").toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试单表详细信息
|
||||
*
|
||||
* @param id 测试ID
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<TestDemoVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable("id") Long id) {
|
||||
return R.ok(testDemoService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增测试单表
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:add")
|
||||
@Log(title = "测试单表", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit(interval = 2, timeUnit = TimeUnit.SECONDS, message = "{repeat.submit.message}")
|
||||
@PostMapping()
|
||||
public R<Void> add(@RequestBody TestDemoBo bo) {
|
||||
// 使用校验工具对标 @Validated(AddGroup.class) 注解
|
||||
// 用于在非 Controller 的地方校验对象
|
||||
ValidatorUtils.validate(bo, AddGroup.class);
|
||||
return toAjax(testDemoService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改测试单表
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:edit")
|
||||
@Log(title = "测试单表", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestDemoBo bo) {
|
||||
return toAjax(testDemoService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除测试单表
|
||||
*
|
||||
* @param ids 测试ID串
|
||||
*/
|
||||
@SaCheckPermission("demo:demo:remove")
|
||||
@Log(title = "测试单表", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(testDemoService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.demo.domain.TestDemoEncrypt;
|
||||
import org.dromara.demo.mapper.TestDemoEncryptMapper;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* 测试数据库加解密功能
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/demo/encrypt")
|
||||
public class TestEncryptController {
|
||||
|
||||
@Autowired
|
||||
private TestDemoEncryptMapper mapper;
|
||||
@Value("${mybatis-encryptor.enable}")
|
||||
private Boolean encryptEnable;
|
||||
|
||||
/**
|
||||
* 测试数据库加解密
|
||||
*
|
||||
* @param key 测试key
|
||||
* @param value 测试value
|
||||
*/
|
||||
@GetMapping()
|
||||
public R<Map<String, TestDemoEncrypt>> test(String key, String value) {
|
||||
if (!encryptEnable) {
|
||||
throw new RuntimeException("加密功能未开启!");
|
||||
}
|
||||
Map<String, TestDemoEncrypt> map = new HashMap<>(2);
|
||||
TestDemoEncrypt demo = new TestDemoEncrypt();
|
||||
demo.setTestKey(key);
|
||||
demo.setValue(value);
|
||||
mapper.insert(demo);
|
||||
map.put("加密", demo);
|
||||
TestDemoEncrypt testDemo = mapper.selectById(demo.getId());
|
||||
map.put("解密", testDemo);
|
||||
return R.ok(map);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.excel.core.ExcelResult;
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
import org.dromara.demo.domain.vo.ExportDemoVo;
|
||||
import org.dromara.demo.listener.ExportDemoListener;
|
||||
import org.dromara.demo.service.IExportExcelService;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 测试Excel功能
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/excel")
|
||||
public class TestExcelController {
|
||||
|
||||
private final IExportExcelService exportExcelService;
|
||||
|
||||
/**
|
||||
* 单列表多数据
|
||||
*/
|
||||
@GetMapping("/exportTemplateOne")
|
||||
public void exportTemplateOne(HttpServletResponse response) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("title", "单列表多数据");
|
||||
map.put("test1", "数据测试1");
|
||||
map.put("test2", "数据测试2");
|
||||
map.put("test3", "数据测试3");
|
||||
map.put("test4", "数据测试4");
|
||||
map.put("testTest", "666");
|
||||
List<TestObj> list = new ArrayList<>();
|
||||
list.add(new TestObj("单列表测试1", "列表测试1", "列表测试2", "列表测试3", "列表测试4"));
|
||||
list.add(new TestObj("单列表测试2", "列表测试5", "列表测试6", "列表测试7", "列表测试8"));
|
||||
list.add(new TestObj("单列表测试3", "列表测试9", "列表测试10", "列表测试11", "列表测试12"));
|
||||
ExcelBuilder.template("excel/单列表.xlsx")
|
||||
.filename("单列表.xlsx")
|
||||
.data(CollUtil.newArrayList(map, list))
|
||||
.toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多列表多数据
|
||||
*/
|
||||
@GetMapping("/exportTemplateMuliti")
|
||||
public void exportTemplateMuliti(HttpServletResponse response) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
map.put("title1", "标题1");
|
||||
map.put("title2", "标题2");
|
||||
map.put("title3", "标题3");
|
||||
map.put("title4", "标题4");
|
||||
map.put("author", "Lion Li");
|
||||
List<TestObj1> list1 = new ArrayList<>();
|
||||
list1.add(new TestObj1("list1测试1", "list1测试2", "list1测试3"));
|
||||
list1.add(new TestObj1("list1测试4", "list1测试5", "list1测试6"));
|
||||
list1.add(new TestObj1("list1测试7", "list1测试8", "list1测试9"));
|
||||
List<TestObj1> list2 = new ArrayList<>();
|
||||
list2.add(new TestObj1("list2测试1", "list2测试2", "list2测试3"));
|
||||
list2.add(new TestObj1("list2测试4", "list2测试5", "list2测试6"));
|
||||
List<TestObj1> list3 = new ArrayList<>();
|
||||
list3.add(new TestObj1("list3测试1", "list3测试2", "list3测试3"));
|
||||
List<TestObj1> list4 = new ArrayList<>();
|
||||
list4.add(new TestObj1("list4测试1", "list4测试2", "list4测试3"));
|
||||
list4.add(new TestObj1("list4测试4", "list4测试5", "list4测试6"));
|
||||
list4.add(new TestObj1("list4测试7", "list4测试8", "list4测试9"));
|
||||
list4.add(new TestObj1("list4测试10", "list4测试11", "list4测试12"));
|
||||
Map<String, Object> multiListMap = new HashMap<>();
|
||||
multiListMap.put("map", map);
|
||||
multiListMap.put("data1", list1);
|
||||
multiListMap.put("data2", list2);
|
||||
multiListMap.put("data3", list3);
|
||||
multiListMap.put("data4", list4);
|
||||
ExcelBuilder.template("excel/多列表.xlsx")
|
||||
.filename("多列表.xlsx")
|
||||
.multiList(multiListMap)
|
||||
.toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出下拉框
|
||||
*
|
||||
* @param response /
|
||||
*/
|
||||
@GetMapping("/exportWithOptions")
|
||||
public void exportWithOptions(HttpServletResponse response) {
|
||||
exportExcelService.exportWithOptions(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 自定义导出
|
||||
*
|
||||
* @param response /
|
||||
*/
|
||||
@GetMapping("/customExport")
|
||||
public void customExport(HttpServletResponse response) {
|
||||
exportExcelService.customExport(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 多个sheet导出
|
||||
*/
|
||||
@GetMapping("/exportTemplateMultiSheet")
|
||||
public void exportTemplateMultiSheet(HttpServletResponse response) {
|
||||
List<TestObj1> list1 = new ArrayList<>();
|
||||
list1.add(new TestObj1("list1测试1", "list1测试2", "list1测试3"));
|
||||
list1.add(new TestObj1("list1测试4", "list1测试5", "list1测试6"));
|
||||
List<TestObj1> list2 = new ArrayList<>();
|
||||
list2.add(new TestObj1("list2测试1", "list2测试2", "list2测试3"));
|
||||
list2.add(new TestObj1("list2测试4", "list2测试5", "list2测试6"));
|
||||
List<TestObj1> list3 = new ArrayList<>();
|
||||
list3.add(new TestObj1("list3测试1", "list3测试2", "list3测试3"));
|
||||
list3.add(new TestObj1("list3测试4", "list3测试5", "list3测试6"));
|
||||
List<TestObj1> list4 = new ArrayList<>();
|
||||
list4.add(new TestObj1("list4测试1", "list4测试2", "list4测试3"));
|
||||
list4.add(new TestObj1("list4测试4", "list4测试5", "list4测试6"));
|
||||
|
||||
List<Map<String, Object>> list = new ArrayList<>();
|
||||
Map<String, Object> sheetMap1 = new HashMap<>();
|
||||
sheetMap1.put("data1", list1);
|
||||
Map<String, Object> sheetMap2 = new HashMap<>();
|
||||
sheetMap2.put("data2", list2);
|
||||
Map<String, Object> sheetMap3 = new HashMap<>();
|
||||
sheetMap3.put("data3", list3);
|
||||
Map<String, Object> sheetMap4 = new HashMap<>();
|
||||
sheetMap4.put("data4", list4);
|
||||
|
||||
list.add(sheetMap1);
|
||||
list.add(sheetMap2);
|
||||
list.add(sheetMap3);
|
||||
list.add(sheetMap4);
|
||||
ExcelBuilder.template("excel/多sheet列表.xlsx")
|
||||
.filename("多sheet列表")
|
||||
.multiSheet(list)
|
||||
.toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入表格
|
||||
*/
|
||||
@PostMapping(value = "/importWithOptions", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||
public List<ExportDemoVo> importWithOptions(@RequestPart("file") MultipartFile file) throws Exception {
|
||||
// 处理解析结果
|
||||
ExcelResult<ExportDemoVo> excelResult = ExcelBuilder.read(file.getInputStream(), ExportDemoVo.class)
|
||||
.listener(new ExportDemoListener())
|
||||
.doRead();
|
||||
return excelResult.getList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 多列表模板填充测试对象。
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class TestObj1 {
|
||||
/**
|
||||
* 测试字段1。
|
||||
*/
|
||||
private String test1;
|
||||
/**
|
||||
* 测试字段2。
|
||||
*/
|
||||
private String test2;
|
||||
/**
|
||||
* 测试字段3。
|
||||
*/
|
||||
private String test3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 单列表模板填充测试对象。
|
||||
*/
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
static class TestObj {
|
||||
/**
|
||||
* 名称。
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* 列表字段1。
|
||||
*/
|
||||
private String list1;
|
||||
/**
|
||||
* 列表字段2。
|
||||
*/
|
||||
private String list2;
|
||||
/**
|
||||
* 列表字段3。
|
||||
*/
|
||||
private String list3;
|
||||
/**
|
||||
* 列表字段4。
|
||||
*/
|
||||
private String list4;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.utils.MessageUtils;
|
||||
import org.hibernate.validator.constraints.Range;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
||||
/**
|
||||
* 测试国际化
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Validated
|
||||
@RestController
|
||||
@RequestMapping("/demo/i18n")
|
||||
public class TestI18nController {
|
||||
|
||||
/**
|
||||
* 通过code获取国际化内容
|
||||
* code为 messages.properties 中的 key
|
||||
* <p>
|
||||
* 测试使用 user.register.success
|
||||
*
|
||||
* @param code 国际化code
|
||||
*/
|
||||
@GetMapping()
|
||||
public R<Void> get(String code) {
|
||||
return R.ok(MessageUtils.message(code));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validator 校验国际化
|
||||
* 不传值 分别查看异常返回
|
||||
* <p>
|
||||
* 测试使用 not.null
|
||||
*/
|
||||
@GetMapping("/test1")
|
||||
public R<Void> test1(@NotBlank(message = "{not.null}") String str) {
|
||||
return R.ok(str);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bean 校验国际化
|
||||
* 不传值 分别查看异常返回
|
||||
* <p>
|
||||
* 测试使用 not.null
|
||||
*/
|
||||
@GetMapping("/test2")
|
||||
public R<TestI18nBo> test2(@Validated TestI18nBo bo) {
|
||||
return R.ok(bo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 国际化 Bean 校验测试对象。
|
||||
*/
|
||||
@Data
|
||||
public static class TestI18nBo {
|
||||
|
||||
/**
|
||||
* 名称。
|
||||
*/
|
||||
@NotBlank(message = "{not.null}")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 年龄。
|
||||
*/
|
||||
@NotNull(message = "{not.null}")
|
||||
@Range(min = 0, max = 100, message = "{length.not.valid}")
|
||||
private Integer age;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.sensitive.annotation.Sensitive;
|
||||
import org.dromara.common.sensitive.core.SensitiveService;
|
||||
import org.dromara.common.sensitive.core.SensitiveStrategy;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 测试数据脱敏控制器
|
||||
* <p>
|
||||
* 默认管理员不过滤
|
||||
* 需自行根据业务重写实现
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0
|
||||
* @see SensitiveService
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/demo/sensitive")
|
||||
public class TestSensitiveController extends BaseController {
|
||||
|
||||
/**
|
||||
* 测试数据脱敏
|
||||
*/
|
||||
@GetMapping("/test")
|
||||
public R<TestSensitive> test() {
|
||||
TestSensitive testSensitive = new TestSensitive();
|
||||
testSensitive.setIdCard("210397198608215431");
|
||||
testSensitive.setPhone("17640125371");
|
||||
testSensitive.setAddress("北京市朝阳区某某四合院1203室");
|
||||
testSensitive.setEmail("17640125371@163.com");
|
||||
testSensitive.setBankCard("6226456952351452853");
|
||||
return R.ok(testSensitive);
|
||||
}
|
||||
|
||||
/**
|
||||
* 脱敏测试对象。
|
||||
*/
|
||||
@Data
|
||||
static class TestSensitive {
|
||||
|
||||
/**
|
||||
* 身份证
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.ID_CARD)
|
||||
private String idCard;
|
||||
|
||||
/**
|
||||
* 电话
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.PHONE, roleKey = "common")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 地址
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.ADDRESS, perms = "system:user:query")
|
||||
private String address;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.EMAIL, roleKey = "common", perms = "system:user:query1")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 银行卡
|
||||
*/
|
||||
@Sensitive(strategy = SensitiveStrategy.BANK_CARD, roleKey = "common1", perms = "system:user:query")
|
||||
private String bankCard;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.core.validate.QueryGroup;
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.demo.domain.bo.TestTreeBo;
|
||||
import org.dromara.demo.domain.vo.TestTreeVo;
|
||||
import org.dromara.demo.service.ITestTreeService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试树表Controller
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/tree")
|
||||
public class TestTreeController extends BaseController {
|
||||
|
||||
private final ITestTreeService testTreeService;
|
||||
|
||||
/**
|
||||
* 查询测试树表列表
|
||||
*/
|
||||
@SaCheckPermission("demo:tree:list")
|
||||
@GetMapping("/list")
|
||||
public R<List<TestTreeVo>> list(@Validated(QueryGroup.class) TestTreeBo bo) {
|
||||
List<TestTreeVo> list = testTreeService.queryList(bo);
|
||||
return R.ok(list);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出测试树表列表
|
||||
*/
|
||||
@SaCheckPermission("demo:tree:export")
|
||||
@Log(title = "测试树表", businessType = BusinessType.EXPORT)
|
||||
@GetMapping("/export")
|
||||
public void export(@Validated TestTreeBo bo, HttpServletResponse response) {
|
||||
List<TestTreeVo> list = testTreeService.queryList(bo);
|
||||
ExcelBuilder.of(list, TestTreeVo.class).sheetName("测试树表").toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取测试树表详细信息
|
||||
*
|
||||
* @param id 测试树ID
|
||||
*/
|
||||
@SaCheckPermission("demo:tree:query")
|
||||
@GetMapping("/{id}")
|
||||
public R<TestTreeVo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable("id") Long id) {
|
||||
return R.ok(testTreeService.queryById(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增测试树表
|
||||
*/
|
||||
@SaCheckPermission("demo:tree:add")
|
||||
@Log(title = "测试树表", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody TestTreeBo bo) {
|
||||
return toAjax(testTreeService.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改测试树表
|
||||
*/
|
||||
@SaCheckPermission("demo:tree:edit")
|
||||
@Log(title = "测试树表", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody TestTreeBo bo) {
|
||||
return toAjax(testTreeService.updateByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除测试树表
|
||||
*
|
||||
* @param ids 测试树ID串
|
||||
*/
|
||||
@SaCheckPermission("demo:tree:remove")
|
||||
@Log(title = "测试树表", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{ids}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable Long[] ids) {
|
||||
return toAjax(testTreeService.deleteWithValidByIds(Arrays.asList(ids), true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package org.dromara.demo.controller;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.enums.PushSourceEnum;
|
||||
import org.dromara.common.core.enums.PushTypeEnum;
|
||||
import org.dromara.system.api.MessageService;
|
||||
import org.dromara.system.api.domain.PushPayloadDTO;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* WebSocket 演示案例
|
||||
*
|
||||
* @author zendwang
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/websocket")
|
||||
@Slf4j
|
||||
public class WebSocketController {
|
||||
|
||||
private final MessageService messageService;
|
||||
|
||||
/**
|
||||
* 发布消息
|
||||
*
|
||||
* @param userId 目标用户
|
||||
* @param message 发送内容
|
||||
*/
|
||||
@GetMapping("/send")
|
||||
public R<Void> send(Long userId, String message) {
|
||||
PushPayloadDTO payload = PushPayloadDTO.of(
|
||||
PushTypeEnum.MESSAGE,
|
||||
PushSourceEnum.BACKEND,
|
||||
message,
|
||||
null
|
||||
);
|
||||
if (userId == null) {
|
||||
messageService.publishAll(payload);
|
||||
} else {
|
||||
messageService.publishMessage(List.of(userId), payload);
|
||||
}
|
||||
return R.ok("操作成功");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package org.dromara.demo.controller;
|
||||
@@ -0,0 +1,35 @@
|
||||
package org.dromara.demo.controller.queue;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/**
|
||||
* 实体类 注意不允许使用内部类 否则会找不到类
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class PriorityDemo implements Comparable<PriorityDemo> {
|
||||
/**
|
||||
* 队列元素名称。
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 优先级排序值。
|
||||
*/
|
||||
private Integer orderNum;
|
||||
|
||||
/**
|
||||
* 按排序值比较优先级。
|
||||
*
|
||||
* @param other 另一个队列元素
|
||||
* @return 比较结果
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(PriorityDemo other) {
|
||||
return Integer.compare(getOrderNum(), other.getOrderNum());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package org.dromara.demo.controller.queue;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.redis.utils.QueueUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* 优先队列 演示案例
|
||||
* <p>
|
||||
* 轻量级队列 重量级数据量 请使用 MQ
|
||||
* <p>
|
||||
* 集群测试通过 同一个消息只会被消费一次 做好事务补偿
|
||||
* 集群测试流程 在其中一台发送数据 两端分别调用获取接口 一次获取一条
|
||||
*
|
||||
* @author Lion Li
|
||||
* @version 3.6.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/demo/queue/priority")
|
||||
public class PriorityQueueController {
|
||||
|
||||
/**
|
||||
* 添加队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
@GetMapping("/add")
|
||||
public R<Void> add(String queueName) {
|
||||
// 用完了一定要销毁 否则会一直存在
|
||||
boolean b = QueueUtils.destroyPriorityQueue(queueName);
|
||||
log.info("通道: {} , 删除: {}", queueName, b);
|
||||
|
||||
for (int i = 0; i < 10; i++) {
|
||||
int randomNum = RandomUtil.randomInt(10);
|
||||
PriorityDemo data = new PriorityDemo();
|
||||
data.setName("data-" + i);
|
||||
data.setOrderNum(randomNum);
|
||||
if (QueueUtils.addPriorityQueueObject(queueName, data)) {
|
||||
log.info("通道: {} , 发送数据: {}", queueName, data);
|
||||
} else {
|
||||
log.info("通道: {} , 发送数据: {}, 发送失败", queueName, data);
|
||||
}
|
||||
}
|
||||
return R.ok("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
* @param name 对象名
|
||||
* @param orderNum 排序号
|
||||
*/
|
||||
@GetMapping("/remove")
|
||||
public R<Void> remove(String queueName, String name, Integer orderNum) {
|
||||
PriorityDemo data = new PriorityDemo();
|
||||
data.setName(name);
|
||||
data.setOrderNum(orderNum);
|
||||
if (QueueUtils.removePriorityQueueObject(queueName, data)) {
|
||||
log.info("通道: {} , 删除数据: {}", queueName, data);
|
||||
} else {
|
||||
return R.fail("操作失败");
|
||||
}
|
||||
return R.ok("操作成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取队列数据
|
||||
*
|
||||
* @param queueName 队列名
|
||||
*/
|
||||
@GetMapping("/get")
|
||||
public R<Void> get(String queueName) {
|
||||
PriorityDemo data;
|
||||
do {
|
||||
data = QueueUtils.getPriorityQueueObject(queueName);
|
||||
log.info("通道: {} , 获取数据: {}", queueName, data);
|
||||
} while (data != null);
|
||||
return R.ok("操作成功");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.dromara.demo.domain;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 文档实体
|
||||
*/
|
||||
@Data
|
||||
public class Document {
|
||||
|
||||
/**
|
||||
* es中的唯一id
|
||||
*/
|
||||
private String id;
|
||||
|
||||
/**
|
||||
* 文档标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 文档内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package org.dromara.demo.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 测试单表对象 test_demo
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("test_demo")
|
||||
public class TestDemo extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
@OrderBy(asc = false, sort = 1)
|
||||
private Integer orderNum;
|
||||
|
||||
/**
|
||||
* key键
|
||||
*/
|
||||
private String testKey;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
/**
|
||||
* 删除标志
|
||||
*/
|
||||
@TableLogic
|
||||
private Long delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package org.dromara.demo.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.encrypt.annotation.EncryptField;
|
||||
import org.dromara.common.encrypt.enums.AlgorithmType;
|
||||
|
||||
/**
|
||||
* 测试加密字段实体。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("test_demo")
|
||||
public class TestDemoEncrypt extends TestDemo {
|
||||
|
||||
/**
|
||||
* key键
|
||||
*/
|
||||
// @EncryptField(algorithm=AlgorithmType.SM2, privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgZSlOvw8FBiH+aFJWLYZP/VRjg9wjfRarTkGBZd/T3N+gCgYIKoEcz1UBgi2hRANCAAR5DGuQwJqkxnbCsP+iPSDoHWIF4RwcR5EsSvT8QPxO1wRkR2IhCkzvRb32x2CUgJFdvoqVqfApFDPZzShqzBwX", publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEeQxrkMCapMZ2wrD/oj0g6B1iBeEcHEeRLEr0/ED8TtcEZEdiIQpM70W99sdglICRXb6KlanwKRQz2c0oaswcFw==")
|
||||
@EncryptField(algorithm = AlgorithmType.RSA, privateKey = "MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBANBBEeueWlXlkkj2+WY5l+IWe42d8b5K28g+G/CFKC/yYAEHtqGlCsBOrb+YBkG9mPzmuYA/n9k0NFIc8E8yY5vZQaroyFBrTTWEzG9RY2f7Y3svVyybs6jpXSUs4xff8abo7wL1Y/wUaeatTViamxYnyTvdTmLm3d+JjRij68rxAgMBAAECgYAB0TnhXraSopwIVRfmboea1b0upl+BUdTJcmci412UjrKr5aE695ZLPkXbFXijVu7HJlyyv94NVUdaMACV7Ku/S2RuNB70M7YJm8rAjHFC3/i2ZeIM60h1Ziy4QKv0XM3pRATlDCDNhC1WUrtQCQSgU8kcp6eUUppruOqDzcY04QJBAPm9+sBP9CwDRgy3e5+V8aZtJkwDstb0lVVV/KY890cydVxiCwvX3fqVnxKMlb+x0YtH0sb9v+71xvK2lGobaRECQQDVePU6r/cCEfpc+nkWF6osAH1f8Mux3rYv2DoBGvaPzV2BGfsLed4neRfCwWNCKvGPCdW+L0xMJg8+RwaoBUPhAkAT5kViqXxFPYWJYd1h2+rDXhMdH3ZSlm6HvDBDdrwlWinr0Iwcx3iSjPV93uHXwm118aUj4fg3LDJMCKxOwBxhAkByrQXfvwOMYygBprRBf/j0plazoWFrbd6lGR0f1uI5IfNnFRPdeFw1DEINZ2Hw+6zEUF44SqRMC+4IYJNc02dBAkBCgy7RvfyV/A7N6kKXxTHauY0v6XwSSvpeKtRJkbIcRWOdIYvaHO9L7cklj3vIEdwjSUp9K4VTBYYlmAz1xh03", publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQQRHrnlpV5ZJI9vlmOZfiFnuNnfG+StvIPhvwhSgv8mABB7ahpQrATq2/mAZBvZj85rmAP5/ZNDRSHPBPMmOb2UGq6MhQa001hMxvUWNn+2N7L1csm7Oo6V0lLOMX3/Gm6O8C9WP8FGnmrU1YmpsWJ8k73U5i5t3fiY0Yo+vK8QIDAQAB")
|
||||
private String testKey;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
// @EncryptField // 什么也不写走默认yml配置
|
||||
// @EncryptField(algorithm = AlgorithmType.SM4, password = "10rfylhtccpuyke5")
|
||||
@EncryptField(algorithm = AlgorithmType.AES, password = "10rfylhtccpuyke5")
|
||||
private String value;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package org.dromara.demo.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableLogic;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import com.baomidou.mybatisplus.annotation.Version;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* 测试树表对象 test_tree
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("test_tree")
|
||||
public class TestTree extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@TableId(value = "id")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 父ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 树节点名
|
||||
*/
|
||||
private String treeName;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
@Version
|
||||
private Long version;
|
||||
|
||||
/**
|
||||
* 删除标志
|
||||
*/
|
||||
@TableLogic
|
||||
private Long delFlag;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package org.dromara.demo.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 测试单表业务对象 test_demo
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
|
||||
@Data
|
||||
@AutoMapper(target = TestDemo.class, reverseConvertGenerate = false)
|
||||
public class TestDemoBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "主键不能为空", groups = {EditGroup.class})
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@NotNull(message = "部门id不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@NotNull(message = "用户id不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
@NotNull(message = "排序号不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Integer orderNum;
|
||||
|
||||
/**
|
||||
* key键
|
||||
*/
|
||||
@NotBlank(message = "key键不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private String testKey;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
@NotBlank(message = "值不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
private Long version;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package org.dromara.demo.domain.bo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.demo.domain.TestTree;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 测试树表业务对象 test_tree
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
|
||||
@Data
|
||||
@AutoMapper(target = TestTree.class, reverseConvertGenerate = false)
|
||||
public class TestTreeBo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@NotNull(message = "主键不能为空", groups = {EditGroup.class})
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 父ID
|
||||
*/
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@NotNull(message = "部门id不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@NotNull(message = "用户id不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 树节点名
|
||||
*/
|
||||
@NotBlank(message = "树节点名不能为空", groups = {AddGroup.class, EditGroup.class})
|
||||
private String treeName;
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package org.dromara.demo.domain;
|
||||
@@ -0,0 +1,122 @@
|
||||
package org.dromara.demo.domain.vo;
|
||||
|
||||
import jakarta.validation.constraints.NotEmpty;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.dromara.common.core.enums.UserStatus;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.annotation.ExcelEnumFormat;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import org.dromara.common.excel.convert.ExcelEnumConvert;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 带有下拉选的Excel导出
|
||||
*
|
||||
* @author Emil.Zhang
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class ExportDemoVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户昵称
|
||||
*/
|
||||
@ExcelProperty(value = "用户昵称", index = 0)
|
||||
@NotEmpty(message = "用户昵称不能为空", groups = AddGroup.class)
|
||||
private String nickName;
|
||||
|
||||
/**
|
||||
* 用户类型
|
||||
* </p>
|
||||
* 使用ExcelEnumFormat注解需要进行下拉选的部分
|
||||
*/
|
||||
@ExcelProperty(value = "用户类型", index = 1, converter = ExcelEnumConvert.class)
|
||||
@ExcelEnumFormat(enumClass = UserStatus.class, textField = "info")
|
||||
@NotEmpty(message = "用户类型不能为空", groups = AddGroup.class)
|
||||
private String userStatus;
|
||||
|
||||
/**
|
||||
* 性别
|
||||
* <p>
|
||||
* 使用ExcelDictFormat注解需要进行下拉选的部分
|
||||
*/
|
||||
@ExcelProperty(value = "性别", index = 2, converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "sys_user_gender")
|
||||
@NotEmpty(message = "性别不能为空", groups = AddGroup.class)
|
||||
private String gender;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@ExcelProperty(value = "手机号", index = 3)
|
||||
@NotEmpty(message = "手机号不能为空", groups = AddGroup.class)
|
||||
private String phoneNumber;
|
||||
|
||||
/**
|
||||
* Email
|
||||
*/
|
||||
@ExcelProperty(value = "Email", index = 4)
|
||||
@NotEmpty(message = "Email不能为空", groups = AddGroup.class)
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 省
|
||||
* <p>
|
||||
* 级联下拉,仅判断是否选了
|
||||
*/
|
||||
@ExcelProperty(value = "省", index = 5)
|
||||
@NotNull(message = "省不能为空", groups = AddGroup.class)
|
||||
private String province;
|
||||
|
||||
/**
|
||||
* 数据库中的省ID
|
||||
* </p>
|
||||
* 处理完毕后再判断是否市正确的值
|
||||
*/
|
||||
@NotNull(message = "请勿手动输入", groups = EditGroup.class)
|
||||
private Integer provinceId;
|
||||
|
||||
/**
|
||||
* 市
|
||||
* <p>
|
||||
* 级联下拉
|
||||
*/
|
||||
@ExcelProperty(value = "市", index = 6)
|
||||
@NotNull(message = "市不能为空", groups = AddGroup.class)
|
||||
private String city;
|
||||
|
||||
/**
|
||||
* 数据库中的市ID
|
||||
*/
|
||||
@NotNull(message = "请勿手动输入", groups = EditGroup.class)
|
||||
private Integer cityId;
|
||||
|
||||
/**
|
||||
* 县
|
||||
* <p>
|
||||
* 级联下拉
|
||||
*/
|
||||
@ExcelProperty(value = "县", index = 7)
|
||||
@NotNull(message = "县不能为空", groups = AddGroup.class)
|
||||
private String area;
|
||||
|
||||
/**
|
||||
* 数据库中的县ID
|
||||
*/
|
||||
@NotNull(message = "请勿手动输入", groups = EditGroup.class)
|
||||
private Integer areaId;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package org.dromara.demo.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import lombok.Data;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
|
||||
/**
|
||||
* 测试单表业务对象 test_demo
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = TestDemo.class)
|
||||
public class TestDemoImportVo {
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@NotNull(message = "部门id不能为空")
|
||||
@ExcelProperty(value = "部门id")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@NotNull(message = "用户id不能为空")
|
||||
@ExcelProperty(value = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
@NotNull(message = "排序号不能为空")
|
||||
@ExcelProperty(value = "排序号")
|
||||
private Long orderNum;
|
||||
|
||||
/**
|
||||
* key键
|
||||
*/
|
||||
@NotBlank(message = "key键不能为空")
|
||||
@ExcelProperty(value = "key键")
|
||||
private String testKey;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
@NotBlank(message = "值不能为空")
|
||||
@ExcelProperty(value = "值")
|
||||
private String value;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.dromara.demo.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.apache.fesod.sheet.annotation.format.DateTimeFormat;
|
||||
import org.dromara.common.excel.annotation.ExcelNotation;
|
||||
import org.dromara.common.excel.annotation.ExcelRequired;
|
||||
import org.dromara.common.translation.annotation.Translation;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* 测试单表视图对象 test_demo
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = TestDemo.class)
|
||||
public class TestDemoVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
@ExcelProperty(value = "主键")
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@ExcelRequired
|
||||
@ExcelProperty(value = "部门id")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@ExcelRequired
|
||||
@ExcelProperty(value = "用户id", index = 5)
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 排序号
|
||||
*/
|
||||
@ExcelRequired
|
||||
@ExcelProperty(value = "排序号")
|
||||
private Integer orderNum;
|
||||
|
||||
/**
|
||||
* key键
|
||||
*/
|
||||
@ExcelNotation(value = "测试key")
|
||||
@ExcelProperty(value = "key键")
|
||||
private String testKey;
|
||||
|
||||
/**
|
||||
* 值
|
||||
*/
|
||||
@ExcelNotation(value = "测试value")
|
||||
@ExcelProperty(value = "值")
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ExcelRequired
|
||||
@DateTimeFormat("yyyy-MM-dd HH:mm:ss")
|
||||
@ExcelProperty(value = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
@ExcelProperty(value = "创建人")
|
||||
private Long createBy;
|
||||
|
||||
/**
|
||||
* 创建人账号
|
||||
*/
|
||||
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "createBy")
|
||||
@ExcelProperty(value = "创建人账号")
|
||||
private String createByName;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
@ExcelProperty(value = "更新时间")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
@ExcelProperty(value = "更新人")
|
||||
private Long updateBy;
|
||||
|
||||
/**
|
||||
* 更新人账号
|
||||
*/
|
||||
@Translation(type = TransConstant.USER_ID_TO_NAME, mapper = "updateBy")
|
||||
@ExcelProperty(value = "更新人账号")
|
||||
private String updateByName;
|
||||
|
||||
/**
|
||||
* 版本
|
||||
*/
|
||||
private Long version;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.dromara.demo.domain.vo;
|
||||
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.dromara.demo.domain.TestTree;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
|
||||
/**
|
||||
* 测试树表视图对象 test_tree
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = TestTree.class)
|
||||
public class TestTreeVo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 父id
|
||||
*/
|
||||
@ExcelProperty(value = "父id")
|
||||
private Long parentId;
|
||||
|
||||
/**
|
||||
* 部门id
|
||||
*/
|
||||
@ExcelProperty(value = "部门id")
|
||||
private Long deptId;
|
||||
|
||||
/**
|
||||
* 用户id
|
||||
*/
|
||||
@ExcelProperty(value = "用户id")
|
||||
private Long userId;
|
||||
|
||||
/**
|
||||
* 树节点名
|
||||
*/
|
||||
@ExcelProperty(value = "树节点名")
|
||||
private String treeName;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
@ExcelProperty(value = "创建时间")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package org.dromara.demo.esmapper;
|
||||
|
||||
import org.dromara.demo.domain.Document;
|
||||
import org.dromara.easyes.core.kernel.BaseEsMapper;
|
||||
|
||||
/**
|
||||
* Easy-Es 文档 Mapper。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface DocumentMapper extends BaseEsMapper<Document> {
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.dromara.demo.listener;
|
||||
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import org.apache.fesod.sheet.context.AnalysisContext;
|
||||
import org.dromara.common.core.utils.ValidatorUtils;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.excel.core.DefaultExcelListener;
|
||||
import org.dromara.common.excel.core.DropDownOptions;
|
||||
import org.dromara.demo.domain.vo.ExportDemoVo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Excel带下拉框的解析处理器
|
||||
*
|
||||
* @author Emil.Zhang
|
||||
*/
|
||||
public class ExportDemoListener extends DefaultExcelListener<ExportDemoVo> {
|
||||
|
||||
/**
|
||||
* 创建下拉框导入解析监听器。
|
||||
*/
|
||||
public ExportDemoListener() {
|
||||
// 显示使用构造函数,否则将导致空指针
|
||||
super(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并校验一行下拉框演示数据。
|
||||
*
|
||||
* @param data 行数据
|
||||
* @param context 解析上下文
|
||||
*/
|
||||
@Override
|
||||
public void invoke(ExportDemoVo data, AnalysisContext context) {
|
||||
// 先校验必填
|
||||
ValidatorUtils.validate(data, AddGroup.class);
|
||||
|
||||
// 处理级联下拉的部分
|
||||
String province = data.getProvince();
|
||||
String city = data.getCity();
|
||||
String area = data.getArea();
|
||||
// 本行用户选择的省
|
||||
List<String> thisRowSelectedProvinceOption = DropDownOptions.analyzeOptionValue(province);
|
||||
if (thisRowSelectedProvinceOption.size() == 2) {
|
||||
String provinceIdStr = thisRowSelectedProvinceOption.get(1);
|
||||
if (NumberUtil.isNumber(provinceIdStr)) {
|
||||
// 严格要求数据的话可以在这里做与数据库相关的判断
|
||||
// 例如判断省信息是否在数据库中存在等,建议结合RedisCache做缓存10s,减少数据库调用
|
||||
data.setProvinceId(Integer.parseInt(provinceIdStr));
|
||||
}
|
||||
}
|
||||
// 本行用户选择的市
|
||||
List<String> thisRowSelectedCityOption = DropDownOptions.analyzeOptionValue(city);
|
||||
if (thisRowSelectedCityOption.size() == 2) {
|
||||
String cityIdStr = thisRowSelectedCityOption.get(1);
|
||||
if (NumberUtil.isNumber(cityIdStr)) {
|
||||
data.setCityId(Integer.parseInt(cityIdStr));
|
||||
}
|
||||
}
|
||||
// 本行用户选择的县
|
||||
List<String> thisRowSelectedAreaOption = DropDownOptions.analyzeOptionValue(area);
|
||||
if (thisRowSelectedAreaOption.size() == 2) {
|
||||
String areaIdStr = thisRowSelectedAreaOption.get(1);
|
||||
if (NumberUtil.isNumber(areaIdStr)) {
|
||||
data.setAreaId(Integer.parseInt(areaIdStr));
|
||||
}
|
||||
}
|
||||
|
||||
// 处理完毕以后判断是否符合规则
|
||||
ValidatorUtils.validate(data, EditGroup.class);
|
||||
|
||||
// 添加到处理结果中
|
||||
getExcelResult().getList().add(data);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.dromara.demo.mapper;
|
||||
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.demo.domain.TestDemoEncrypt;
|
||||
|
||||
/**
|
||||
* 测试加密功能
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface TestDemoEncryptMapper extends BaseMapperPlus<TestDemoEncrypt, TestDemoEncrypt> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.dromara.demo.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Constants;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.dromara.common.mybatis.annotation.DataColumn;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.demo.domain.vo.TestDemoVo;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试单表Mapper接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
public interface TestDemoMapper extends BaseMapperPlus<TestDemo, TestDemoVo> {
|
||||
|
||||
/**
|
||||
* 自定义分页查询演示数据。
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param wrapper 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
@DataPermission({
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "user_id")
|
||||
})
|
||||
Page<TestDemoVo> customPageList(@Param("page") Page<TestDemo> page, @Param("ew") Wrapper<TestDemo> wrapper);
|
||||
|
||||
/**
|
||||
* 分页查询演示 VO 列表并应用数据权限。
|
||||
*
|
||||
* @param page 分页对象
|
||||
* @param wrapper 查询条件
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Override
|
||||
@DataPermission({
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "user_id")
|
||||
})
|
||||
default <P extends IPage<TestDemoVo>> P selectVoPage(IPage<TestDemo> page, Wrapper<TestDemo> wrapper) {
|
||||
return selectVoPage(page, wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询演示 VO 列表并应用数据权限。
|
||||
*
|
||||
* @param wrapper 查询条件
|
||||
* @return 演示 VO 列表
|
||||
*/
|
||||
@Override
|
||||
@DataPermission({
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "user_id")
|
||||
})
|
||||
default List<TestDemoVo> selectVoList(Wrapper<TestDemo> wrapper) {
|
||||
return selectVoList(wrapper, this.currentVoClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主键集合查询演示数据并应用数据权限。
|
||||
*
|
||||
* @param idList 主键集合
|
||||
* @return 演示数据列表
|
||||
*/
|
||||
@Override
|
||||
@DataPermission(value = {
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "user_id")
|
||||
}, joinStr = "AND")
|
||||
List<TestDemo> selectByIds(@Param(Constants.COLL) Collection<? extends Serializable> idList);
|
||||
|
||||
/**
|
||||
* 按主键更新演示数据并应用数据权限。
|
||||
*
|
||||
* @param entity 演示数据实体
|
||||
* @return 更新行数
|
||||
*/
|
||||
@Override
|
||||
@DataPermission({
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "user_id")
|
||||
})
|
||||
int updateById(@Param(Constants.ENTITY) TestDemo entity);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package org.dromara.demo.mapper;
|
||||
|
||||
import org.dromara.common.mybatis.annotation.DataColumn;
|
||||
import org.dromara.common.mybatis.annotation.DataPermission;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.demo.domain.TestTree;
|
||||
import org.dromara.demo.domain.vo.TestTreeVo;
|
||||
|
||||
/**
|
||||
* 测试树表Mapper接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@DataPermission({
|
||||
@DataColumn(key = "deptName", value = "dept_id"),
|
||||
@DataColumn(key = "userName", value = "user_id")
|
||||
})
|
||||
public interface TestTreeMapper extends BaseMapperPlus<TestTree, TestTreeVo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package org.dromara.demo.mapper;
|
||||
@@ -0,0 +1,58 @@
|
||||
package org.dromara.demo.mcp;
|
||||
|
||||
import io.modelcontextprotocol.spec.McpSchema;
|
||||
import org.springframework.ai.mcp.annotation.McpElicitation;
|
||||
import org.springframework.ai.mcp.annotation.McpSampling;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MCP Client 能力回调演示。
|
||||
* <p>
|
||||
* `@McpSampling` 与 `@McpElicitation` 不是对外提供给 MCP Client 调用的工具,
|
||||
* 而是 MCP Server 在处理请求时,反向请求 MCP Client 进行模型采样或补充信息时的回调处理器。
|
||||
* <p>
|
||||
* 这里提供最小示例,主要用于展示注解写法,并让 Spring AI MCP 注解扫描器能扫描到
|
||||
* sampling/elicitation 方法,避免启动时输出 "No sampling methods found" 与
|
||||
* "No elicitation methods found" 提示。
|
||||
* <p>
|
||||
* Spring AI 2.0.0-M6 要求 `clients` 不能为空。这里使用 `ruoyi-demo` 作为示例客户端名称,
|
||||
* 实际使用时应与 `spring.ai.mcp.client.*.connections` 中的连接名保持一致。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
public class McpDemoClientHandlers {
|
||||
|
||||
/**
|
||||
* MCP sampling 回调示例。
|
||||
*
|
||||
* @param request 采样请求
|
||||
* @return 采样结果
|
||||
*/
|
||||
@McpSampling(clients = "ruoyi-demo")
|
||||
public McpSchema.CreateMessageResult sampling(McpSchema.CreateMessageRequest request) {
|
||||
String text = request.systemPrompt() == null ? "demo sampling response" : request.systemPrompt();
|
||||
return new McpSchema.CreateMessageResult(
|
||||
McpSchema.Role.ASSISTANT,
|
||||
new McpSchema.TextContent(text),
|
||||
"ruoyi-demo-sampling",
|
||||
McpSchema.CreateMessageResult.StopReason.END_TURN
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP elicitation 回调示例。
|
||||
*
|
||||
* @param request 补充信息请求
|
||||
* @return 补充信息结果
|
||||
*/
|
||||
@McpElicitation(clients = "ruoyi-demo")
|
||||
public McpSchema.ElicitResult elicitation(McpSchema.ElicitRequest request) {
|
||||
return new McpSchema.ElicitResult(
|
||||
McpSchema.ElicitResult.Action.ACCEPT,
|
||||
Map.of("message", request.message())
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package org.dromara.demo.mcp;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.mcp.core.McpClientTemplate;
|
||||
import org.dromara.common.mcp.core.McpResourceReadResult;
|
||||
import org.dromara.common.mcp.core.McpToolCallResult;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* MCP Client 演示服务,从外部 MCP Server 接收数据后交给业务层处理。
|
||||
* <p>
|
||||
* 本服务会注入 Spring AI 自动创建的多个 `McpSyncClient`。当
|
||||
* `spring.ai.mcp.client.enabled=true` 且配置了多个 connection 时,
|
||||
* 这里可以统一遍历这些 MCP Server。
|
||||
* 未启用 MCP Client 时,本服务仍会注册,避免演示 Controller 影响应用启动。
|
||||
* <p>
|
||||
* 如果要用当前项目自己连自己测试,建议启动两个 `ruoyi-admin` 实例:
|
||||
* 一个作为 server,一个作为 client,client 指向 server 的地址。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class McpDemoClientService {
|
||||
|
||||
private final ObjectProvider<McpClientTemplate> mcpClientTemplateProvider;
|
||||
|
||||
/**
|
||||
* 查询已接入的 MCP Server 与工具列表。
|
||||
*
|
||||
* @return Server 名称与工具名称列表
|
||||
*/
|
||||
public Map<String, List<String>> listRemoteTools() {
|
||||
return execute(McpClientTemplate::listTools);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调用外部 MCP 工具接收数据。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @param arguments 工具参数
|
||||
* @return 工具返回内容
|
||||
*/
|
||||
public Map<String, McpToolCallResult> callRemoteTool(String toolName, Map<String, Object> arguments) {
|
||||
return execute(template -> template.callTool(toolName, arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取外部 MCP 资源。
|
||||
*
|
||||
* @param uri 资源地址
|
||||
* @return 资源内容
|
||||
*/
|
||||
public Map<String, McpResourceReadResult> readRemoteResource(String uri) {
|
||||
return execute(template -> template.readResource(uri));
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟业务处理入口。真实业务可在这里做校验、转换、去重、入库和审计。
|
||||
* <p>
|
||||
* MCP 返回数据不建议直接入库,应先转换成业务 BO/DTO,再进入业务 Service。
|
||||
*
|
||||
* @param toolName 工具名称
|
||||
* @param arguments 工具参数
|
||||
* @return 处理结果
|
||||
*/
|
||||
public McpDemoHandleResult receiveAndHandle(String toolName, Map<String, Object> arguments) {
|
||||
return new McpDemoHandleResult("MCP", true, callRemoteTool(toolName, arguments));
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行 MCP Client 操作。
|
||||
*
|
||||
* @param action MCP Client 操作
|
||||
* @param <T> 返回值类型
|
||||
* @return 操作结果
|
||||
*/
|
||||
private <T> T execute(Function<McpClientTemplate, T> action) {
|
||||
McpClientTemplate template = mcpClientTemplateProvider.getIfAvailable();
|
||||
if (template == null) {
|
||||
throw new IllegalStateException("MCP Client 未启用,请配置 spring.ai.mcp.client.enabled=true 并设置 connections");
|
||||
}
|
||||
return action.apply(template);
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP 数据处理结果。
|
||||
*
|
||||
* @param sourceType 数据来源类型
|
||||
* @param handled 是否已处理
|
||||
* @param data MCP 原始返回数据
|
||||
*/
|
||||
public record McpDemoHandleResult(
|
||||
String sourceType,
|
||||
boolean handled,
|
||||
Map<String, McpToolCallResult> data
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package org.dromara.demo.mcp;
|
||||
|
||||
import org.springframework.ai.mcp.annotation.McpResource;
|
||||
import org.springframework.ai.mcp.annotation.McpTool;
|
||||
import org.springframework.ai.mcp.annotation.McpToolParam;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* MCP Server 演示工具,对外提供业务数据。
|
||||
* <p>
|
||||
* 启动 `ruoyi-admin` 后,本类中的 `@McpTool` 与 `@McpResource` 会被 Spring AI
|
||||
* MCP 注解扫描器注册到当前应用的 MCP Server,默认通过同端口 `/mcp` 暴露。
|
||||
* <p>
|
||||
* 可被外部 MCP Client 调用的示例能力:
|
||||
* <ul>
|
||||
* <li>`demo_get_data`:根据 id 查询一条演示数据</li>
|
||||
* <li>`demo_list_data`:按 limit 查询演示数据列表</li>
|
||||
* <li>`demo://summary`:读取演示模块摘要资源</li>
|
||||
* </ul>
|
||||
* 真实业务中不要在 MCP 工具里直接访问 Mapper,应继续调用业务 Service,
|
||||
* 并保留权限、租户、脱敏、审计和幂等规则。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Component
|
||||
public class McpDemoServerTool {
|
||||
|
||||
/**
|
||||
* 查询一条演示数据。
|
||||
*
|
||||
* @param id 业务主键
|
||||
* @return 演示数据
|
||||
*/
|
||||
@McpTool(name = "demo_get_data", description = "根据业务主键查询演示数据")
|
||||
public McpDemoData getData(@McpToolParam(description = "业务主键") String id) {
|
||||
return new McpDemoData(id, "演示数据-" + id, "ruoyi-demo", 100, LocalDateTime.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询演示数据列表。
|
||||
*
|
||||
* @param limit 返回条数
|
||||
* @return 演示数据列表
|
||||
*/
|
||||
@McpTool(name = "demo_list_data", description = "查询演示数据列表")
|
||||
public List<McpDemoData> listData(@McpToolParam(description = "返回条数") Integer limit) {
|
||||
int size = limit == null || limit < 1 ? 3 : Math.min(limit, 20);
|
||||
return java.util.stream.IntStream.rangeClosed(1, size)
|
||||
.mapToObj(index -> new McpDemoData(String.valueOf(index), "演示数据-" + index, "ruoyi-demo", index * 10, LocalDateTime.now()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取演示资源。
|
||||
*
|
||||
* @return 演示资源内容
|
||||
*/
|
||||
@McpResource(
|
||||
name = "demo_summary",
|
||||
uri = "demo://summary",
|
||||
description = "演示模块摘要资源",
|
||||
mimeType = "text/plain"
|
||||
)
|
||||
public String summary() {
|
||||
return "ruoyi-demo MCP resource summary";
|
||||
}
|
||||
|
||||
/**
|
||||
* MCP 演示数据。
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public record McpDemoData(
|
||||
String id,
|
||||
String name,
|
||||
String source,
|
||||
Integer amount,
|
||||
LocalDateTime syncTime
|
||||
) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.dromara.demo.service;
|
||||
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 导出下拉框Excel示例
|
||||
*
|
||||
* @author Emil.Zhang
|
||||
*/
|
||||
public interface IExportExcelService {
|
||||
|
||||
/**
|
||||
* 导出下拉框
|
||||
*
|
||||
* @param response /
|
||||
*/
|
||||
void exportWithOptions(HttpServletResponse response);
|
||||
|
||||
/**
|
||||
* 自定义导出
|
||||
*
|
||||
* @param response /
|
||||
*/
|
||||
void customExport(HttpServletResponse response);
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
package org.dromara.demo.service;
|
||||
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.demo.domain.bo.TestDemoBo;
|
||||
import org.dromara.demo.domain.vo.TestDemoVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试单表Service接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
public interface ITestDemoService {
|
||||
|
||||
/**
|
||||
* 查询单个
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 测试单表视图对象
|
||||
*/
|
||||
TestDemoVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 分页查询测试单表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 按自定义 SQL 分页查询测试单表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
PageResult<TestDemoVo> customPageList(TestDemoBo bo, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询符合条件的测试单表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 结果列表
|
||||
*/
|
||||
List<TestDemoVo> queryList(TestDemoBo bo);
|
||||
|
||||
/**
|
||||
* 根据新增业务对象插入测试单表
|
||||
*
|
||||
* @param bo 测试单表新增业务对象
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(TestDemoBo bo);
|
||||
|
||||
/**
|
||||
* 根据编辑业务对象修改测试单表
|
||||
*
|
||||
* @param bo 测试单表编辑业务对象
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(TestDemoBo bo);
|
||||
|
||||
/**
|
||||
* 校验并删除数据
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @param isValid 是否校验,true-删除前校验,false-不校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
|
||||
/**
|
||||
* 批量保存测试单表数据。
|
||||
*
|
||||
* @param list 待保存数据
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
Boolean saveBatch(List<TestDemo> list);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package org.dromara.demo.service;
|
||||
|
||||
import org.dromara.demo.domain.bo.TestTreeBo;
|
||||
import org.dromara.demo.domain.vo.TestTreeVo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试树表Service接口
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
public interface ITestTreeService {
|
||||
/**
|
||||
* 查询单个
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 测试树表视图对象
|
||||
*/
|
||||
TestTreeVo queryById(Long id);
|
||||
|
||||
/**
|
||||
* 查询符合条件的测试树表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 结果列表
|
||||
*/
|
||||
List<TestTreeVo> queryList(TestTreeBo bo);
|
||||
|
||||
/**
|
||||
* 根据新增业务对象插入测试树表
|
||||
*
|
||||
* @param bo 测试树表新增业务对象
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(TestTreeBo bo);
|
||||
|
||||
/**
|
||||
* 根据编辑业务对象修改测试树表
|
||||
*
|
||||
* @param bo 测试树表编辑业务对象
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(TestTreeBo bo);
|
||||
|
||||
/**
|
||||
* 校验并删除数据
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @param isValid 是否校验,true-删除前校验,false-不校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
package org.dromara.demo.service.impl;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.Data;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.apache.fesod.sheet.write.metadata.WriteSheet;
|
||||
import org.dromara.common.core.constant.SystemConstants;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.excel.core.DropDownOptions;
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
import org.dromara.common.excel.utils.ExcelWriterWrapper;
|
||||
import org.dromara.demo.domain.vo.ExportDemoVo;
|
||||
import org.dromara.demo.service.IExportExcelService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 导出下拉框Excel示例
|
||||
*
|
||||
* @author Emil.Zhang
|
||||
*/
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class ExportExcelServiceImpl implements IExportExcelService {
|
||||
|
||||
/**
|
||||
* 导出带下拉框选项的 Excel 示例文件。
|
||||
*
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
@Override
|
||||
public void exportWithOptions(HttpServletResponse response) {
|
||||
// 创建表格数据,业务中一般通过数据库查询
|
||||
List<ExportDemoVo> excelDataList = new ArrayList<>();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
// 模拟数据库中的一条数据
|
||||
ExportDemoVo everyRowData = new ExportDemoVo();
|
||||
everyRowData.setNickName("用户-" + i);
|
||||
everyRowData.setUserStatus(SystemConstants.NORMAL);
|
||||
everyRowData.setGender("1");
|
||||
everyRowData.setPhoneNumber(String.format("175%08d", i));
|
||||
everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
|
||||
everyRowData.setProvinceId(i);
|
||||
everyRowData.setCityId(i);
|
||||
everyRowData.setAreaId(i);
|
||||
excelDataList.add(everyRowData);
|
||||
}
|
||||
|
||||
// 通过@ExcelIgnoreUnannotated配合@ExcelProperty合理显示需要的列
|
||||
// 并通过@DropDown注解指定下拉值,或者通过创建ExcelOptions来指定下拉框
|
||||
// 使用ExcelOptions时建议指定列index,防止出现下拉列解析不对齐
|
||||
|
||||
// 首先从数据库中查询下拉框内的可选项
|
||||
// 这里模拟查询结果
|
||||
List<DemoCityData> provinceList = getProvinceList(),
|
||||
cityList = getCityList(provinceList),
|
||||
areaList = getAreaList(cityList);
|
||||
int provinceIndex = 5, cityIndex = 6, areaIndex = 7;
|
||||
|
||||
DropDownOptions provinceToCity = DropDownOptions.buildLinkedOptions(
|
||||
provinceList,
|
||||
provinceIndex,
|
||||
cityList,
|
||||
cityIndex,
|
||||
DemoCityData::getId,
|
||||
DemoCityData::getPid,
|
||||
everyOptions -> DropDownOptions.createOptionValue(
|
||||
everyOptions.getName(),
|
||||
everyOptions.getId()
|
||||
)
|
||||
);
|
||||
|
||||
DropDownOptions cityToArea = DropDownOptions.buildLinkedOptions(
|
||||
cityList,
|
||||
cityIndex,
|
||||
areaList,
|
||||
areaIndex,
|
||||
DemoCityData::getId,
|
||||
DemoCityData::getPid,
|
||||
everyOptions -> DropDownOptions.createOptionValue(
|
||||
everyOptions.getName(),
|
||||
everyOptions.getId()
|
||||
)
|
||||
);
|
||||
|
||||
// 把所有的下拉框存储
|
||||
List<DropDownOptions> options = new ArrayList<>();
|
||||
options.add(provinceToCity);
|
||||
options.add(cityToArea);
|
||||
|
||||
// 到此为止所有的下拉框可选项已全部配置完毕
|
||||
|
||||
// 接下来需要将Excel中的展示数据转换为对应的下拉选
|
||||
List<ExportDemoVo> outList = StreamUtils.toList(excelDataList, everyRowData -> {
|
||||
// 只需要处理没有使用@ExcelDictFormat注解的下拉框
|
||||
// 一般来说,可以直接在数据库查询即查询出省市县信息,这里通过模拟操作赋值
|
||||
everyRowData.setProvince(buildOptions(provinceList, everyRowData.getProvinceId()));
|
||||
everyRowData.setCity(buildOptions(cityList, everyRowData.getCityId()));
|
||||
everyRowData.setArea(buildOptions(areaList, everyRowData.getAreaId()));
|
||||
return everyRowData;
|
||||
});
|
||||
|
||||
ExcelBuilder.of(outList, ExportDemoVo.class)
|
||||
.sheetName("下拉框示例")
|
||||
.options(options)
|
||||
.toResponse(response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建下拉框显示值。
|
||||
*
|
||||
* @param cityDataList 城市演示数据列表
|
||||
* @param id 选项 id
|
||||
* @return 下拉框显示值
|
||||
*/
|
||||
private String buildOptions(List<DemoCityData> cityDataList, Integer id) {
|
||||
Map<Integer, List<DemoCityData>> groupByIdMap =
|
||||
StreamUtils.groupByKey(cityDataList, DemoCityData::getId);
|
||||
if (groupByIdMap.containsKey(id)) {
|
||||
DemoCityData demoCityData = groupByIdMap.get(id).getFirst();
|
||||
return DropDownOptions.createOptionValue(demoCityData.getName(), demoCityData.getId());
|
||||
} else {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟查询数据库操作
|
||||
*
|
||||
* @return /
|
||||
*/
|
||||
private List<DemoCityData> getProvinceList() {
|
||||
List<DemoCityData> provinceList = new ArrayList<>();
|
||||
|
||||
// 实际业务中一般采用数据库读取的形式,这里直接拼接创建
|
||||
provinceList.add(new DemoCityData(0, null, "P100000"));
|
||||
provinceList.add(new DemoCityData(1, null, "P200000"));
|
||||
provinceList.add(new DemoCityData(2, null, "P300000"));
|
||||
|
||||
return provinceList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟查找数据库操作,需要连带查询出省的数据
|
||||
*
|
||||
* @param provinceList 模拟的父省数据
|
||||
* @return /
|
||||
*/
|
||||
private List<DemoCityData> getCityList(List<DemoCityData> provinceList) {
|
||||
List<DemoCityData> cityList = new ArrayList<>();
|
||||
|
||||
// 实际业务中一般采用数据库读取的形式,这里直接拼接创建
|
||||
cityList.add(new DemoCityData(0, 0, "C110000"));
|
||||
cityList.add(new DemoCityData(1, 0, "C120000"));
|
||||
cityList.add(new DemoCityData(2, 1, "C210000"));
|
||||
cityList.add(new DemoCityData(3, 1, "C220000"));
|
||||
cityList.add(new DemoCityData(4, 1, "C230000"));
|
||||
|
||||
selectParentData(provinceList, cityList);
|
||||
|
||||
return cityList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟查找数据库操作,需要连带查询出市的数据
|
||||
*
|
||||
* @param cityList 模拟的父市数据
|
||||
* @return /
|
||||
*/
|
||||
private List<DemoCityData> getAreaList(List<DemoCityData> cityList) {
|
||||
List<DemoCityData> areaList = new ArrayList<>();
|
||||
|
||||
int minCount = 500;
|
||||
int maxCount = 10000;
|
||||
|
||||
// 实际业务中一般采用数据库读取的形式,这里直接拼接创建
|
||||
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
|
||||
areaList.add(new DemoCityData(areaList.size(), 0, String.format("A11%04d", i)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
|
||||
areaList.add(new DemoCityData(areaList.size(), 1, String.format("A12%04d", i)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
|
||||
areaList.add(new DemoCityData(areaList.size(), 2, String.format("A21%04d", i)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
|
||||
areaList.add(new DemoCityData(areaList.size(), 3, String.format("A22%04d", i)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < RandomUtil.randomInt(minCount, maxCount); i++) {
|
||||
areaList.add(new DemoCityData(areaList.size(), 4, String.format("A23%04d", i)));
|
||||
}
|
||||
|
||||
selectParentData(cityList, areaList);
|
||||
|
||||
return areaList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟数据库的查询父数据操作
|
||||
*
|
||||
* @param parentList /
|
||||
* @param sonList /
|
||||
*/
|
||||
private void selectParentData(List<DemoCityData> parentList, List<DemoCityData> sonList) {
|
||||
Map<Integer, List<DemoCityData>> parentGroupByIdMap =
|
||||
StreamUtils.groupByKey(parentList, DemoCityData::getId);
|
||||
|
||||
sonList.forEach(everySon -> {
|
||||
if (parentGroupByIdMap.containsKey(everySon.getPid())) {
|
||||
everySon.setPData(parentGroupByIdMap.get(everySon.getPid()).getFirst());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟的数据库省市县
|
||||
*/
|
||||
@Data
|
||||
private static class DemoCityData {
|
||||
/**
|
||||
* 数据库id字段
|
||||
*/
|
||||
private Integer id;
|
||||
/**
|
||||
* 数据库pid字段
|
||||
*/
|
||||
private Integer pid;
|
||||
/**
|
||||
* 数据库name字段
|
||||
*/
|
||||
private String name;
|
||||
/**
|
||||
* MyBatisPlus连带查询父数据
|
||||
*/
|
||||
private DemoCityData pData;
|
||||
|
||||
/**
|
||||
* 构造城市演示数据。
|
||||
*
|
||||
* @param id 数据库 id
|
||||
* @param pid 父级 id
|
||||
* @param name 名称
|
||||
*/
|
||||
public DemoCityData(Integer id, Integer pid, String name) {
|
||||
this.id = id;
|
||||
this.pid = pid;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 自定义写入多个工作表。
|
||||
*
|
||||
* @param response HTTP 响应
|
||||
*/
|
||||
@Override
|
||||
public void customExport(HttpServletResponse response) {
|
||||
ExcelBuilder.writer(ExportDemoVo.class).sheetName("自定义导出").toResponse(response, wrapper -> {
|
||||
// 创建表格数据,业务中一般通过数据库查询
|
||||
List<ExportDemoVo> excelDataList = new ArrayList<>();
|
||||
for (int i = 0; i < 30; i++) {
|
||||
// 模拟数据库中的一条数据
|
||||
ExportDemoVo everyRowData = new ExportDemoVo();
|
||||
everyRowData.setNickName("用户-" + i);
|
||||
everyRowData.setUserStatus(SystemConstants.NORMAL);
|
||||
everyRowData.setGender("1");
|
||||
everyRowData.setPhoneNumber(String.format("175%08d", i));
|
||||
everyRowData.setEmail(String.format("175%08d", i) + "@163.com");
|
||||
everyRowData.setProvinceId(i);
|
||||
everyRowData.setCityId(i);
|
||||
everyRowData.setAreaId(i);
|
||||
excelDataList.add(everyRowData);
|
||||
}
|
||||
|
||||
// 创建表格
|
||||
WriteSheet sheet = ExcelWriterWrapper.sheetBuilder("自定义导出demo")
|
||||
// 合并单元格
|
||||
// .registerWriteHandler(new CellMergeStrategy(excelDataList, true))
|
||||
.build();
|
||||
|
||||
|
||||
wrapper.write(excelDataList, sheet);
|
||||
|
||||
List<ExportDemoVo> excelDataList2 = new ArrayList<>();
|
||||
for (int i = 0; i < 20; i++) {
|
||||
int index = 1000 + i;
|
||||
// 模拟数据库中的一条数据
|
||||
ExportDemoVo everyRowData = new ExportDemoVo();
|
||||
everyRowData.setNickName("用户-" + index);
|
||||
everyRowData.setUserStatus(SystemConstants.NORMAL);
|
||||
everyRowData.setGender("1");
|
||||
everyRowData.setPhoneNumber(String.format("175%08d", index));
|
||||
everyRowData.setEmail(String.format("175%08d", index) + "@163.com");
|
||||
everyRowData.setProvinceId(index);
|
||||
everyRowData.setCityId(index);
|
||||
everyRowData.setAreaId(index);
|
||||
excelDataList2.add(everyRowData);
|
||||
}
|
||||
|
||||
wrapper.write(excelDataList2, sheet);
|
||||
|
||||
// 或者在同一个excel中创建多个表格
|
||||
// WriteSheet sheet2 = ExcelWriterWrapper.sheetBuilder("自定义导出demo2").build();
|
||||
// wrapper.write(excelDataList2, sheet2);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
package org.dromara.demo.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.demo.domain.TestDemo;
|
||||
import org.dromara.demo.domain.bo.TestDemoBo;
|
||||
import org.dromara.demo.domain.vo.TestDemoVo;
|
||||
import org.dromara.demo.mapper.TestDemoMapper;
|
||||
import org.dromara.demo.service.ITestDemoService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试单表Service业务层处理
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class TestDemoServiceImpl implements ITestDemoService {
|
||||
|
||||
private final TestDemoMapper demoMapper;
|
||||
|
||||
/**
|
||||
* 根据主键查询测试单表详情。
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 测试单表视图对象
|
||||
*/
|
||||
@Override
|
||||
public TestDemoVo queryById(Long id) {
|
||||
return demoMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询测试单表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Override
|
||||
public PageResult<TestDemoVo> queryPageList(TestDemoBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
|
||||
Page<TestDemoVo> result = demoMapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return PageResult.build(result.getRecords(), result.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过自定义 SQL 分页查询测试单表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 分页结果
|
||||
*/
|
||||
@Override
|
||||
public PageResult<TestDemoVo> customPageList(TestDemoBo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<TestDemo> lqw = buildQueryWrapper(bo);
|
||||
Page<TestDemoVo> result = demoMapper.customPageList(pageQuery.build(), lqw);
|
||||
return PageResult.build(result.getRecords(), result.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的测试单表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 结果列表
|
||||
*/
|
||||
@Override
|
||||
public List<TestDemoVo> queryList(TestDemoBo bo) {
|
||||
return demoMapper.selectVoList(buildQueryWrapper(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建测试单表动态查询条件。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 查询条件包装器
|
||||
*/
|
||||
private LambdaQueryWrapper<TestDemo> buildQueryWrapper(TestDemoBo bo) {
|
||||
LambdaQueryWrapper<TestDemo> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(bo.getDeptId() != null, TestDemo::getDeptId, bo.getDeptId());
|
||||
lqw.eq(bo.getUserId() != null, TestDemo::getUserId, bo.getUserId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getTestKey()), TestDemo::getTestKey, bo.getTestKey());
|
||||
lqw.eq(StringUtils.isNotBlank(bo.getValue()), TestDemo::getValue, bo.getValue());
|
||||
lqw.orderByAsc(TestDemo::getId);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增测试单表数据。
|
||||
*
|
||||
* @param bo 新增业务对象
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(TestDemoBo bo) {
|
||||
TestDemo add = MapstructUtils.convert(bo, TestDemo.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = demoMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新测试单表数据。
|
||||
*
|
||||
* @param bo 编辑业务对象
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(TestDemoBo bo) {
|
||||
TestDemo update = MapstructUtils.convert(bo, TestDemo.class);
|
||||
validEntityBeforeSave(update);
|
||||
return demoMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*
|
||||
* @param entity 实体类数据
|
||||
*/
|
||||
private void validEntityBeforeSave(TestDemo entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主键集合删除测试单表数据,并按需执行删除前校验。
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @param isValid 是否执行删除校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// 做一些业务上的校验,判断是否需要校验
|
||||
List<TestDemo> list = demoMapper.selectByIds(ids);
|
||||
if (list.size() != ids.size()) {
|
||||
throw new ServiceException("您没有删除权限!");
|
||||
}
|
||||
}
|
||||
return demoMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量保存测试单表数据。
|
||||
*
|
||||
* @param list 待保存实体列表
|
||||
* @return 是否保存成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean saveBatch(List<TestDemo> list) {
|
||||
return demoMapper.insertBatch(list);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package org.dromara.demo.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.demo.domain.TestTree;
|
||||
import org.dromara.demo.domain.bo.TestTreeBo;
|
||||
import org.dromara.demo.domain.vo.TestTreeVo;
|
||||
import org.dromara.demo.mapper.TestTreeMapper;
|
||||
import org.dromara.demo.service.ITestTreeService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 测试树表Service业务层处理
|
||||
*
|
||||
* @author Lion Li
|
||||
* @date 2021-07-26
|
||||
*/
|
||||
// @DS("slave") // 切换从库查询
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class TestTreeServiceImpl implements ITestTreeService {
|
||||
|
||||
private final TestTreeMapper treeMapper;
|
||||
|
||||
/**
|
||||
* 根据主键查询测试树表详情。
|
||||
*
|
||||
* @param id 主键
|
||||
* @return 测试树表视图对象
|
||||
*/
|
||||
@Override
|
||||
public TestTreeVo queryById(Long id) {
|
||||
return treeMapper.selectVoById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询符合条件的测试树表列表。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 结果列表
|
||||
*/
|
||||
// @DS("slave") // 切换从库查询
|
||||
@Override
|
||||
public List<TestTreeVo> queryList(TestTreeBo bo) {
|
||||
LambdaQueryWrapper<TestTree> lqw = buildQueryWrapper(bo);
|
||||
return treeMapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建测试树表动态查询条件。
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return 查询条件包装器
|
||||
*/
|
||||
private LambdaQueryWrapper<TestTree> buildQueryWrapper(TestTreeBo bo) {
|
||||
LambdaQueryWrapper<TestTree> lqw = Wrappers.lambdaQuery();
|
||||
lqw.eq(bo.getDeptId() != null, TestTree::getDeptId, bo.getDeptId());
|
||||
lqw.eq(bo.getUserId() != null, TestTree::getUserId, bo.getUserId());
|
||||
lqw.like(StringUtils.isNotBlank(bo.getTreeName()), TestTree::getTreeName, bo.getTreeName());
|
||||
lqw.orderByAsc(TestTree::getId);
|
||||
return lqw;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增测试树表数据。
|
||||
*
|
||||
* @param bo 新增业务对象
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(TestTreeBo bo) {
|
||||
TestTree add = MapstructUtils.convert(bo, TestTree.class);
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = treeMapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.setId(add.getId());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新测试树表数据。
|
||||
*
|
||||
* @param bo 编辑业务对象
|
||||
* @return 是否更新成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(TestTreeBo bo) {
|
||||
TestTree update = MapstructUtils.convert(bo, TestTree.class);
|
||||
validEntityBeforeSave(update);
|
||||
return treeMapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*
|
||||
* @param entity 实体类数据
|
||||
*/
|
||||
private void validEntityBeforeSave(TestTree entity) {
|
||||
//TODO 做一些数据校验,如唯一约束
|
||||
}
|
||||
|
||||
/**
|
||||
* 按主键集合删除测试树表数据,并按需执行删除前校验。
|
||||
*
|
||||
* @param ids 主键集合
|
||||
* @param isValid 是否执行删除校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
//TODO 做一些业务上的校验,判断是否需要校验
|
||||
}
|
||||
return treeMapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
package org.dromara.demo.service.impl;
|
||||
@@ -0,0 +1 @@
|
||||
package org.dromara.demo.service;
|
||||
BIN
ruoyi-modules/ruoyi-demo/src/main/resources/excel/单列表.xlsx
Normal file
BIN
ruoyi-modules/ruoyi-demo/src/main/resources/excel/单列表.xlsx
Normal file
Binary file not shown.
BIN
ruoyi-modules/ruoyi-demo/src/main/resources/excel/多sheet列表.xlsx
Normal file
BIN
ruoyi-modules/ruoyi-demo/src/main/resources/excel/多sheet列表.xlsx
Normal file
Binary file not shown.
BIN
ruoyi-modules/ruoyi-demo/src/main/resources/excel/多列表.xlsx
Normal file
BIN
ruoyi-modules/ruoyi-demo/src/main/resources/excel/多列表.xlsx
Normal file
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.demo.mapper.TestDemoMapper">
|
||||
|
||||
<select id="customPageList" resultType="org.dromara.demo.domain.vo.TestDemoVo">
|
||||
SELECT * FROM test_demo ${ew.customSqlSegment}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.demo.mapper.TestTreeMapper">
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,3 @@
|
||||
java包使用 `.` 分割 resource 目录使用 `/` 分割
|
||||
<br>
|
||||
此文件目的 防止文件夹粘连找不到 `xml` 文件
|
||||
90
ruoyi-modules/ruoyi-gen/pom.xml
Normal file
90
ruoyi-modules/ruoyi-gen/pom.xml
Normal file
@@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-gen</artifactId>
|
||||
|
||||
<description>
|
||||
generator 代码生成
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 接口模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-doc</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-mybatis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- web服务 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 日志记录 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-log</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- FreeMarker代码生成模板 -->
|
||||
<dependency>
|
||||
<groupId>org.freemarker</groupId>
|
||||
<artifactId>freemarker</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- AnyLine Spring JDBC 环境 -->
|
||||
<dependency>
|
||||
<groupId>org.anyline</groupId>
|
||||
<artifactId>anyline-environment-spring-data-jdbc</artifactId>
|
||||
<version>${anyline.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- AnyLine MySQL 驱动 -->
|
||||
<dependency>
|
||||
<groupId>org.anyline</groupId>
|
||||
<artifactId>anyline-data-jdbc-mysql</artifactId>
|
||||
<version>${anyline.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- anyline支持100+种类型数据库 添加对应的jdbc依赖与anyline对应数据库依赖包即可 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.anyline</groupId>-->
|
||||
<!-- <artifactId>anyline-data-jdbc-oracle</artifactId>-->
|
||||
<!-- <version>${anyline.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.anyline</groupId>-->
|
||||
<!-- <artifactId>anyline-data-jdbc-postgresql</artifactId>-->
|
||||
<!-- <version>${anyline.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.anyline</groupId>-->
|
||||
<!-- <artifactId>anyline-data-jdbc-mssql</artifactId>-->
|
||||
<!-- <version>${anyline.version}</version>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,111 @@
|
||||
package org.dromara.gen.config;
|
||||
|
||||
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
|
||||
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.anyline.data.datasource.DataSourceMonitor;
|
||||
import org.anyline.data.runtime.DataRuntime;
|
||||
import org.anyline.util.ConfigTable;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import org.springframework.jdbc.datasource.DataSourceUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DatabaseMetaData;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* anyline 适配 动态数据源改造
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class MyBatisDataSourceMonitor implements DataSourceMonitor {
|
||||
|
||||
/**
|
||||
* 初始化 anyline 与动态数据源联动所需的元数据解析策略。
|
||||
*/
|
||||
public MyBatisDataSourceMonitor() {
|
||||
// 调整执行模式为自定义
|
||||
ConfigTable.KEEP_ADAPTER = 2;
|
||||
// 禁用缓存
|
||||
ConfigTable.METADATA_CACHE_SCOPE = 0;
|
||||
}
|
||||
|
||||
private final Map<String, String> features = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 数据源特征 用来定准 adapter 包含数据库或JDBC协议关键字<br/>
|
||||
* 一般会通过 产品名_url 合成 如果返回null 上层方法会通过driver_产品名_url合成
|
||||
*
|
||||
* @param datasource 数据源
|
||||
* @return String 返回null由上层自动提取
|
||||
*/
|
||||
@Override
|
||||
public String feature(DataRuntime runtime, Object datasource) {
|
||||
String feature = null;
|
||||
if (datasource instanceof JdbcTemplate jdbc) {
|
||||
DataSource ds = jdbc.getDataSource();
|
||||
if (ds instanceof DynamicRoutingDataSource) {
|
||||
String key = DynamicDataSourceContextHolder.peek();
|
||||
feature = features.get(key);
|
||||
if (null == feature) {
|
||||
Connection con = null;
|
||||
try {
|
||||
con = DataSourceUtils.getConnection(ds);
|
||||
DatabaseMetaData meta = con.getMetaData();
|
||||
String url = meta.getURL();
|
||||
feature = meta.getDatabaseProductName().toLowerCase().replace(" ", "") + "_" + url;
|
||||
features.put(key, feature);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(), e);
|
||||
} finally {
|
||||
if (null != con && !DataSourceUtils.isConnectionTransactional(con, ds)) {
|
||||
DataSourceUtils.releaseConnection(con, ds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return feature;
|
||||
}
|
||||
|
||||
/**
|
||||
* 数据源唯一标识 如果不实现则默认feature
|
||||
*
|
||||
* @param runtime 数据运行时上下文
|
||||
* @param datasource 数据源
|
||||
* @return String 返回null由上层自动提取
|
||||
*/
|
||||
@Override
|
||||
public String key(DataRuntime runtime, Object datasource) {
|
||||
if (datasource instanceof JdbcTemplate jdbc) {
|
||||
DataSource ds = jdbc.getDataSource();
|
||||
if (ds instanceof DynamicRoutingDataSource) {
|
||||
return DynamicDataSourceContextHolder.peek();
|
||||
}
|
||||
}
|
||||
return runtime.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* ConfigTable.KEEP_ADAPTER=2 : 根据当前接口判断是否保持同一个数据源绑定同一个adapter<br/>
|
||||
* DynamicRoutingDataSource类型的返回false,因为同一个DynamicRoutingDataSource可能对应多类数据库, 如果项目中只有一种数据库 应该直接返回true
|
||||
*
|
||||
* @param runtime 数据运行时上下文
|
||||
* @param datasource 数据源
|
||||
* @return boolean
|
||||
*/
|
||||
@Override
|
||||
public boolean keepAdapter(DataRuntime runtime, Object datasource) {
|
||||
if (datasource instanceof JdbcTemplate jdbc) {
|
||||
DataSource ds = jdbc.getDataSource();
|
||||
return !(ds instanceof DynamicRoutingDataSource);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.dromara.gen.config.properties;
|
||||
|
||||
import lombok.Data;
|
||||
import org.dromara.common.core.factory.YmlPropertySourceFactory;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.context.annotation.PropertySource;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 代码生成配置属性
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "gen")
|
||||
@PropertySource(value = "classpath:generator.yml", factory = YmlPropertySourceFactory.class)
|
||||
public class GenProperties {
|
||||
|
||||
/**
|
||||
* 作者
|
||||
*/
|
||||
private String author;
|
||||
|
||||
/**
|
||||
* 生成包路径
|
||||
*/
|
||||
private String packageName;
|
||||
|
||||
/**
|
||||
* 自动去除表前缀,默认是false
|
||||
*/
|
||||
private boolean autoRemovePre = false;
|
||||
|
||||
/**
|
||||
* 表前缀(类名不会包含表前缀)
|
||||
*/
|
||||
private String tablePrefix;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package org.dromara.gen.constant;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 代码生成通用常量
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public interface GenConstants {
|
||||
/**
|
||||
* 单表(增删改查)
|
||||
*/
|
||||
String TPL_CRUD = "crud";
|
||||
|
||||
/**
|
||||
* 树表(增删改查)
|
||||
*/
|
||||
String TPL_TREE = "tree";
|
||||
|
||||
/**
|
||||
* 树编码字段
|
||||
*/
|
||||
String TREE_CODE = "treeCode";
|
||||
|
||||
/**
|
||||
* 树父编码字段
|
||||
*/
|
||||
String TREE_PARENT_CODE = "treeParentCode";
|
||||
|
||||
/**
|
||||
* 树名称字段
|
||||
*/
|
||||
String TREE_NAME = "treeName";
|
||||
|
||||
/**
|
||||
* 上级菜单ID字段
|
||||
*/
|
||||
String PARENT_MENU_ID = "parentMenuId";
|
||||
|
||||
/**
|
||||
* 上级菜单名称字段
|
||||
*/
|
||||
String PARENT_MENU_NAME = "parentMenuName";
|
||||
|
||||
/**
|
||||
* 是否启用导出能力
|
||||
*/
|
||||
String ENABLE_EXPORT = "enableExport";
|
||||
|
||||
/**
|
||||
* 是否启用状态切换能力
|
||||
*/
|
||||
String ENABLE_STATUS = "enableStatus";
|
||||
|
||||
/**
|
||||
* 状态字段
|
||||
*/
|
||||
String STATUS_FIELD = "statusField";
|
||||
|
||||
/**
|
||||
* 是否启用组合唯一校验
|
||||
*/
|
||||
String ENABLE_UNIQUE = "enableUnique";
|
||||
|
||||
/**
|
||||
* 组合唯一字段
|
||||
*/
|
||||
String UNIQUE_FIELDS = "uniqueFields";
|
||||
|
||||
/**
|
||||
* 是否启用排序调整能力
|
||||
*/
|
||||
String ENABLE_SORT = "enableSort";
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
String SORT_FIELD = "sortField";
|
||||
|
||||
/**
|
||||
* 默认前端模板类型,对应模板根目录下的 vue 目录。
|
||||
*/
|
||||
String FRONTEND_TYPE_VUE = "vue";
|
||||
|
||||
/**
|
||||
* React 前端模板类型,对应模板根目录下的 react 目录。
|
||||
*/
|
||||
String FRONTEND_TYPE_REACT = "react";
|
||||
|
||||
/**
|
||||
* 树根节点值
|
||||
*/
|
||||
String TREE_ROOT_VALUE = "treeRootValue";
|
||||
|
||||
/**
|
||||
* 树祖级字段
|
||||
*/
|
||||
String TREE_ANCESTORS = "treeAncestors";
|
||||
|
||||
/**
|
||||
* 树排序字段
|
||||
*/
|
||||
String TREE_ORDER_FIELD = "treeOrderField";
|
||||
|
||||
/**
|
||||
* 数据库字符串类型
|
||||
*/
|
||||
String[] COLUMNTYPE_STR = {"char", "varchar", "enum", "set", "nchar", "nvarchar", "varchar2", "nvarchar2",
|
||||
"long varchar", "long nvarchar", "uniqueidentifier", "uuid", "json", "jsonb", "xml"};
|
||||
|
||||
/**
|
||||
* 数据库文本类型
|
||||
*/
|
||||
String[] COLUMNTYPE_TEXT = {"tinytext", "text", "mediumtext", "longtext", "binary", "varbinary", "blob",
|
||||
"ntext", "image", "bytea", "clob", "nclob", "long", "long raw", "raw"};
|
||||
|
||||
/**
|
||||
* 数据库时间类型
|
||||
*/
|
||||
String[] COLUMNTYPE_TIME = {"datetime", "time", "date", "timestamp", "year", "interval",
|
||||
"smalldatetime", "datetime2", "datetimeoffset", "timestamptz", "timestamp with time zone",
|
||||
"timestamp without time zone", "time with time zone", "time without time zone"};
|
||||
|
||||
/**
|
||||
* 数据库数字类型
|
||||
*/
|
||||
String[] COLUMNTYPE_NUMBER = {"tinyint", "smallint", "mediumint", "int", "int2", "int4", "int8", "number", "integer",
|
||||
"bit", "boolean", "bool", "bigint", "float", "float4", "float8", "double", "decimal", "dec", "fixed",
|
||||
"numeric", "real", "double precision", "binary_float", "binary_double", "smallserial", "serial",
|
||||
"serial2", "serial4", "serial8", "bigserial", "money", "smallmoney"};
|
||||
|
||||
/**
|
||||
* BO对象 不需要添加字段
|
||||
*/
|
||||
String[] COLUMNNAME_NOT_ADD = {"del_flag", "version"};
|
||||
|
||||
/**
|
||||
* BO对象 不需要编辑字段
|
||||
*/
|
||||
String[] COLUMNNAME_NOT_EDIT = {"del_flag", "version"};
|
||||
|
||||
/**
|
||||
* VO对象 不需要返回字段
|
||||
*/
|
||||
String[] COLUMNNAME_NOT_LIST = {"create_dept", "create_by", "create_time", "del_flag", "update_by",
|
||||
"update_time", "version"};
|
||||
|
||||
/**
|
||||
* BO对象 不需要查询字段
|
||||
*/
|
||||
String[] COLUMNNAME_NOT_QUERY = {"id", "del_flag", "remark", "version"};
|
||||
|
||||
/**
|
||||
* Entity基类字段
|
||||
*/
|
||||
String[] BASE_ENTITY = {"createDept", "createBy", "createTime", "updateBy", "updateTime"};
|
||||
|
||||
/**
|
||||
* 文本框
|
||||
*/
|
||||
String HTML_INPUT = "input";
|
||||
|
||||
/**
|
||||
* 文本域
|
||||
*/
|
||||
String HTML_TEXTAREA = "textarea";
|
||||
|
||||
/**
|
||||
* 下拉框
|
||||
*/
|
||||
String HTML_SELECT = "select";
|
||||
|
||||
/**
|
||||
* 单选框
|
||||
*/
|
||||
String HTML_RADIO = "radio";
|
||||
|
||||
/**
|
||||
* 复选框
|
||||
*/
|
||||
String HTML_CHECKBOX = "checkbox";
|
||||
|
||||
/**
|
||||
* 日期控件
|
||||
*/
|
||||
String HTML_DATETIME = "datetime";
|
||||
|
||||
/**
|
||||
* 开关控件
|
||||
*/
|
||||
String HTML_SWITCH = "switch";
|
||||
|
||||
/**
|
||||
* 数字输入控件
|
||||
*/
|
||||
String HTML_INPUT_NUMBER = "inputNumber";
|
||||
|
||||
/**
|
||||
* 图片上传控件
|
||||
*/
|
||||
String HTML_IMAGE_UPLOAD = "imageUpload";
|
||||
|
||||
/**
|
||||
* 文件上传控件
|
||||
*/
|
||||
String HTML_FILE_UPLOAD = "fileUpload";
|
||||
|
||||
/**
|
||||
* 富文本控件
|
||||
*/
|
||||
String HTML_EDITOR = "editor";
|
||||
|
||||
/**
|
||||
* 字符串类型
|
||||
*/
|
||||
String TYPE_STRING = "String";
|
||||
|
||||
/**
|
||||
* 整型
|
||||
*/
|
||||
String TYPE_INTEGER = "Integer";
|
||||
|
||||
/**
|
||||
* 长整型
|
||||
*/
|
||||
String TYPE_LONG = "Long";
|
||||
|
||||
/**
|
||||
* 浮点型
|
||||
*/
|
||||
String TYPE_DOUBLE = "Double";
|
||||
|
||||
/**
|
||||
* 单精度浮点型
|
||||
*/
|
||||
String TYPE_FLOAT = "Float";
|
||||
|
||||
/**
|
||||
* 高精度计算类型
|
||||
*/
|
||||
String TYPE_BIGDECIMAL = "BigDecimal";
|
||||
|
||||
/**
|
||||
* 布尔类型
|
||||
*/
|
||||
String TYPE_BOOLEAN = "Boolean";
|
||||
|
||||
/**
|
||||
* 时间类型
|
||||
*/
|
||||
String TYPE_DATE = "LocalDateTime";
|
||||
|
||||
/**
|
||||
* 模糊查询
|
||||
*/
|
||||
String QUERY_LIKE = "LIKE";
|
||||
|
||||
/**
|
||||
* 相等查询
|
||||
*/
|
||||
String QUERY_EQ = "EQ";
|
||||
/**
|
||||
* 范围查询
|
||||
*/
|
||||
String QUERY_BETWEEN = "BETWEEN";
|
||||
|
||||
/**
|
||||
* 必填标识,对应前端表单规则中的必填字段配置。
|
||||
*/
|
||||
String REQUIRE = "1";
|
||||
|
||||
// 模板根路径与后缀
|
||||
String TEMPLATE_ROOT_PATH = "fm";
|
||||
String TEMPLATE_FILE_SUFFIX = ".ftl";
|
||||
String TEMPLATE_RESOURCE_PREFIX = "classpath*:";
|
||||
String TEMPLATE_PAGE_PATTERN_SUFFIX = ".*" + TEMPLATE_FILE_SUFFIX;
|
||||
// 后端源码模板
|
||||
String JAVA_TEMPLATE_ROOT_PATH = TEMPLATE_ROOT_PATH + "/java";
|
||||
String JAVA_DOMAIN_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/domain.java" + TEMPLATE_FILE_SUFFIX;
|
||||
String JAVA_VO_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/vo.java" + TEMPLATE_FILE_SUFFIX;
|
||||
String JAVA_BO_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/bo.java" + TEMPLATE_FILE_SUFFIX;
|
||||
String JAVA_MAPPER_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/mapper.java" + TEMPLATE_FILE_SUFFIX;
|
||||
String JAVA_SERVICE_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/service.java" + TEMPLATE_FILE_SUFFIX;
|
||||
String JAVA_SERVICE_IMPL_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/serviceImpl.java" + TEMPLATE_FILE_SUFFIX;
|
||||
String JAVA_CONTROLLER_TEMPLATE_PATH = JAVA_TEMPLATE_ROOT_PATH + "/controller.java" + TEMPLATE_FILE_SUFFIX;
|
||||
// MyBatis MapperXML 模板
|
||||
String XML_TEMPLATE_ROOT_PATH = TEMPLATE_ROOT_PATH + "/xml";
|
||||
String XML_MAPPER_TEMPLATE_PATH = XML_TEMPLATE_ROOT_PATH + "/mapper.xml" + TEMPLATE_FILE_SUFFIX;
|
||||
// 前端源码模板
|
||||
String FRONTEND_API_TEMPLATE_NAME = "api.ts" + TEMPLATE_FILE_SUFFIX;
|
||||
String FRONTEND_TYPES_TEMPLATE_NAME = "types.ts" + TEMPLATE_FILE_SUFFIX;
|
||||
String FRONTEND_INDEX_TEMPLATE_PREFIX = "index";
|
||||
String FRONTEND_INDEX_TREE_TEMPLATE_PREFIX = "index-tree";
|
||||
// 数据库SQL模板
|
||||
String SQL_TEMPLATE_ROOT_PATH = TEMPLATE_ROOT_PATH + "/sql";
|
||||
String SQL_ORACLE_TEMPLATE_PATH = SQL_TEMPLATE_ROOT_PATH + "/oracle.sql" + TEMPLATE_FILE_SUFFIX;
|
||||
String SQL_POSTGRES_TEMPLATE_PATH = SQL_TEMPLATE_ROOT_PATH + "/postgres.sql" + TEMPLATE_FILE_SUFFIX;
|
||||
String SQL_SQLSERVER_TEMPLATE_PATH = SQL_TEMPLATE_ROOT_PATH + "/sqlserver.sql" + TEMPLATE_FILE_SUFFIX;
|
||||
String SQL_MYSQL_TEMPLATE_PATH = SQL_TEMPLATE_ROOT_PATH + "/mysql.sql" + TEMPLATE_FILE_SUFFIX;
|
||||
|
||||
/**
|
||||
* 固定模板路径集合,前端模板按 frontendType 动态解析。
|
||||
*/
|
||||
Set<String> TEMPLATE_PATHS = CollUtil.newHashSet(
|
||||
JAVA_DOMAIN_TEMPLATE_PATH
|
||||
, JAVA_VO_TEMPLATE_PATH
|
||||
, JAVA_BO_TEMPLATE_PATH
|
||||
, JAVA_MAPPER_TEMPLATE_PATH
|
||||
, JAVA_SERVICE_TEMPLATE_PATH
|
||||
, JAVA_SERVICE_IMPL_TEMPLATE_PATH
|
||||
, JAVA_CONTROLLER_TEMPLATE_PATH
|
||||
, XML_MAPPER_TEMPLATE_PATH
|
||||
, SQL_ORACLE_TEMPLATE_PATH
|
||||
, SQL_POSTGRES_TEMPLATE_PATH
|
||||
, SQL_SQLSERVER_TEMPLATE_PATH
|
||||
, SQL_MYSQL_TEMPLATE_PATH
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package org.dromara.gen.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.baomidou.lock.annotation.Lock4j;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.gen.domain.GenTable;
|
||||
import org.dromara.gen.domain.GenTableColumn;
|
||||
import org.dromara.gen.service.IGenTableService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 代码生成 操作处理
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/tool/gen")
|
||||
public class GenController extends BaseController {
|
||||
|
||||
private final IGenTableService genTableService;
|
||||
|
||||
/**
|
||||
* 分页查询代码生成业务列表。
|
||||
*
|
||||
* @param genTable 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 代码生成列表
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:list")
|
||||
@GetMapping("/list")
|
||||
public R<PageResult<GenTable>> genList(GenTable genTable, PageQuery pageQuery) {
|
||||
return R.ok(genTableService.selectPageGenTableList(genTable, pageQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改代码生成业务
|
||||
*
|
||||
* @param tableId 表ID
|
||||
* @return 表与字段信息
|
||||
*/
|
||||
@RepeatSubmit()
|
||||
@SaCheckPermission("tool:gen:query")
|
||||
@GetMapping(value = "/{tableId}")
|
||||
public R<Map<String, Object>> getInfo(@PathVariable Long tableId) {
|
||||
GenTable table = genTableService.selectGenTableById(tableId);
|
||||
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
|
||||
Map<String, Object> map = new HashMap<>(2);
|
||||
map.put("info", table);
|
||||
map.put("rows", list);
|
||||
return R.ok(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询数据库表列表。
|
||||
*
|
||||
* @param genTable 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 数据库表列表
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:list")
|
||||
@GetMapping("/db/list")
|
||||
public R<PageResult<GenTable>> dataList(GenTable genTable, PageQuery pageQuery) {
|
||||
return R.ok(genTableService.selectPageDbTableList(genTable, pageQuery));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据表字段列表
|
||||
*
|
||||
* @param tableId 表ID
|
||||
* @return 字段列表
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:list")
|
||||
@GetMapping(value = "/column/{tableId}")
|
||||
public R<PageResult<GenTableColumn>> columnList(@PathVariable("tableId") Long tableId) {
|
||||
List<GenTableColumn> list = genTableService.selectGenTableColumnListByTableId(tableId);
|
||||
return R.ok(PageResult.build(list));
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入表结构(保存)
|
||||
*
|
||||
* @param tables 表名串
|
||||
* @param dataName 数据源名称
|
||||
* @return 操作结果
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:import")
|
||||
@Log(title = "代码生成", businessType = BusinessType.IMPORT)
|
||||
@Lock4j(keys = {"#dataName"}, acquireTimeout = 10000)
|
||||
@RepeatSubmit()
|
||||
@PostMapping("/importTable")
|
||||
public R<Void> importTableSave(String tables, String dataName) {
|
||||
String[] tableNames = Convert.toStrArray(tables);
|
||||
// 查询表信息
|
||||
List<GenTable> tableList = genTableService.selectDbTableListByNames(tableNames, dataName);
|
||||
genTableService.importGenTable(tableList, dataName);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存代码生成业务配置。
|
||||
*
|
||||
* @param genTable 业务配置
|
||||
* @return 操作结果
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:edit")
|
||||
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping
|
||||
public R<Void> editSave(@Validated @RequestBody GenTable genTable) {
|
||||
genTableService.validateEdit(genTable);
|
||||
genTableService.updateGenTable(genTable);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除代码生成
|
||||
*
|
||||
* @param tableIds 表ID串
|
||||
* @return 操作结果
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:remove")
|
||||
@Log(title = "代码生成", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{tableIds}")
|
||||
public R<Void> remove(@PathVariable Long[] tableIds) {
|
||||
genTableService.deleteGenTableByIds(tableIds);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览代码
|
||||
*
|
||||
* @param tableId 表ID
|
||||
* @return 模板路径与生成代码内容映射
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:preview")
|
||||
@GetMapping("/preview/{tableId}")
|
||||
public R<Map<String, String>> preview(@PathVariable("tableId") Long tableId) throws IOException {
|
||||
Map<String, String> dataMap = genTableService.previewCode(tableId);
|
||||
return R.ok(dataMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成代码(下载方式)
|
||||
*
|
||||
* @param response HTTP 响应
|
||||
* @param tableId 表ID
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:code")
|
||||
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
|
||||
@GetMapping("/download/{tableId}")
|
||||
public void download(HttpServletResponse response, @PathVariable("tableId") Long tableId) throws IOException {
|
||||
byte[] data = genTableService.downloadCode(tableId);
|
||||
genCode(response, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步数据库
|
||||
*
|
||||
* @param tableId 表ID
|
||||
* @return 操作结果
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:edit")
|
||||
@Log(title = "代码生成", businessType = BusinessType.UPDATE)
|
||||
@Lock4j(keys = {"#tableId"}, acquireTimeout = 5000)
|
||||
@GetMapping("/synchDb/{tableId}")
|
||||
public R<Void> synchDb(@PathVariable("tableId") Long tableId) {
|
||||
genTableService.synchDb(tableId);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成代码
|
||||
*
|
||||
* @param response HTTP 响应
|
||||
* @param tableIdStr 表ID串
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:code")
|
||||
@Log(title = "代码生成", businessType = BusinessType.GENCODE)
|
||||
@GetMapping("/batchGenCode")
|
||||
public void batchGenCode(HttpServletResponse response, String tableIdStr) throws IOException {
|
||||
String[] tableIds = Convert.toStrArray(tableIdStr);
|
||||
byte[] data = genTableService.downloadCode(tableIds);
|
||||
genCode(response, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将生成结果写出为 zip 文件流。
|
||||
*
|
||||
* @param response HTTP 响应
|
||||
* @param data zip 二进制数据
|
||||
*/
|
||||
private void genCode(HttpServletResponse response, byte[] data) throws IOException {
|
||||
response.reset();
|
||||
response.addHeader("Access-Control-Allow-Origin", "*");
|
||||
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"ruoyi.zip\"");
|
||||
response.addHeader("Content-Length", "" + data.length);
|
||||
response.setContentType("application/octet-stream; charset=UTF-8");
|
||||
IoUtil.write(response.getOutputStream(), false, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前可用数据源名称列表。
|
||||
*
|
||||
* @return 数据源名称集合
|
||||
*/
|
||||
@SaCheckPermission("tool:gen:list")
|
||||
@GetMapping(value = "/getDataNames")
|
||||
public R<Object> getCurrentDataSourceNameList() {
|
||||
return R.ok(DataBaseHelper.getDataSourceNameList());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,282 @@
|
||||
package org.dromara.gen.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import jakarta.validation.Valid;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.gen.constant.GenConstants;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 业务表 gen_table
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("gen_table")
|
||||
public class GenTable extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId(value = "table_id")
|
||||
private Long tableId;
|
||||
|
||||
/**
|
||||
* 数据源名称
|
||||
*/
|
||||
@NotBlank(message = "数据源名称不能为空")
|
||||
private String dataName;
|
||||
|
||||
/**
|
||||
* 表名称
|
||||
*/
|
||||
@NotBlank(message = "表名称不能为空")
|
||||
private String tableName;
|
||||
|
||||
/**
|
||||
* 表描述
|
||||
*/
|
||||
@NotBlank(message = "表描述不能为空")
|
||||
private String tableComment;
|
||||
|
||||
/**
|
||||
* 实体类名称(首字母大写)
|
||||
*/
|
||||
@NotBlank(message = "实体类名称不能为空")
|
||||
private String className;
|
||||
|
||||
/**
|
||||
* 使用的模板(crud单表操作 tree树表操作)
|
||||
*/
|
||||
private String tplCategory;
|
||||
|
||||
/**
|
||||
* 前端模板类型,对应 fm 下的模板目录
|
||||
*/
|
||||
private String frontendType;
|
||||
|
||||
/**
|
||||
* 生成包路径
|
||||
*/
|
||||
@NotBlank(message = "生成包路径不能为空")
|
||||
private String packageName;
|
||||
|
||||
/**
|
||||
* 生成模块名
|
||||
*/
|
||||
@NotBlank(message = "生成模块名不能为空")
|
||||
private String moduleName;
|
||||
|
||||
/**
|
||||
* 生成业务名
|
||||
*/
|
||||
@NotBlank(message = "生成业务名不能为空")
|
||||
private String businessName;
|
||||
|
||||
/**
|
||||
* 生成功能名
|
||||
*/
|
||||
@NotBlank(message = "生成功能名不能为空")
|
||||
private String functionName;
|
||||
|
||||
/**
|
||||
* 生成作者
|
||||
*/
|
||||
@NotBlank(message = "作者不能为空")
|
||||
private String functionAuthor;
|
||||
|
||||
/**
|
||||
* 主键信息
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private GenTableColumn pkColumn;
|
||||
|
||||
/**
|
||||
* 表列信息
|
||||
*/
|
||||
@Valid
|
||||
@TableField(exist = false)
|
||||
private List<GenTableColumn> columns;
|
||||
|
||||
/**
|
||||
* 其它生成选项
|
||||
*/
|
||||
private String options;
|
||||
|
||||
/**
|
||||
* 备注
|
||||
*/
|
||||
private String remark;
|
||||
|
||||
/**
|
||||
* 树编码字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String treeCode;
|
||||
|
||||
/**
|
||||
* 树父编码字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String treeParentCode;
|
||||
|
||||
/**
|
||||
* 树名称字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String treeName;
|
||||
|
||||
/**
|
||||
* 菜单id列表
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> menuIds;
|
||||
|
||||
/**
|
||||
* 上级菜单ID字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Long parentMenuId;
|
||||
|
||||
/**
|
||||
* 上级菜单名称字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String parentMenuName;
|
||||
|
||||
/**
|
||||
* 是否启用导出
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean enableExport;
|
||||
|
||||
/**
|
||||
* 是否启用状态切换
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean enableStatus;
|
||||
|
||||
/**
|
||||
* 状态字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String statusField;
|
||||
|
||||
/**
|
||||
* 是否启用组合唯一校验
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean enableUnique;
|
||||
|
||||
/**
|
||||
* 组合唯一字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<String> uniqueFields;
|
||||
|
||||
/**
|
||||
* 是否启用排序调整
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Boolean enableSort;
|
||||
|
||||
/**
|
||||
* 排序字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String sortField;
|
||||
|
||||
/**
|
||||
* 树根节点值
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String treeRootValue;
|
||||
|
||||
/**
|
||||
* 树祖级字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String treeAncestorsField;
|
||||
|
||||
/**
|
||||
* 树排序字段
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private String treeOrderField;
|
||||
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private Map<String, Object> params = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 判断当前业务表是否采用树表模板。
|
||||
*
|
||||
* @return 树表模板返回 {@code true}
|
||||
*/
|
||||
public boolean isTree() {
|
||||
return isTree(this.tplCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板分类判断是否为树表模板。
|
||||
*
|
||||
* @param tplCategory 模板分类
|
||||
* @return 树表模板返回 {@code true}
|
||||
*/
|
||||
public static boolean isTree(String tplCategory) {
|
||||
return tplCategory != null && StringUtils.equals(GenConstants.TPL_TREE, tplCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前业务表是否采用普通 CRUD 模板。
|
||||
*
|
||||
* @return 普通 CRUD 模板返回 {@code true}
|
||||
*/
|
||||
public boolean isCrud() {
|
||||
return isCrud(this.tplCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板分类判断是否为普通 CRUD 模板。
|
||||
*
|
||||
* @param tplCategory 模板分类
|
||||
* @return 普通 CRUD 模板返回 {@code true}
|
||||
*/
|
||||
public static boolean isCrud(String tplCategory) {
|
||||
return tplCategory != null && StringUtils.equals(GenConstants.TPL_CRUD, tplCategory);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断指定 Java 字段是否属于基类公共字段。
|
||||
*
|
||||
* @param javaField Java 字段名
|
||||
* @return 基类公共字段返回 {@code true}
|
||||
*/
|
||||
public boolean isSuperColumn(String javaField) {
|
||||
return isSuperColumn(this.tplCategory, javaField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板分类与字段名判断是否属于基类公共字段。
|
||||
*
|
||||
* @param tplCategory 模板分类
|
||||
* @param javaField Java 字段名
|
||||
* @return 基类公共字段返回 {@code true}
|
||||
*/
|
||||
public static boolean isSuperColumn(String tplCategory, String javaField) {
|
||||
return StringUtils.equalsAnyIgnoreCase(javaField, GenConstants.BASE_ENTITY);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
package org.dromara.gen.domain;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.apache.ibatis.type.JdbcType;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import org.dromara.gen.constant.GenConstants;
|
||||
|
||||
/**
|
||||
* 代码生成业务字段表 gen_table_column
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("gen_table_column")
|
||||
public class GenTableColumn extends BaseEntity {
|
||||
|
||||
/**
|
||||
* 编号
|
||||
*/
|
||||
@TableId(value = "column_id")
|
||||
private Long columnId;
|
||||
|
||||
/**
|
||||
* 归属表编号
|
||||
*/
|
||||
private Long tableId;
|
||||
|
||||
/**
|
||||
* 列名称
|
||||
*/
|
||||
private String columnName;
|
||||
|
||||
/**
|
||||
* 列描述
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String columnComment;
|
||||
|
||||
/**
|
||||
* 列类型
|
||||
*/
|
||||
private String columnType;
|
||||
|
||||
/**
|
||||
* JAVA类型
|
||||
*/
|
||||
private String javaType;
|
||||
|
||||
/**
|
||||
* JAVA字段名
|
||||
*/
|
||||
@NotBlank(message = "Java属性不能为空")
|
||||
private String javaField;
|
||||
|
||||
/**
|
||||
* 是否主键(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isPk;
|
||||
|
||||
/**
|
||||
* 是否自增(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isIncrement;
|
||||
|
||||
/**
|
||||
* 是否必填(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isRequired;
|
||||
|
||||
/**
|
||||
* 是否为插入字段(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isInsert;
|
||||
|
||||
/**
|
||||
* 是否编辑字段(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isEdit;
|
||||
|
||||
/**
|
||||
* 是否列表字段(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isList;
|
||||
|
||||
/**
|
||||
* 是否查询字段(1是)
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String isQuery;
|
||||
|
||||
/**
|
||||
* 查询方式(EQ等于、NE不等于、GT大于、LT小于、LIKE模糊、BETWEEN范围)
|
||||
*/
|
||||
private String queryType;
|
||||
|
||||
/**
|
||||
* 显示类型(input文本框、textarea文本域、select下拉框、checkbox复选框、radio单选框、datetime日期控件、image图片上传控件、upload文件上传控件、editor富文本控件)
|
||||
*/
|
||||
private String htmlType;
|
||||
|
||||
/**
|
||||
* 字典类型
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS, jdbcType = JdbcType.VARCHAR)
|
||||
private String dictType;
|
||||
|
||||
/**
|
||||
* 排序
|
||||
*/
|
||||
private Integer sort;
|
||||
|
||||
/**
|
||||
* 获取首字母大写后的 Java 字段名。
|
||||
*
|
||||
* @return 首字母大写的 Java 字段名
|
||||
*/
|
||||
public String getCapJavaField() {
|
||||
return StringUtils.capitalize(javaField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取适合界面展示的字段注释,去除括号内的枚举说明。
|
||||
*
|
||||
* @return 字段展示名称
|
||||
*/
|
||||
public String getColumnLabel() {
|
||||
int index = StringUtils.indexOf(this.columnComment, "(");
|
||||
if (index != StringUtils.INDEX_NOT_FOUND) {
|
||||
return StringUtils.substring(this.columnComment, 0, index);
|
||||
}
|
||||
return this.columnComment;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 TypeScript 字段类型。
|
||||
*
|
||||
* @return TypeScript 类型
|
||||
*/
|
||||
public String getTsType() {
|
||||
if (StringUtils.containsAny(this.javaField, "id", "Id")) {
|
||||
return "string | number";
|
||||
}
|
||||
if (StringUtils.equalsAny(this.javaType,
|
||||
GenConstants.TYPE_LONG, GenConstants.TYPE_INTEGER, GenConstants.TYPE_DOUBLE,
|
||||
GenConstants.TYPE_FLOAT, GenConstants.TYPE_BIGDECIMAL)) {
|
||||
return "number";
|
||||
}
|
||||
if (StringUtils.equals(this.javaType, GenConstants.TYPE_BOOLEAN)) {
|
||||
return "boolean";
|
||||
}
|
||||
return "string";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开关启用值的前端字面量。
|
||||
*
|
||||
* @return 前端字面量
|
||||
*/
|
||||
public String getSwitchActiveValue() {
|
||||
if (StringUtils.equals(this.javaType, GenConstants.TYPE_BOOLEAN)) {
|
||||
return "true";
|
||||
}
|
||||
if (isNumberType()) {
|
||||
return "0";
|
||||
}
|
||||
return "'0'";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取开关停用值的前端字面量。
|
||||
*
|
||||
* @return 前端字面量
|
||||
*/
|
||||
public String getSwitchInactiveValue() {
|
||||
if (StringUtils.equals(this.javaType, GenConstants.TYPE_BOOLEAN)) {
|
||||
return "false";
|
||||
}
|
||||
if (isNumberType()) {
|
||||
return "1";
|
||||
}
|
||||
return "'1'";
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前字段是否为数值类型。
|
||||
*
|
||||
* @return 数值类型返回 {@code true}
|
||||
*/
|
||||
public boolean isNumberType() {
|
||||
return StringUtils.equalsAny(this.javaType,
|
||||
GenConstants.TYPE_INTEGER, GenConstants.TYPE_LONG, GenConstants.TYPE_DOUBLE,
|
||||
GenConstants.TYPE_FLOAT, GenConstants.TYPE_BIGDECIMAL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为日期范围查询字段。
|
||||
*
|
||||
* @return 日期范围查询字段返回 {@code true}
|
||||
*/
|
||||
public boolean isDateRangeQuery() {
|
||||
return isQuery()
|
||||
&& StringUtils.equals(this.htmlType, GenConstants.HTML_DATETIME)
|
||||
&& StringUtils.equals(this.queryType, GenConstants.QUERY_BETWEEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否为主键列。
|
||||
*
|
||||
* @return 主键列返回 {@code true}
|
||||
*/
|
||||
public boolean isPk() {
|
||||
return isPk(this.isPk);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断是否为主键列。
|
||||
*
|
||||
* @param isPk 主键标识
|
||||
* @return 主键列返回 {@code true}
|
||||
*/
|
||||
public boolean isPk(String isPk) {
|
||||
return isPk != null && StringUtils.equals("1", isPk);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否为自增列。
|
||||
*
|
||||
* @return 自增列返回 {@code true}
|
||||
*/
|
||||
public boolean isIncrement() {
|
||||
return isIncrement(this.isIncrement);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断是否为自增列。
|
||||
*
|
||||
* @param isIncrement 自增标识
|
||||
* @return 自增列返回 {@code true}
|
||||
*/
|
||||
public boolean isIncrement(String isIncrement) {
|
||||
return isIncrement != null && StringUtils.equals("1", isIncrement);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否必填。
|
||||
*
|
||||
* @return 必填返回 {@code true}
|
||||
*/
|
||||
public boolean isRequired() {
|
||||
return isRequired(this.isRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断当前列是否必填。
|
||||
*
|
||||
* @param isRequired 必填标识
|
||||
* @return 必填返回 {@code true}
|
||||
*/
|
||||
public boolean isRequired(String isRequired) {
|
||||
return isRequired != null && StringUtils.equals("1", isRequired);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否参与新增。
|
||||
*
|
||||
* @return 参与新增返回 {@code true}
|
||||
*/
|
||||
public boolean isInsert() {
|
||||
return isInsert(this.isInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断当前列是否参与新增。
|
||||
*
|
||||
* @param isInsert 新增标识
|
||||
* @return 参与新增返回 {@code true}
|
||||
*/
|
||||
public boolean isInsert(String isInsert) {
|
||||
return isInsert != null && StringUtils.equals("1", isInsert);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否参与编辑。
|
||||
*
|
||||
* @return 参与编辑返回 {@code true}
|
||||
*/
|
||||
public boolean isEdit() {
|
||||
return isEdit(this.isEdit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断当前列是否参与编辑。
|
||||
*
|
||||
* @param isEdit 编辑标识
|
||||
* @return 参与编辑返回 {@code true}
|
||||
*/
|
||||
public boolean isEdit(String isEdit) {
|
||||
return isEdit != null && StringUtils.equals("1", isEdit);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否参与列表展示。
|
||||
*
|
||||
* @return 参与列表展示返回 {@code true}
|
||||
*/
|
||||
public boolean isList() {
|
||||
return isList(this.isList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断当前列是否参与列表展示。
|
||||
*
|
||||
* @param isList 列表展示标识
|
||||
* @return 参与列表展示返回 {@code true}
|
||||
*/
|
||||
public boolean isList(String isList) {
|
||||
return isList != null && StringUtils.equals("1", isList);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否参与查询条件。
|
||||
*
|
||||
* @return 参与查询返回 {@code true}
|
||||
*/
|
||||
public boolean isQuery() {
|
||||
return isQuery(this.isQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据标识判断当前列是否参与查询条件。
|
||||
*
|
||||
* @param isQuery 查询标识
|
||||
* @return 参与查询返回 {@code true}
|
||||
*/
|
||||
public boolean isQuery(String isQuery) {
|
||||
return isQuery != null && StringUtils.equals("1", isQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否为基类公共字段。
|
||||
*
|
||||
* @return 基类公共字段返回 {@code true}
|
||||
*/
|
||||
public boolean isSuperColumn() {
|
||||
return isSuperColumn(this.javaField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段名判断是否为基类公共字段。
|
||||
*
|
||||
* @param javaField Java 字段名
|
||||
* @return 基类公共字段返回 {@code true}
|
||||
*/
|
||||
public static boolean isSuperColumn(String javaField) {
|
||||
return StringUtils.equalsAnyIgnoreCase(javaField,
|
||||
// BaseEntity
|
||||
"createBy", "createTime", "updateBy", "updateTime",
|
||||
// TreeEntity
|
||||
"parentName", "parentId");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否属于生成页面需要保留的白名单字段。
|
||||
*
|
||||
* @return 白名单字段返回 {@code true}
|
||||
*/
|
||||
public boolean isUsableColumn() {
|
||||
return isUsableColumn(javaField);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段名判断是否属于生成页面需要保留的白名单字段。
|
||||
*
|
||||
* @param javaField Java 字段名
|
||||
* @return 白名单字段返回 {@code true}
|
||||
*/
|
||||
public static boolean isUsableColumn(String javaField) {
|
||||
// isSuperColumn()中的名单用于避免生成多余Domain属性,若某些属性在生成页面时需要用到不能忽略,则放在此处白名单
|
||||
return StringUtils.equalsAnyIgnoreCase(javaField, "parentId", "orderNum", "remark");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否需要显式声明 MP 字段映射。
|
||||
*
|
||||
* @return 字段名与下划线列名不一致时返回 {@code true}
|
||||
*/
|
||||
public boolean isNeedTableField() {
|
||||
if (StringUtils.isAnyBlank(this.columnName, this.javaField)) {
|
||||
return false;
|
||||
}
|
||||
return !StringUtils.equalsIgnoreCase(this.columnName, StringUtils.toUnderScoreCase(this.javaField));
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前列是否属于字典控件列。
|
||||
*
|
||||
* @return 仅当已配置字典类型且显示类型支持字典时返回 {@code true}
|
||||
*/
|
||||
public boolean isDictColumn() {
|
||||
return StringUtils.isNotBlank(this.dictType) && StringUtils.equalsAny(this.htmlType,
|
||||
GenConstants.HTML_SELECT,
|
||||
GenConstants.HTML_RADIO,
|
||||
GenConstants.HTML_CHECKBOX,
|
||||
GenConstants.HTML_SWITCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从字段注释中解析字典读转换表达式。
|
||||
*
|
||||
* @return 形如 `0=男,1=女` 的转换表达式,无法解析时返回原始注释
|
||||
*/
|
||||
public String readConverterExp() {
|
||||
String remarks = StringUtils.substringBetween(this.columnComment, "(", ")");
|
||||
StringBuilder sb = new StringBuilder();
|
||||
if (StringUtils.isNotEmpty(remarks)) {
|
||||
for (String value : remarks.split(" ")) {
|
||||
if (StringUtils.isNotEmpty(value)) {
|
||||
Object startStr = value.subSequence(0, 1);
|
||||
String endStr = value.substring(1);
|
||||
sb.append(StringUtils.EMPTY).append(startStr).append("=").append(endStr).append(StringUtils.SEPARATOR);
|
||||
}
|
||||
}
|
||||
return sb.deleteCharAt(sb.length() - 1).toString();
|
||||
} else {
|
||||
return this.columnComment;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.dromara.gen.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.gen.domain.GenTableColumn;
|
||||
|
||||
/**
|
||||
* 业务字段 数据层
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
|
||||
public interface GenTableColumnMapper extends BaseMapperPlus<GenTableColumn, GenTableColumn> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.dromara.gen.mapper;
|
||||
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
import org.dromara.gen.domain.GenTable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 业务 数据层
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@InterceptorIgnore(dataPermission = "true", tenantLine = "true")
|
||||
public interface GenTableMapper extends BaseMapperPlus<GenTable, GenTable> {
|
||||
|
||||
/**
|
||||
* 查询指定数据源下的所有表名列表
|
||||
*
|
||||
* @param dataName 数据源名称,用于选择不同的数据源
|
||||
* @return 当前数据库中的表名列表
|
||||
* @DS("") 使用默认数据源执行查询操作
|
||||
*/
|
||||
@DS("")
|
||||
List<String> selectTableNameList(String dataName);
|
||||
}
|
||||
@@ -0,0 +1,654 @@
|
||||
package org.dromara.gen.service;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.dynamic.datasource.annotation.DS;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.anyline.metadata.Column;
|
||||
import org.anyline.metadata.Table;
|
||||
import org.anyline.proxy.ServiceProxy;
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StreamUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.mybatis.utils.IdGeneratorUtil;
|
||||
import org.dromara.gen.constant.GenConstants;
|
||||
import org.dromara.gen.domain.GenTable;
|
||||
import org.dromara.gen.domain.GenTableColumn;
|
||||
import org.dromara.gen.mapper.GenTableColumnMapper;
|
||||
import org.dromara.gen.mapper.GenTableMapper;
|
||||
import org.dromara.gen.util.GenUtils;
|
||||
import org.dromara.gen.util.TemplateEngineUtils;
|
||||
import org.dromara.gen.util.template.PathNamedTemplate;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
/**
|
||||
* 业务 服务层实现
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class GenTableServiceImpl implements IGenTableService {
|
||||
|
||||
private final GenTableMapper tableMapper;
|
||||
private final GenTableColumnMapper genTableColumnMapper;
|
||||
|
||||
private static final String[] TABLE_IGNORE = new String[]{"sai_", "sj_", "flow_", "gen_"};
|
||||
|
||||
/**
|
||||
* 查询业务字段列表
|
||||
*
|
||||
* @param tableId 业务字段编号
|
||||
* @return 业务字段集合
|
||||
*/
|
||||
@Override
|
||||
public List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId) {
|
||||
return genTableColumnMapper.lambda()
|
||||
.eq(GenTableColumn::getTableId, tableId)
|
||||
.orderByAsc(GenTableColumn::getSort)
|
||||
.list();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询业务信息
|
||||
*
|
||||
* @param id 业务ID
|
||||
* @return 业务信息
|
||||
*/
|
||||
@Override
|
||||
public GenTable selectGenTableById(Long id) {
|
||||
GenTable genTable = getGenTable(id);
|
||||
setTableFromOptions(genTable);
|
||||
return genTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询已导入的代码生成业务表。
|
||||
*
|
||||
* @param genTable 业务表筛选条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return 业务表分页结果
|
||||
*/
|
||||
@Override
|
||||
public PageResult<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery) {
|
||||
Page<GenTable> page = tableMapper.selectPage(pageQuery.build(), this.buildGenTableQueryWrapper(genTable));
|
||||
return PageResult.build(page.getRecords(), page.getTotal());
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造代码生成业务表查询条件。
|
||||
*
|
||||
* @param genTable 业务表筛选条件
|
||||
* @return 包含数据源、表名、表注释和时间区间的查询包装器
|
||||
*/
|
||||
private QueryWrapper<GenTable> buildGenTableQueryWrapper(GenTable genTable) {
|
||||
Map<String, Object> params = genTable.getParams();
|
||||
QueryWrapper<GenTable> wrapper = Wrappers.query();
|
||||
wrapper
|
||||
.eq(StringUtils.isNotEmpty(genTable.getDataName()), "data_name", genTable.getDataName())
|
||||
.like(StringUtils.isNotBlank(genTable.getTableName()), "lower(table_name)", StringUtils.lowerCase(genTable.getTableName()))
|
||||
.like(StringUtils.isNotBlank(genTable.getTableComment()), "lower(table_comment)", StringUtils.lowerCase(genTable.getTableComment()))
|
||||
.between(params.get("beginTime") != null && params.get("endTime") != null,
|
||||
"create_time", params.get("beginTime"), params.get("endTime"))
|
||||
.orderByDesc("update_time");
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询数据库列表
|
||||
*
|
||||
* @param genTable 包含查询条件的GenTable对象
|
||||
* @param pageQuery 包含分页信息的PageQuery对象
|
||||
* @return 包含分页结果的TableDataInfo对象
|
||||
*/
|
||||
@DS("#genTable.dataName")
|
||||
@Override
|
||||
public PageResult<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery) {
|
||||
// 获取查询条件
|
||||
String tableName = genTable.getTableName();
|
||||
String tableComment = genTable.getTableComment();
|
||||
|
||||
LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
|
||||
if (CollUtil.isEmpty(tablesMap)) {
|
||||
return PageResult.build();
|
||||
}
|
||||
List<String> tableNames = tableMapper.selectTableNameList(genTable.getDataName());
|
||||
String[] tableArrays;
|
||||
if (CollUtil.isNotEmpty(tableNames)) {
|
||||
tableArrays = tableNames.toArray(new String[0]);
|
||||
} else {
|
||||
tableArrays = new String[0];
|
||||
}
|
||||
// 过滤并转换表格数据
|
||||
List<GenTable> tables = tablesMap.values().stream()
|
||||
.filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
|
||||
.filter(x -> {
|
||||
if (CollUtil.isEmpty(tableNames)) {
|
||||
return true;
|
||||
}
|
||||
return !StringUtils.equalsAnyIgnoreCase(x.getName(), tableArrays);
|
||||
})
|
||||
.filter(x -> {
|
||||
boolean nameMatches = true;
|
||||
boolean commentMatches = true;
|
||||
// 进行表名称的模糊查询
|
||||
if (StringUtils.isNotBlank(tableName)) {
|
||||
nameMatches = StringUtils.containsIgnoreCase(x.getName(), tableName);
|
||||
}
|
||||
// 进行表描述的模糊查询
|
||||
if (StringUtils.isNotBlank(tableComment)) {
|
||||
commentMatches = StringUtils.containsIgnoreCase(x.getComment(), tableComment);
|
||||
}
|
||||
// 同时匹配名称和描述
|
||||
return nameMatches && commentMatches;
|
||||
})
|
||||
.map(x -> {
|
||||
GenTable gen = new GenTable();
|
||||
gen.setTableName(x.getName());
|
||||
gen.setTableComment(x.getComment());
|
||||
// postgresql的表元数据没有创建时间这个东西(好奇葩) 只能new Date代替
|
||||
Date createDate = ObjectUtil.defaultIfNull(x.getCreateTime(), new Date());
|
||||
gen.setCreateTime(LocalDateTimeUtil.of(createDate));
|
||||
gen.setUpdateTime(x.getUpdateTime() != null ? LocalDateTimeUtil.of(x.getUpdateTime()) : null);
|
||||
return gen;
|
||||
}).sorted(Comparator.comparing(GenTable::getCreateTime).reversed())
|
||||
.toList();
|
||||
// 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
|
||||
if (CollUtil.isEmpty(tables)) {
|
||||
return PageResult.build();
|
||||
}
|
||||
Page<Object> page = pageQuery.build();
|
||||
List<GenTable> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), tables);
|
||||
return PageResult.build(pageList, tables.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param tableNames 表名称组
|
||||
* @param dataName 数据源名称
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
@DS("#dataName")
|
||||
@Override
|
||||
public List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName) {
|
||||
Set<String> tableNameSet = new HashSet<>(List.of(tableNames));
|
||||
LinkedHashMap<String, Table<?>> tablesMap = ServiceProxy.metadata().tables();
|
||||
|
||||
if (CollUtil.isEmpty(tablesMap)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
List<Table<?>> tableList = tablesMap.values().stream()
|
||||
.filter(x -> !StringUtils.startWithAnyIgnoreCase(x.getName(), TABLE_IGNORE))
|
||||
.filter(x -> tableNameSet.contains(x.getName())).toList();
|
||||
|
||||
if (CollUtil.isEmpty(tableList)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
return tableList.stream().map(x -> {
|
||||
GenTable gen = new GenTable();
|
||||
gen.setDataName(dataName);
|
||||
gen.setTableName(x.getName());
|
||||
gen.setTableComment(x.getComment());
|
||||
gen.setCreateTime(LocalDateTimeUtil.of(x.getCreateTime()));
|
||||
gen.setUpdateTime(LocalDateTimeUtil.of(x.getUpdateTime()));
|
||||
return gen;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改业务
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void updateGenTable(GenTable genTable) {
|
||||
normalizeColumnOptions(genTable.getColumns());
|
||||
String options = JsonUtils.toJsonString(genTable.getParams());
|
||||
genTable.setOptions(options);
|
||||
int row = tableMapper.updateById(genTable);
|
||||
if (row > 0) {
|
||||
genTableColumnMapper.updateBatchById(genTable.getColumns());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除业务对象
|
||||
*
|
||||
* @param tableIds 需要删除的数据ID
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Override
|
||||
public void deleteGenTableByIds(Long[] tableIds) {
|
||||
List<Long> ids = Arrays.asList(tableIds);
|
||||
tableMapper.deleteByIds(ids);
|
||||
genTableColumnMapper.lambda()
|
||||
.in(GenTableColumn::getTableId, ids)
|
||||
.deleteCount();
|
||||
}
|
||||
|
||||
/**
|
||||
* 导入表结构
|
||||
*
|
||||
* @param tableList 导入表列表
|
||||
* @param dataName 数据源名称
|
||||
*/
|
||||
@DSTransactional
|
||||
@Override
|
||||
public void importGenTable(List<GenTable> tableList, String dataName) {
|
||||
try {
|
||||
for (GenTable table : tableList) {
|
||||
String tableName = table.getTableName();
|
||||
GenUtils.initTable(table);
|
||||
table.setDataName(dataName);
|
||||
int row = tableMapper.insert(table);
|
||||
if (row > 0) {
|
||||
// 保存列信息
|
||||
List<GenTableColumn> genTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(tableName, dataName);
|
||||
List<GenTableColumn> saveColumns = new ArrayList<>();
|
||||
for (GenTableColumn column : genTableColumns) {
|
||||
GenUtils.initColumnField(column, table);
|
||||
saveColumns.add(column);
|
||||
}
|
||||
if (CollUtil.isNotEmpty(saveColumns)) {
|
||||
genTableColumnMapper.insertBatch(saveColumns);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("导入失败", e);
|
||||
throw new ServiceException("导入失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据表名称查询列信息
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @param dataName 数据源名称
|
||||
* @return 列信息
|
||||
*/
|
||||
@DS("#dataName")
|
||||
@Override
|
||||
public List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName) {
|
||||
Table<?> table = ServiceProxy.metadata().table(tableName);
|
||||
if (ObjectUtil.isNull(table)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
LinkedHashMap<String, Column> columns = table.getColumns();
|
||||
List<GenTableColumn> tableColumns = new ArrayList<>();
|
||||
columns.forEach((columnName, column) -> {
|
||||
GenTableColumn tableColumn = new GenTableColumn();
|
||||
tableColumn.setIsPk(column.isPrimaryKey() ? "1" : "0");
|
||||
tableColumn.setColumnName(column.getName());
|
||||
tableColumn.setColumnComment(column.getComment());
|
||||
tableColumn.setColumnType(column.getOriginType().toLowerCase());
|
||||
tableColumn.setSort(column.getPosition());
|
||||
tableColumn.setIsRequired(column.isNullable() ? "0" : "1");
|
||||
tableColumn.setIsIncrement(column.isAutoIncrement() ? "1" : "0");
|
||||
tableColumns.add(tableColumn);
|
||||
});
|
||||
return tableColumns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 预览代码
|
||||
*
|
||||
* @param tableId 表编号
|
||||
* @return 预览数据列表
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> previewCode(Long tableId) {
|
||||
Map<String, String> dataMap = new LinkedHashMap<>();
|
||||
RenderContext rc = buildRenderContext(tableId);
|
||||
for (PathNamedTemplate template : rc.templates()) {
|
||||
dataMap.put(template.getPathName(), template.render(rc.context()));
|
||||
}
|
||||
return dataMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成代码(下载方式)
|
||||
*
|
||||
* @param tableId 表名称
|
||||
* @return 数据
|
||||
*/
|
||||
@Override
|
||||
public byte[] downloadCode(Long tableId) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zip = new ZipOutputStream(outputStream);
|
||||
writeCodeToZip(tableId, zip);
|
||||
IoUtil.close(zip);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步数据库
|
||||
*
|
||||
* @param tableId 表名称
|
||||
*/
|
||||
@DSTransactional
|
||||
@Override
|
||||
public void synchDb(Long tableId) {
|
||||
GenTable table = getGenTable(tableId);
|
||||
List<GenTableColumn> tableColumns = table.getColumns();
|
||||
Map<String, GenTableColumn> tableColumnMap = StreamUtils.toIdentityMap(tableColumns, GenTableColumn::getColumnName);
|
||||
|
||||
List<GenTableColumn> dbTableColumns = SpringUtils.getAopProxy(this).selectDbTableColumnsByName(table.getTableName(), table.getDataName());
|
||||
if (CollUtil.isEmpty(dbTableColumns)) {
|
||||
throw new ServiceException("同步数据失败,原表结构不存在");
|
||||
}
|
||||
List<String> dbTableColumnNames = StreamUtils.toList(dbTableColumns, GenTableColumn::getColumnName);
|
||||
|
||||
List<GenTableColumn> saveColumns = new ArrayList<>();
|
||||
dbTableColumns.forEach(column -> {
|
||||
GenUtils.initColumnField(column, table);
|
||||
if (tableColumnMap.containsKey(column.getColumnName())) {
|
||||
GenTableColumn prevColumn = tableColumnMap.get(column.getColumnName());
|
||||
column.setColumnId(prevColumn.getColumnId());
|
||||
if (column.isList()) {
|
||||
// 如果是列表,继续保留查询方式/字典类型选项
|
||||
column.setDictType(prevColumn.getDictType());
|
||||
column.setQueryType(prevColumn.getQueryType());
|
||||
}
|
||||
if (StringUtils.isNotEmpty(prevColumn.getIsRequired()) && !column.isPk()
|
||||
&& (column.isInsert() || column.isEdit())
|
||||
&& ((column.isUsableColumn()) || (!column.isSuperColumn()))) {
|
||||
// 如果是(新增/修改&非主键/非忽略及父属性),继续保留必填/显示类型选项
|
||||
column.setIsRequired(prevColumn.getIsRequired());
|
||||
column.setHtmlType(prevColumn.getHtmlType());
|
||||
}
|
||||
}
|
||||
saveColumns.add(column);
|
||||
});
|
||||
if (CollUtil.isNotEmpty(saveColumns)) {
|
||||
normalizeColumnOptions(saveColumns);
|
||||
genTableColumnMapper.insertOrUpdateBatch(saveColumns);
|
||||
}
|
||||
List<GenTableColumn> delColumns = StreamUtils.filter(tableColumns, column -> !dbTableColumnNames.contains(column.getColumnName()));
|
||||
if (CollUtil.isNotEmpty(delColumns)) {
|
||||
List<Long> ids = StreamUtils.toList(delColumns, GenTableColumn::getColumnId);
|
||||
if (CollUtil.isNotEmpty(ids)) {
|
||||
genTableColumnMapper.deleteByIds(ids);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量生成代码(下载方式)
|
||||
*
|
||||
* @param tableIds 表ID数组
|
||||
* @return 数据
|
||||
*/
|
||||
@Override
|
||||
public byte[] downloadCode(String[] tableIds) {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ZipOutputStream zip = new ZipOutputStream(outputStream);
|
||||
for (String tableId : tableIds) {
|
||||
writeCodeToZip(Long.parseLong(tableId), zip);
|
||||
}
|
||||
IoUtil.close(zip);
|
||||
return outputStream.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询表信息并生成代码
|
||||
*
|
||||
* @param tableId 业务表主键
|
||||
* @param zip 代码压缩输出流
|
||||
*/
|
||||
private void writeCodeToZip(Long tableId, ZipOutputStream zip) {
|
||||
RenderContext rc = buildRenderContext(tableId);
|
||||
GenTable table = rc.table();
|
||||
for (PathNamedTemplate template : rc.templates()) {
|
||||
String pathName = template.getPathName();
|
||||
try {
|
||||
String render = template.render(rc.context());
|
||||
zip.putNextEntry(new ZipEntry(TemplateEngineUtils.getFileName(pathName, table)));
|
||||
IoUtil.write(zip, StandardCharsets.UTF_8, false, render);
|
||||
zip.flush();
|
||||
zip.closeEntry();
|
||||
} catch (IOException e) {
|
||||
log.error("渲染模板失败,表名:{}", table.getTableName(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建代码渲染上下文(含表信息、菜单ID、主键列、模板列表)
|
||||
*
|
||||
* @param tableId 业务表主键
|
||||
* @return 渲染上下文
|
||||
*/
|
||||
private RenderContext buildRenderContext(Long tableId) {
|
||||
GenTable table = getGenTable(tableId);
|
||||
List<Long> menuIds = new ArrayList<>();
|
||||
for (int i = 0; i < 6; i++) {
|
||||
menuIds.add(IdGeneratorUtil.nextLongId());
|
||||
}
|
||||
table.setMenuIds(menuIds);
|
||||
setPkColumn(table);
|
||||
Dict context = TemplateEngineUtils.buildContext(table);
|
||||
List<PathNamedTemplate> templates = TemplateEngineUtils.getTemplateList(table.getTplCategory(), table.getDataName(), table.getFrontendType());
|
||||
return new RenderContext(table, context, templates);
|
||||
}
|
||||
|
||||
/**
|
||||
* 模板渲染上下文。
|
||||
*
|
||||
* @param table 生成表信息
|
||||
* @param context 模板上下文
|
||||
* @param templates 待渲染模板
|
||||
*/
|
||||
private record RenderContext(GenTable table, Dict context, List<PathNamedTemplate> templates) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改保存参数校验
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
*/
|
||||
@Override
|
||||
public void validateEdit(GenTable genTable) {
|
||||
validateOptionColumns(genTable);
|
||||
if (GenConstants.TPL_TREE.equals(genTable.getTplCategory())) {
|
||||
String options = JsonUtils.toJsonString(genTable.getParams());
|
||||
Dict paramsObj = JsonUtils.parseMap(options);
|
||||
if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_CODE))) {
|
||||
throw new ServiceException("树编码字段不能为空");
|
||||
} else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_PARENT_CODE))) {
|
||||
throw new ServiceException("树父编码字段不能为空");
|
||||
} else if (StringUtils.isEmpty(paramsObj.getStr(GenConstants.TREE_NAME))) {
|
||||
throw new ServiceException("树名称字段不能为空");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验生成选项中配置的字段是否存在。
|
||||
*
|
||||
* @param genTable 业务表信息
|
||||
*/
|
||||
private void validateOptionColumns(GenTable genTable) {
|
||||
Map<String, Object> params = genTable.getParams();
|
||||
if (CollUtil.isEmpty(params) || CollUtil.isEmpty(genTable.getColumns())) {
|
||||
return;
|
||||
}
|
||||
Set<String> validFields = new HashSet<>();
|
||||
genTable.getColumns().forEach(column -> {
|
||||
validFields.add(column.getColumnName());
|
||||
validFields.add(column.getJavaField());
|
||||
});
|
||||
validateOptionField(validFields, params.get(GenConstants.STATUS_FIELD), "状态字段");
|
||||
validateOptionField(validFields, params.get(GenConstants.SORT_FIELD), "排序字段");
|
||||
validateOptionField(validFields, params.get(GenConstants.TREE_ANCESTORS), "树祖级字段");
|
||||
validateOptionField(validFields, params.get(GenConstants.TREE_ORDER_FIELD), "树排序字段");
|
||||
Object uniqueFields = params.get(GenConstants.UNIQUE_FIELDS);
|
||||
if (uniqueFields instanceof Collection<?> collection) {
|
||||
for (Object field : collection) {
|
||||
validateOptionField(validFields, field, "组合唯一字段");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验单个选项字段。
|
||||
*
|
||||
* @param validFields 有效字段集合
|
||||
* @param field 待校验字段
|
||||
* @param label 字段显示名称
|
||||
*/
|
||||
private void validateOptionField(Set<String> validFields, Object field, String label) {
|
||||
if (ObjectUtil.isNull(field)) {
|
||||
return;
|
||||
}
|
||||
String fieldValue = Convert.toStr(field);
|
||||
if (StringUtils.isBlank(fieldValue)) {
|
||||
return;
|
||||
}
|
||||
if (!validFields.contains(fieldValue)) {
|
||||
throw new ServiceException(label + "不存在,请刷新字段后重试");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化字段扩展配置。
|
||||
*
|
||||
* @param columns 表字段列表
|
||||
*/
|
||||
private void normalizeColumnOptions(List<GenTableColumn> columns) {
|
||||
if (CollUtil.isEmpty(columns)) {
|
||||
return;
|
||||
}
|
||||
for (GenTableColumn column : columns) {
|
||||
if (!column.isDictColumn()) {
|
||||
column.setDictType(StringUtils.EMPTY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询业务表并补齐其列信息。
|
||||
*
|
||||
* @param tableId 业务表主键
|
||||
* @return 包含字段集合的业务表实体
|
||||
*/
|
||||
private GenTable getGenTable(Long tableId) {
|
||||
GenTable table = tableMapper.selectById(tableId);
|
||||
if (ObjectUtil.isNull(table)) {
|
||||
throw new ServiceException("业务表不存在");
|
||||
}
|
||||
fillTableColumns(Collections.singletonList(table));
|
||||
return table;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量填充业务表对应的字段列表。
|
||||
*
|
||||
* @param tables 业务表集合
|
||||
* @return 已填充字段信息的业务表集合
|
||||
*/
|
||||
private List<GenTable> fillTableColumns(List<GenTable> tables) {
|
||||
if (CollUtil.isEmpty(tables)) {
|
||||
return tables;
|
||||
}
|
||||
List<Long> tableIds = StreamUtils.toList(tables, GenTable::getTableId);
|
||||
List<GenTableColumn> columns = genTableColumnMapper.lambda()
|
||||
.in(GenTableColumn::getTableId, tableIds)
|
||||
.orderByAsc(GenTableColumn::getTableId)
|
||||
.orderByAsc(GenTableColumn::getSort)
|
||||
.list();
|
||||
Map<Long, List<GenTableColumn>> columnMap = StreamUtils.groupByKey(columns, GenTableColumn::getTableId);
|
||||
tables.forEach(table -> table.setColumns(columnMap.getOrDefault(table.getTableId(), new ArrayList<>())));
|
||||
return tables;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置主键列信息
|
||||
*
|
||||
* @param table 业务表信息
|
||||
*/
|
||||
public void setPkColumn(GenTable table) {
|
||||
if (CollUtil.isEmpty(table.getColumns())) {
|
||||
throw new ServiceException("表【" + table.getTableName() + "】字段为空,请检查表结构");
|
||||
}
|
||||
for (GenTableColumn column : table.getColumns()) {
|
||||
if (column.isPk()) {
|
||||
table.setPkColumn(column);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (ObjectUtil.isNull(table.getPkColumn())) {
|
||||
table.setPkColumn(table.getColumns().getFirst());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置代码生成其他选项值
|
||||
*
|
||||
* @param genTable 设置后的生成对象
|
||||
*/
|
||||
public void setTableFromOptions(GenTable genTable) {
|
||||
Dict paramsObj = JsonUtils.parseMap(genTable.getOptions());
|
||||
if (ObjectUtil.isNull(paramsObj)) {
|
||||
paramsObj = new Dict();
|
||||
}
|
||||
String treeCode = paramsObj.getStr(GenConstants.TREE_CODE);
|
||||
String treeParentCode = paramsObj.getStr(GenConstants.TREE_PARENT_CODE);
|
||||
String treeName = paramsObj.getStr(GenConstants.TREE_NAME);
|
||||
Long parentMenuId = Convert.toLong(TemplateEngineUtils.getParentMenuId(paramsObj));
|
||||
String parentMenuName = paramsObj.getStr(GenConstants.PARENT_MENU_NAME);
|
||||
Boolean enableExport = Convert.toBool(paramsObj.get(GenConstants.ENABLE_EXPORT), true);
|
||||
Boolean enableStatus = Convert.toBool(paramsObj.get(GenConstants.ENABLE_STATUS), false);
|
||||
String statusField = paramsObj.getStr(GenConstants.STATUS_FIELD);
|
||||
Boolean enableUnique = Convert.toBool(paramsObj.get(GenConstants.ENABLE_UNIQUE), false);
|
||||
List<String> uniqueFields = Convert.toList(String.class, paramsObj.get(GenConstants.UNIQUE_FIELDS));
|
||||
Boolean enableSort = Convert.toBool(paramsObj.get(GenConstants.ENABLE_SORT), false);
|
||||
String sortField = paramsObj.getStr(GenConstants.SORT_FIELD);
|
||||
String treeRootValue = paramsObj.getStr(GenConstants.TREE_ROOT_VALUE);
|
||||
String treeAncestorsField = paramsObj.getStr(GenConstants.TREE_ANCESTORS);
|
||||
String treeOrderField = paramsObj.getStr(GenConstants.TREE_ORDER_FIELD);
|
||||
|
||||
genTable.setTreeCode(treeCode);
|
||||
genTable.setTreeParentCode(treeParentCode);
|
||||
genTable.setTreeName(treeName);
|
||||
genTable.setParentMenuId(parentMenuId);
|
||||
genTable.setParentMenuName(parentMenuName);
|
||||
genTable.setEnableExport(enableExport);
|
||||
genTable.setEnableStatus(enableStatus);
|
||||
genTable.setStatusField(statusField);
|
||||
genTable.setEnableUnique(enableUnique);
|
||||
genTable.setUniqueFields(uniqueFields);
|
||||
genTable.setEnableSort(enableSort);
|
||||
genTable.setSortField(sortField);
|
||||
genTable.setTreeRootValue(treeRootValue);
|
||||
genTable.setTreeAncestorsField(treeAncestorsField);
|
||||
genTable.setTreeOrderField(treeOrderField);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package org.dromara.gen.service;
|
||||
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.gen.domain.GenTable;
|
||||
import org.dromara.gen.domain.GenTableColumn;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 业务 服务层
|
||||
*
|
||||
* @author Lion Li
|
||||
*/
|
||||
public interface IGenTableService {
|
||||
|
||||
/**
|
||||
* 查询业务字段列表
|
||||
*
|
||||
* @param tableId 业务字段编号
|
||||
* @return 业务字段集合
|
||||
*/
|
||||
List<GenTableColumn> selectGenTableColumnListByTableId(Long tableId);
|
||||
|
||||
/**
|
||||
* 查询业务列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @param pageQuery 分页参数
|
||||
* @return 业务分页集合
|
||||
*/
|
||||
PageResult<GenTable> selectPageGenTableList(GenTable genTable, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
* @param pageQuery 分页参数
|
||||
* @return 数据库表分页集合
|
||||
*/
|
||||
PageResult<GenTable> selectPageDbTableList(GenTable genTable, PageQuery pageQuery);
|
||||
|
||||
/**
|
||||
* 查询据库列表
|
||||
*
|
||||
* @param tableNames 表名称组
|
||||
* @param dataName 数据源名称
|
||||
* @return 数据库表集合
|
||||
*/
|
||||
List<GenTable> selectDbTableListByNames(String[] tableNames, String dataName);
|
||||
|
||||
/**
|
||||
* 查询业务信息
|
||||
*
|
||||
* @param id 业务ID
|
||||
* @return 业务信息
|
||||
*/
|
||||
GenTable selectGenTableById(Long id);
|
||||
|
||||
/**
|
||||
* 修改业务
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
*/
|
||||
void updateGenTable(GenTable genTable);
|
||||
|
||||
/**
|
||||
* 删除业务信息
|
||||
*
|
||||
* @param tableIds 需要删除的表数据ID
|
||||
*/
|
||||
void deleteGenTableByIds(Long[] tableIds);
|
||||
|
||||
/**
|
||||
* 导入表结构
|
||||
*
|
||||
* @param tableList 导入表列表
|
||||
* @param dataName 数据源名称
|
||||
*/
|
||||
void importGenTable(List<GenTable> tableList, String dataName);
|
||||
|
||||
/**
|
||||
* 根据表名称查询列信息
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @param dataName 数据源名称
|
||||
* @return 列信息
|
||||
*/
|
||||
List<GenTableColumn> selectDbTableColumnsByName(String tableName, String dataName);
|
||||
|
||||
/**
|
||||
* 预览代码
|
||||
*
|
||||
* @param tableId 表编号
|
||||
* @return 预览数据列表
|
||||
*/
|
||||
Map<String, String> previewCode(Long tableId);
|
||||
|
||||
/**
|
||||
* 生成代码(下载方式)
|
||||
*
|
||||
* @param tableId 表名称
|
||||
* @return 数据
|
||||
*/
|
||||
byte[] downloadCode(Long tableId);
|
||||
|
||||
/**
|
||||
* 同步数据库
|
||||
*
|
||||
* @param tableId 表名称
|
||||
*/
|
||||
void synchDb(Long tableId);
|
||||
|
||||
/**
|
||||
* 批量生成代码(下载方式)
|
||||
*
|
||||
* @param tableIds 表ID数组
|
||||
* @return 数据
|
||||
*/
|
||||
byte[] downloadCode(String[] tableIds);
|
||||
|
||||
/**
|
||||
* 修改保存参数校验
|
||||
*
|
||||
* @param genTable 业务信息
|
||||
*/
|
||||
void validateEdit(GenTable genTable);
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
package org.dromara.gen.util;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.apache.commons.lang3.RegExUtils;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.gen.config.properties.GenProperties;
|
||||
import org.dromara.gen.constant.GenConstants;
|
||||
import org.dromara.gen.domain.GenTable;
|
||||
import org.dromara.gen.domain.GenTableColumn;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* 代码生成器 工具类
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class GenUtils {
|
||||
|
||||
private final static GenProperties PROPERTIES = SpringUtils.getBean(GenProperties.class);
|
||||
|
||||
/**
|
||||
* 初始化表信息
|
||||
*
|
||||
* @param genTable 待初始化的业务表对象
|
||||
*/
|
||||
public static void initTable(GenTable genTable) {
|
||||
genTable.setClassName(convertClassName(genTable.getTableName()));
|
||||
genTable.setPackageName(PROPERTIES.getPackageName());
|
||||
genTable.setModuleName(getModuleName(PROPERTIES.getPackageName()));
|
||||
genTable.setBusinessName(getBusinessName(genTable.getTableName()));
|
||||
genTable.setFunctionName(replaceText(genTable.getTableComment()));
|
||||
genTable.setFunctionAuthor(PROPERTIES.getAuthor());
|
||||
genTable.setFrontendType(GenConstants.FRONTEND_TYPE_VUE);
|
||||
genTable.setCreateTime(null);
|
||||
genTable.setUpdateTime(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化列属性字段
|
||||
*
|
||||
* @param column 待初始化的列对象
|
||||
* @param table 所属业务表对象
|
||||
*/
|
||||
public static void initColumnField(GenTableColumn column, GenTable table) {
|
||||
String dataType = normalizeDbType(getDbType(column.getColumnType()));
|
||||
// 统一转小写 避免有些数据库默认大写问题 如果需要特别书写方式 请在实体类增加注解标注别名
|
||||
String columnName = column.getColumnName().toLowerCase();
|
||||
Integer columnLength = getColumnLength(column.getColumnType());
|
||||
Integer columnScale = getColumnScale(column.getColumnType());
|
||||
column.setTableId(table.getTableId());
|
||||
column.setCreateTime(null);
|
||||
column.setUpdateTime(null);
|
||||
// 设置java字段名
|
||||
column.setJavaField(StringUtils.toCamelCase(columnName));
|
||||
// 设置默认类型
|
||||
column.setJavaType(GenConstants.TYPE_STRING);
|
||||
// 设置默认HTML类型
|
||||
column.setHtmlType(GenConstants.HTML_INPUT);
|
||||
column.setQueryType(GenConstants.QUERY_EQ);
|
||||
|
||||
if (arraysContains(GenConstants.COLUMNTYPE_STR, dataType) || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType)) {
|
||||
// 字符串长度超过500设置为文本域
|
||||
String htmlType = columnLength >= 500 || arraysContains(GenConstants.COLUMNTYPE_TEXT, dataType) ? GenConstants.HTML_TEXTAREA : GenConstants.HTML_INPUT;
|
||||
if (isBooleanColumn(dataType, columnLength, columnScale, columnName)) {
|
||||
column.setJavaType(GenConstants.TYPE_BOOLEAN);
|
||||
htmlType = GenConstants.HTML_SWITCH;
|
||||
}
|
||||
column.setHtmlType(htmlType);
|
||||
} else if (arraysContains(GenConstants.COLUMNTYPE_TIME, dataType)) {
|
||||
column.setJavaType(GenConstants.TYPE_DATE);
|
||||
column.setHtmlType(GenConstants.HTML_DATETIME);
|
||||
} else if (arraysContains(GenConstants.COLUMNTYPE_NUMBER, dataType)) {
|
||||
column.setJavaType(resolveNumberJavaType(dataType, columnLength, columnScale, columnName));
|
||||
column.setHtmlType(GenConstants.TYPE_BOOLEAN.equals(column.getJavaType()) ? GenConstants.HTML_SWITCH : GenConstants.HTML_INPUT_NUMBER);
|
||||
}
|
||||
|
||||
// BO对象 默认插入勾选
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_ADD, columnName) && !column.isPk()) {
|
||||
column.setIsInsert(GenConstants.REQUIRE);
|
||||
}
|
||||
// BO对象 默认编辑勾选
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_EDIT, columnName)) {
|
||||
column.setIsEdit(GenConstants.REQUIRE);
|
||||
}
|
||||
// VO对象 默认返回勾选
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_LIST, columnName)) {
|
||||
column.setIsList(GenConstants.REQUIRE);
|
||||
}
|
||||
// BO对象 默认查询勾选
|
||||
if (!arraysContains(GenConstants.COLUMNNAME_NOT_QUERY, columnName) && !column.isPk()) {
|
||||
column.setIsQuery(GenConstants.REQUIRE);
|
||||
}
|
||||
|
||||
// 查询字段类型
|
||||
if (StringUtils.endsWithIgnoreCase(columnName, "name")) {
|
||||
column.setQueryType(GenConstants.QUERY_LIKE);
|
||||
}
|
||||
if (GenConstants.HTML_DATETIME.equals(column.getHtmlType()) && column.isQuery()) {
|
||||
column.setQueryType(GenConstants.QUERY_BETWEEN);
|
||||
}
|
||||
// 状态字段设置单选框
|
||||
if (isSwitchColumn(columnName) || GenConstants.TYPE_BOOLEAN.equals(column.getJavaType())) {
|
||||
column.setHtmlType(GenConstants.HTML_SWITCH);
|
||||
}
|
||||
// 状态字段设置单选框/开关
|
||||
if (StringUtils.endsWithIgnoreCase(columnName, "status")) {
|
||||
column.setHtmlType(GenConstants.TYPE_BOOLEAN.equals(column.getJavaType()) ? GenConstants.HTML_SWITCH : GenConstants.HTML_RADIO);
|
||||
}
|
||||
// 类型&性别字段设置下拉框
|
||||
else if (StringUtils.endsWithIgnoreCase(columnName, "type")
|
||||
|| StringUtils.endsWithIgnoreCase(columnName, "sex")) {
|
||||
column.setHtmlType(GenConstants.HTML_SELECT);
|
||||
}
|
||||
// 排序字段设置数字输入控件
|
||||
else if (isSortColumn(columnName)) {
|
||||
column.setHtmlType(GenConstants.HTML_INPUT_NUMBER);
|
||||
}
|
||||
// 图片字段设置图片上传控件
|
||||
else if (StringUtils.endsWithAny(columnName, "image", "avatar", "logo", "picture")) {
|
||||
column.setHtmlType(GenConstants.HTML_IMAGE_UPLOAD);
|
||||
}
|
||||
// 文件字段设置文件上传控件
|
||||
else if (StringUtils.endsWithAny(columnName, "file", "attachment")) {
|
||||
column.setHtmlType(GenConstants.HTML_FILE_UPLOAD);
|
||||
}
|
||||
// 备注描述类字段设置文本域
|
||||
else if (StringUtils.endsWithAny(columnName, "remark", "description", "desc", "note")) {
|
||||
column.setHtmlType(GenConstants.HTML_TEXTAREA);
|
||||
}
|
||||
// 内容字段设置富文本控件
|
||||
else if (StringUtils.endsWithAny(columnName, "content", "html", "body")) {
|
||||
column.setHtmlType(GenConstants.HTML_EDITOR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析数值类型对应的 Java 类型。
|
||||
*
|
||||
* @param dataType 数据库字段类型
|
||||
* @param columnLength 字段长度
|
||||
* @param columnScale 小数位数
|
||||
* @param columnName 字段名称
|
||||
* @return Java 类型
|
||||
*/
|
||||
private static String resolveNumberJavaType(String dataType, Integer columnLength, Integer columnScale, String columnName) {
|
||||
if (isBooleanColumn(dataType, columnLength, columnScale, columnName)) {
|
||||
return GenConstants.TYPE_BOOLEAN;
|
||||
}
|
||||
if (arraysContains(new String[]{"decimal", "dec", "fixed", "numeric", "money", "smallmoney"}, dataType)) {
|
||||
return GenConstants.TYPE_BIGDECIMAL;
|
||||
}
|
||||
if (arraysContains(new String[]{"float4", "real", "binary_float"}, dataType)) {
|
||||
return GenConstants.TYPE_FLOAT;
|
||||
}
|
||||
if (arraysContains(new String[]{"float", "float8", "double", "double precision", "binary_double"}, dataType)) {
|
||||
return GenConstants.TYPE_DOUBLE;
|
||||
}
|
||||
if (arraysContains(new String[]{"bigint", "int8", "bigserial", "serial8"}, dataType)) {
|
||||
return GenConstants.TYPE_LONG;
|
||||
}
|
||||
if (arraysContains(new String[]{"tinyint", "smallint", "mediumint", "int", "int2", "int4", "integer",
|
||||
"smallserial", "serial", "serial2", "serial4"}, dataType)) {
|
||||
return GenConstants.TYPE_INTEGER;
|
||||
}
|
||||
if (StringUtils.equals(dataType, "number")) {
|
||||
if (columnScale > 0) {
|
||||
return GenConstants.TYPE_BIGDECIMAL;
|
||||
}
|
||||
return resolveIntegerJavaType(columnLength);
|
||||
}
|
||||
return GenConstants.TYPE_LONG;
|
||||
}
|
||||
|
||||
/**
|
||||
* 规范化数据库字段类型,去除 MySQL 等数据库的类型修饰。
|
||||
*
|
||||
* @param dataType 数据库字段类型
|
||||
* @return 规范化后的数据库字段类型
|
||||
*/
|
||||
private static String normalizeDbType(String dataType) {
|
||||
return StringUtils.trim(dataType)
|
||||
.toLowerCase()
|
||||
.replace(" unsigned", "")
|
||||
.replace(" zerofill", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据整数字段长度解析 Java 类型。
|
||||
*
|
||||
* @param columnLength 字段长度
|
||||
* @return Java 类型
|
||||
*/
|
||||
private static String resolveIntegerJavaType(Integer columnLength) {
|
||||
if (columnLength > 0 && columnLength <= 9) {
|
||||
return GenConstants.TYPE_INTEGER;
|
||||
}
|
||||
return GenConstants.TYPE_LONG;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字段是否适合按布尔类型生成。
|
||||
*
|
||||
* @param dataType 数据库字段类型
|
||||
* @param columnLength 字段长度
|
||||
* @param columnScale 小数位数
|
||||
* @param columnName 字段名称
|
||||
* @return 是否布尔字段
|
||||
*/
|
||||
private static boolean isBooleanColumn(String dataType, Integer columnLength, Integer columnScale, String columnName) {
|
||||
if (columnScale > 0) {
|
||||
return false;
|
||||
}
|
||||
if (StringUtils.equalsAny(dataType, "bit", "boolean", "bool")) {
|
||||
return true;
|
||||
}
|
||||
if (StringUtils.equalsAny(dataType, "tinyint", "number", "numeric", "decimal", "char", "nchar")
|
||||
&& columnLength == 1 && isSwitchColumn(columnName)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字段名称是否为开关类字段。
|
||||
*
|
||||
* @param columnName 字段名称
|
||||
* @return 是否开关类字段
|
||||
*/
|
||||
private static boolean isSwitchColumn(String columnName) {
|
||||
return StringUtils.endsWithAny(columnName, "status", "flag", "enabled", "disabled", "available", "visible")
|
||||
|| columnName.startsWith("is_")
|
||||
|| columnName.startsWith("has_")
|
||||
|| columnName.startsWith("enable_")
|
||||
|| columnName.startsWith("disable_");
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断字段名称是否为排序字段。
|
||||
*
|
||||
* @param columnName 字段名称
|
||||
* @return 是否排序字段
|
||||
*/
|
||||
private static boolean isSortColumn(String columnName) {
|
||||
return StringUtils.endsWithAny(columnName, "sort", "order_num", "order", "rank", "seq", "sequence");
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验数组是否包含指定值
|
||||
*
|
||||
* @param arr 数组
|
||||
* @param targetValue 值
|
||||
* @return 是否包含
|
||||
*/
|
||||
public static boolean arraysContains(String[] arr, String targetValue) {
|
||||
return Arrays.asList(arr).contains(targetValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模块名
|
||||
*
|
||||
* @param packageName 包名
|
||||
* @return 模块名
|
||||
*/
|
||||
public static String getModuleName(String packageName) {
|
||||
int lastIndex = packageName.lastIndexOf(".");
|
||||
int nameLength = packageName.length();
|
||||
return StringUtils.substring(packageName, lastIndex + 1, nameLength);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取业务名
|
||||
*
|
||||
* @param tableName 表名
|
||||
* @return 业务名
|
||||
*/
|
||||
public static String getBusinessName(String tableName) {
|
||||
int firstIndex = tableName.indexOf("_");
|
||||
int nameLength = tableName.length();
|
||||
String businessName = StringUtils.substring(tableName, firstIndex + 1, nameLength);
|
||||
businessName = StringUtils.toCamelCase(businessName);
|
||||
return businessName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 表名转换成Java类名
|
||||
*
|
||||
* @param tableName 表名称
|
||||
* @return 类名
|
||||
*/
|
||||
public static String convertClassName(String tableName) {
|
||||
boolean autoRemovePre = PROPERTIES.isAutoRemovePre();
|
||||
String tablePrefix = PROPERTIES.getTablePrefix();
|
||||
if (autoRemovePre && StringUtils.isNotEmpty(tablePrefix)) {
|
||||
String[] searchList = StringUtils.split(tablePrefix, StringUtils.SEPARATOR);
|
||||
tableName = replaceFirst(tableName, searchList);
|
||||
}
|
||||
return StringUtils.convertToCamelCase(tableName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量替换前缀
|
||||
*
|
||||
* @param replacementm 替换值
|
||||
* @param searchList 替换列表
|
||||
* @return 去除命中前缀后的字符串
|
||||
*/
|
||||
public static String replaceFirst(String replacementm, String[] searchList) {
|
||||
String text = replacementm;
|
||||
for (String searchString : searchList) {
|
||||
if (replacementm.startsWith(searchString)) {
|
||||
text = StringUtils.removeStart(replacementm, searchString);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键字替换
|
||||
*
|
||||
* @param text 需要被替换的名字
|
||||
* @return 替换后的名字
|
||||
*/
|
||||
public static String replaceText(String text) {
|
||||
return RegExUtils.replaceAll(text, "(?:表|若依)", "");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库类型字段
|
||||
*
|
||||
* @param columnType 列类型
|
||||
* @return 截取后的列类型
|
||||
*/
|
||||
public static String getDbType(String columnType) {
|
||||
if (StringUtils.indexOf(columnType, "(") > 0) {
|
||||
return StringUtils.substringBefore(columnType, "(");
|
||||
} else {
|
||||
return columnType;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段长度
|
||||
*
|
||||
* @param columnType 列类型
|
||||
* @return 字段长度,未声明长度时返回 0
|
||||
*/
|
||||
public static Integer getColumnLength(String columnType) {
|
||||
if (StringUtils.indexOf(columnType, "(") > 0) {
|
||||
String length = StringUtils.substringBetween(columnType, "(", ")").trim();
|
||||
// 处理 decimal(10,2) 这类带精度的类型,只取长度部分
|
||||
if (length.contains(",")) {
|
||||
length = StringUtils.substringBefore(length, ",").trim();
|
||||
}
|
||||
return Integer.valueOf(length);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字段精度
|
||||
*
|
||||
* @param columnType 列类型
|
||||
* @return 字段精度,未声明精度时返回 0
|
||||
*/
|
||||
public static Integer getColumnScale(String columnType) {
|
||||
if (StringUtils.indexOf(columnType, "(") > 0) {
|
||||
String length = StringUtils.substringBetween(columnType, "(", ")").trim();
|
||||
if (length.contains(",")) {
|
||||
return Integer.valueOf(StringUtils.substringAfter(length, ",").trim());
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,763 @@
|
||||
package org.dromara.gen.util;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.convert.Convert;
|
||||
import cn.hutool.core.lang.Dict;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.template.TemplateConfig;
|
||||
import cn.hutool.extra.template.TemplateEngine;
|
||||
import cn.hutool.extra.template.TemplateUtil;
|
||||
import cn.hutool.extra.template.engine.freemarker.FreemarkerEngine;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.DateUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.json.utils.JsonUtils;
|
||||
import org.dromara.common.mybatis.enums.DataBaseType;
|
||||
import org.dromara.common.mybatis.helper.DataBaseHelper;
|
||||
import org.dromara.gen.constant.GenConstants;
|
||||
import org.dromara.gen.domain.GenTable;
|
||||
import org.dromara.gen.domain.GenTableColumn;
|
||||
import org.dromara.gen.util.template.PathNamedTemplate;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
|
||||
import org.springframework.core.io.support.ResourcePatternResolver;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 模板引擎工具
|
||||
*/
|
||||
@Slf4j
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class TemplateEngineUtils {
|
||||
|
||||
/**
|
||||
* 项目空间路径
|
||||
*/
|
||||
private static final String PROJECT_PATH = "main/java";
|
||||
|
||||
/**
|
||||
* mybatis空间路径
|
||||
*/
|
||||
private static final String MYBATIS_PATH = "main/resources/mapper";
|
||||
|
||||
/**
|
||||
* 默认上级菜单,系统工具
|
||||
*/
|
||||
private static final String DEFAULT_PARENT_MENU_ID = "1761400000000000003";
|
||||
|
||||
// 模板引擎
|
||||
private static final TemplateEngine TEMPLATE_ENGINE;
|
||||
private static final Map<String, PathNamedTemplate> TEMPLATE_MAPPER;
|
||||
private static final ResourcePatternResolver RESOURCE_PATTERN_RESOLVER = new PathMatchingResourcePatternResolver();
|
||||
|
||||
static {
|
||||
// 模板引擎初始化
|
||||
TemplateConfig templateConfig = new TemplateConfig(StandardCharsets.UTF_8, StringUtils.EMPTY, TemplateConfig.ResourceMode.CLASSPATH);
|
||||
templateConfig.setCustomEngine(FreemarkerEngine.class);
|
||||
TEMPLATE_ENGINE = TemplateUtil.createEngine(templateConfig);
|
||||
// 禁用数字千分位格式化 避免 Long 类型的 ID 渲染是被加上逗号
|
||||
if (TEMPLATE_ENGINE instanceof FreemarkerEngine freemarkerEngine) {
|
||||
freemarkerEngine.getConfiguration().setNumberFormat("computer");
|
||||
}
|
||||
TEMPLATE_MAPPER = PathNamedTemplate.form(TEMPLATE_ENGINE, GenConstants.TEMPLATE_PATHS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建模板上下文
|
||||
*
|
||||
* @param contextInit 模板上下文初始化函数
|
||||
* @return 模板上下文
|
||||
*/
|
||||
public static Dict buildContext(Consumer<Dict> contextInit) {
|
||||
Dict context = new Dict();
|
||||
contextInit.accept(context);
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建模板上下文
|
||||
*
|
||||
* @param genTable 代码生成业务表对象
|
||||
* @return 模板上下文
|
||||
*/
|
||||
public static Dict buildContext(GenTable genTable) {
|
||||
// 构建上下文
|
||||
Dict context = new Dict();
|
||||
String moduleName = genTable.getModuleName();
|
||||
String businessName = genTable.getBusinessName();
|
||||
String packageName = genTable.getPackageName();
|
||||
String tplCategory = genTable.getTplCategory();
|
||||
String functionName = genTable.getFunctionName();
|
||||
|
||||
context.put("tplCategory", genTable.getTplCategory());
|
||||
context.put("frontendType", resolveFrontendType(genTable.getFrontendType()));
|
||||
context.put("tableName", genTable.getTableName());
|
||||
context.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
|
||||
context.put("ClassName", genTable.getClassName());
|
||||
context.put("className", StringUtils.uncapitalize(genTable.getClassName()));
|
||||
context.put("moduleName", moduleName);
|
||||
context.put("BusinessName", StringUtils.capitalize(businessName));
|
||||
context.put("businessName", businessName);
|
||||
context.put("basePackage", getPackagePrefix(packageName));
|
||||
context.put("packageName", packageName);
|
||||
context.put("author", genTable.getFunctionAuthor());
|
||||
context.put("datetime", DateUtils.now());
|
||||
context.put("pkColumn", genTable.getPkColumn());
|
||||
String dicts = getDicts(genTable);
|
||||
context.put("importList", getImportList(genTable));
|
||||
context.put("permissionPrefix", getPermissionPrefix(moduleName, businessName));
|
||||
context.put("columns", genTable.getColumns());
|
||||
context.put("table", genTable);
|
||||
context.put("dicts", dicts);
|
||||
context.put("dictsNoSymbol", StringUtils.replace(dicts, "'", StringUtils.EMPTY));
|
||||
setColumnFeatureContext(context, genTable, dicts);
|
||||
// 向模板上下文写入菜单相关变量
|
||||
String options = genTable.getOptions();
|
||||
Dict paramsObj = JsonUtils.parseMap(options);
|
||||
if (ObjectUtil.isNull(paramsObj)) {
|
||||
paramsObj = new Dict();
|
||||
}
|
||||
String parentMenuId = getParentMenuId(paramsObj);
|
||||
context.put("parentMenuId", parentMenuId);
|
||||
boolean enableExport = getBooleanOption(paramsObj, GenConstants.ENABLE_EXPORT, true);
|
||||
boolean enableStatus = getBooleanOption(paramsObj, GenConstants.ENABLE_STATUS, false);
|
||||
boolean enableUnique = getBooleanOption(paramsObj, GenConstants.ENABLE_UNIQUE, false);
|
||||
boolean enableSort = getBooleanOption(paramsObj, GenConstants.ENABLE_SORT, false);
|
||||
GenTableColumn statusColumn = getColumn(genTable, paramsObj.getStr(GenConstants.STATUS_FIELD));
|
||||
GenTableColumn sortColumn = getColumn(genTable, paramsObj.getStr(GenConstants.SORT_FIELD));
|
||||
List<GenTableColumn> uniqueColumns = getColumns(genTable, paramsObj.get(GenConstants.UNIQUE_FIELDS));
|
||||
context.put("enableExport", enableExport);
|
||||
context.put("enableStatus", enableStatus && ObjectUtil.isNotNull(statusColumn));
|
||||
context.put("statusColumn", statusColumn);
|
||||
context.put("statusField", ObjectUtil.isNotNull(statusColumn) ? statusColumn.getJavaField() : StringUtils.EMPTY);
|
||||
context.put("enableUnique", enableUnique && CollUtil.isNotEmpty(uniqueColumns));
|
||||
context.put("uniqueColumns", uniqueColumns);
|
||||
context.put("enableSort", enableSort && ObjectUtil.isNotNull(sortColumn));
|
||||
context.put("sortColumn", sortColumn);
|
||||
context.put("sortField", ObjectUtil.isNotNull(sortColumn) ? sortColumn.getJavaField() : StringUtils.EMPTY);
|
||||
context.put("statusActiveValue", ObjectUtil.isNotNull(statusColumn) ? statusColumn.getSwitchActiveValue() : StringUtils.EMPTY);
|
||||
context.put("statusInactiveValue", ObjectUtil.isNotNull(statusColumn) ? statusColumn.getSwitchInactiveValue() : StringUtils.EMPTY);
|
||||
|
||||
// 向树形模板上下文写入树字段相关变量
|
||||
if (GenConstants.TPL_TREE.equals(tplCategory)) {
|
||||
setTreeContext(context, genTable, paramsObj);
|
||||
}
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入模板中常用的列派生开关,避免在模板内反复扫描和处理。
|
||||
*
|
||||
* @param context 模板上下文
|
||||
* @param genTable 代码生成业务表对象
|
||||
* @param dicts 字典类型表达式
|
||||
*/
|
||||
private static void setColumnFeatureContext(Dict context, GenTable genTable, String dicts) {
|
||||
boolean hasBetween = false;
|
||||
boolean needImagePreview = false;
|
||||
boolean needImageUpload = false;
|
||||
boolean needFileUpload = false;
|
||||
boolean needEditor = false;
|
||||
boolean needCheckbox = false;
|
||||
boolean needSelect = false;
|
||||
boolean needTextArea = false;
|
||||
boolean needDigit = false;
|
||||
boolean needDateField = false;
|
||||
boolean needSwitchField = false;
|
||||
boolean needParseTime = false;
|
||||
String firstTreeListField = StringUtils.EMPTY;
|
||||
for (GenTableColumn column : genTable.getColumns()) {
|
||||
boolean writable = column.isInsert() || column.isEdit();
|
||||
hasBetween = hasBetween || column.isQuery() && StringUtils.equals(column.getQueryType(), GenConstants.QUERY_BETWEEN);
|
||||
needImagePreview = needImagePreview || column.isList() && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_IMAGE_UPLOAD);
|
||||
needImageUpload = needImageUpload || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_IMAGE_UPLOAD);
|
||||
needFileUpload = needFileUpload || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_FILE_UPLOAD);
|
||||
needEditor = needEditor || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_EDITOR);
|
||||
needCheckbox = needCheckbox || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_CHECKBOX);
|
||||
needSelect = needSelect || (writable || column.isQuery()) && StringUtils.equalsAny(column.getHtmlType(),
|
||||
GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_SWITCH);
|
||||
needTextArea = needTextArea || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_TEXTAREA);
|
||||
needDigit = needDigit || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_INPUT_NUMBER);
|
||||
needDateField = needDateField || writable && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_DATETIME);
|
||||
needSwitchField = needSwitchField || (writable || column.isList()) && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_SWITCH);
|
||||
needParseTime = needParseTime || column.isList() && StringUtils.equals(column.getHtmlType(), GenConstants.HTML_DATETIME);
|
||||
if (StringUtils.isBlank(firstTreeListField) && column.isList()) {
|
||||
firstTreeListField = column.getJavaField();
|
||||
}
|
||||
}
|
||||
boolean needDateRange = hasDateRangeQuery(genTable);
|
||||
context.put("hasBetween", hasBetween);
|
||||
context.put("needAddDateRange", needDateRange);
|
||||
context.put("needDateRange", needDateRange);
|
||||
context.put("needDict", StringUtils.isNotBlank(dicts));
|
||||
context.put("needImagePreview", needImagePreview);
|
||||
context.put("needImageUpload", needImageUpload);
|
||||
context.put("needFileUpload", needFileUpload);
|
||||
context.put("needEditor", needEditor);
|
||||
context.put("needCheckbox", needCheckbox);
|
||||
context.put("needSelect", needSelect);
|
||||
context.put("needTextArea", needTextArea);
|
||||
context.put("needDigit", needDigit);
|
||||
context.put("needDateField", needDateField);
|
||||
context.put("needSwitchField", needSwitchField);
|
||||
context.put("needParseTime", needParseTime);
|
||||
context.put("firstTreeListField", firstTreeListField);
|
||||
}
|
||||
|
||||
private static boolean hasDateRangeQuery(GenTable genTable) {
|
||||
for (GenTableColumn column : genTable.getColumns()) {
|
||||
if (column.isDateRangeQuery()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 向树形模板上下文写入树字段相关变量。
|
||||
*
|
||||
* @param context 模板上下文
|
||||
* @param genTable 代码生成业务表对象
|
||||
* @param paramsObj 已解析的 options 参数(避免重复解析)
|
||||
*/
|
||||
public static void setTreeContext(Dict context, GenTable genTable, Dict paramsObj) {
|
||||
String treeCode = getTreeCode(paramsObj);
|
||||
String treeParentCode = getTreeParentCode(paramsObj);
|
||||
String treeName = getTreeName(paramsObj);
|
||||
GenTableColumn treeParentColumn = getColumn(genTable, paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
|
||||
GenTableColumn treeAncestorsColumn = getColumn(genTable, paramsObj.getStr(GenConstants.TREE_ANCESTORS));
|
||||
GenTableColumn treeOrderColumn = getColumn(genTable, paramsObj.getStr(GenConstants.TREE_ORDER_FIELD));
|
||||
String treeRootValue = getTreeRootValue(paramsObj, treeParentColumn);
|
||||
|
||||
context.put("treeCode", treeCode);
|
||||
context.put("treeParentCode", treeParentCode);
|
||||
context.put("treeName", treeName);
|
||||
context.put("treeParentColumn", treeParentColumn);
|
||||
context.put("treeAncestorsField", ObjectUtil.isNotNull(treeAncestorsColumn) ? treeAncestorsColumn.getJavaField() : StringUtils.EMPTY);
|
||||
context.put("treeOrderField", ObjectUtil.isNotNull(treeOrderColumn) ? treeOrderColumn.getJavaField() : StringUtils.EMPTY);
|
||||
context.put("treeOrderColumn", treeOrderColumn);
|
||||
context.put("treeRootValue", treeRootValue);
|
||||
context.put("treeRootValueJavaLiteral", getJavaLiteral(treeParentColumn, treeRootValue));
|
||||
context.put("treeRootValueTsLiteral", getTsLiteral(treeParentColumn, treeRootValue));
|
||||
context.put("treeParentCap", StringUtils.capitalize(treeParentCode));
|
||||
context.put("treeAncestorsCap", StringUtils.capitalize(ObjectUtil.isNotNull(treeAncestorsColumn) ? treeAncestorsColumn.getJavaField() : StringUtils.EMPTY));
|
||||
context.put("treeOrderCap", StringUtils.capitalize(ObjectUtil.isNotNull(treeOrderColumn) ? treeOrderColumn.getJavaField() : StringUtils.EMPTY));
|
||||
String expandTreeName = paramsObj.getStr(GenConstants.TREE_NAME);
|
||||
int expandColumn = 0;
|
||||
for (GenTableColumn column : genTable.getColumns()) {
|
||||
if (column.isList()) {
|
||||
expandColumn++;
|
||||
if (column.getColumnName().equals(expandTreeName)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
context.put("expandColumn", expandColumn);
|
||||
if (paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
|
||||
context.put("tree_parent_code", paramsObj.get(GenConstants.TREE_PARENT_CODE));
|
||||
}
|
||||
if (paramsObj.containsKey(GenConstants.TREE_NAME)) {
|
||||
context.put("tree_name", paramsObj.get(GenConstants.TREE_NAME));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板信息
|
||||
*
|
||||
* @return 模板列表
|
||||
*/
|
||||
public static List<PathNamedTemplate> getTemplateList(String tplCategory, String dsName) {
|
||||
return getTemplateList(tplCategory, dsName, GenConstants.FRONTEND_TYPE_VUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板信息
|
||||
*
|
||||
* @param tplCategory 前端页面模板分类
|
||||
* @param dsName 数据源名称
|
||||
* @param frontendType 前端模板类型
|
||||
* @return 模板列表
|
||||
*/
|
||||
public static List<PathNamedTemplate> getTemplateList(String tplCategory, String dsName, String frontendType) {
|
||||
List<PathNamedTemplate> templates = new ArrayList<>();
|
||||
// 后端源码模板
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_DOMAIN_TEMPLATE_PATH));
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_VO_TEMPLATE_PATH));
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_BO_TEMPLATE_PATH));
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_MAPPER_TEMPLATE_PATH));
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_SERVICE_TEMPLATE_PATH));
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_SERVICE_IMPL_TEMPLATE_PATH));
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.JAVA_CONTROLLER_TEMPLATE_PATH));
|
||||
// MyBatis MapperXML 模板
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.XML_MAPPER_TEMPLATE_PATH));
|
||||
// 前端 API 与类型模板
|
||||
templates.add(getTemplate(getFrontendApiTemplatePath(frontendType)));
|
||||
templates.add(getTemplate(getFrontendTypesTemplatePath(frontendType)));
|
||||
// 数据库模板
|
||||
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType(dsName);
|
||||
if (dataBaseType.isOracle()) {
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.SQL_ORACLE_TEMPLATE_PATH));
|
||||
} else if (dataBaseType.isPostgreSql()) {
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.SQL_POSTGRES_TEMPLATE_PATH));
|
||||
} else if (dataBaseType.isSqlServer()) {
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.SQL_SQLSERVER_TEMPLATE_PATH));
|
||||
} else {
|
||||
// 默认使用MySQL模板
|
||||
templates.add(TEMPLATE_MAPPER.get(GenConstants.SQL_MYSQL_TEMPLATE_PATH));
|
||||
}
|
||||
// 前端页面模板
|
||||
if (GenConstants.TPL_CRUD.equals(tplCategory)) {
|
||||
templates.add(getTemplate(getFrontendIndexTemplatePath(frontendType)));
|
||||
} else if (GenConstants.TPL_TREE.equals(tplCategory)) {
|
||||
templates.add(getTemplate(getFrontendIndexTreeTemplatePath(frontendType)));
|
||||
}
|
||||
return templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取模板,支持按前端模板类型动态加载模板根目录下的模板。
|
||||
*
|
||||
* @param templatePath 模板路径
|
||||
* @return 带路径名的模板
|
||||
*/
|
||||
private static PathNamedTemplate getTemplate(String templatePath) {
|
||||
PathNamedTemplate template = TEMPLATE_MAPPER.get(templatePath);
|
||||
if (ObjectUtil.isNotNull(template)) {
|
||||
return template;
|
||||
}
|
||||
return PathNamedTemplate.form(TEMPLATE_ENGINE, templatePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件名
|
||||
*
|
||||
* @param template 模板路径
|
||||
* @param genTable 代码生成业务表对象
|
||||
* @return 模板对应的目标文件相对路径
|
||||
*/
|
||||
public static String getFileName(String template, GenTable genTable) {
|
||||
// 文件名称
|
||||
String fileName = "";
|
||||
// 包路径
|
||||
String packageName = genTable.getPackageName();
|
||||
// 模块名
|
||||
String moduleName = genTable.getModuleName();
|
||||
// 大写类名
|
||||
String className = genTable.getClassName();
|
||||
// 业务名称
|
||||
String businessName = genTable.getBusinessName();
|
||||
|
||||
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
|
||||
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
|
||||
String frontendPath = getFrontendPath(genTable.getFrontendType());
|
||||
String frontendPagePath = getFrontendPagePath(genTable.getFrontendType());
|
||||
// templatePath
|
||||
// genFilePathFormat
|
||||
if (template.contains("domain.java.")) {
|
||||
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
|
||||
} else if (template.contains("vo.java.")) {
|
||||
fileName = StringUtils.format("{}/domain/vo/{}Vo.java", javaPath, className);
|
||||
} else if (template.contains("bo.java.")) {
|
||||
fileName = StringUtils.format("{}/domain/bo/{}Bo.java", javaPath, className);
|
||||
} else if (template.contains("mapper.java.")) {
|
||||
fileName = StringUtils.format("{}/mapper/{}Mapper.java", javaPath, className);
|
||||
} else if (template.contains("service.java.")) {
|
||||
fileName = StringUtils.format("{}/service/I{}Service.java", javaPath, className);
|
||||
} else if (template.contains("serviceImpl.java.")) {
|
||||
fileName = StringUtils.format("{}/service/impl/{}ServiceImpl.java", javaPath, className);
|
||||
} else if (template.contains("controller.java.")) {
|
||||
fileName = StringUtils.format("{}/controller/{}Controller.java", javaPath, className);
|
||||
} else if (template.contains("mapper.xml.")) {
|
||||
fileName = StringUtils.format("{}/{}Mapper.xml", mybatisPath, className);
|
||||
} else if (template.contains("sql.")) {
|
||||
fileName = businessName + "Menu.sql";
|
||||
} else if (template.contains("api.ts.")) {
|
||||
fileName = StringUtils.format("{}/api/{}/{}/index.ts", frontendPath, moduleName, businessName);
|
||||
} else if (template.contains("types.ts.")) {
|
||||
fileName = StringUtils.format("{}/api/{}/{}/types.ts", frontendPath, moduleName, businessName);
|
||||
} else if (isFrontendPageTemplate(template, GenConstants.FRONTEND_INDEX_TEMPLATE_PREFIX)) {
|
||||
fileName = StringUtils.format("{}/{}/{}/{}/index.{}", frontendPath, frontendPagePath, moduleName, businessName,
|
||||
getFrontendPageExtension(template, GenConstants.FRONTEND_INDEX_TEMPLATE_PREFIX));
|
||||
} else if (isFrontendPageTemplate(template, GenConstants.FRONTEND_INDEX_TREE_TEMPLATE_PREFIX)) {
|
||||
fileName = StringUtils.format("{}/{}/{}/{}/index.{}", frontendPath, frontendPagePath, moduleName, businessName,
|
||||
getFrontendPageExtension(template, GenConstants.FRONTEND_INDEX_TREE_TEMPLATE_PREFIX));
|
||||
}
|
||||
return fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端输出目录。
|
||||
*
|
||||
* @param frontendType 前端模板类型
|
||||
* @return 前端输出目录
|
||||
*/
|
||||
private static String getFrontendPath(String frontendType) {
|
||||
return resolveFrontendType(frontendType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端页面输出目录。
|
||||
*
|
||||
* @param frontendType 前端模板类型
|
||||
* @return 页面输出目录
|
||||
*/
|
||||
private static String getFrontendPagePath(String frontendType) {
|
||||
return GenConstants.FRONTEND_TYPE_REACT.equals(resolveFrontendType(frontendType)) ? "pages" : "views";
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端页面文件扩展名。
|
||||
*
|
||||
* @param template 模板路径
|
||||
* @param templatePrefix 模板文件名前缀
|
||||
* @return 文件扩展名
|
||||
*/
|
||||
private static String getFrontendPageExtension(String template, String templatePrefix) {
|
||||
String fileName = getTemplateFileName(template);
|
||||
int suffixIndex = fileName.lastIndexOf('.');
|
||||
String withoutTemplateSuffix = suffixIndex >= 0 ? fileName.substring(0, suffixIndex) : fileName;
|
||||
return withoutTemplateSuffix.substring((templatePrefix + ".").length());
|
||||
}
|
||||
|
||||
private static String getFrontendApiTemplatePath(String frontendType) {
|
||||
return getFrontendTemplatePath(frontendType, GenConstants.FRONTEND_API_TEMPLATE_NAME);
|
||||
}
|
||||
|
||||
private static String getFrontendTypesTemplatePath(String frontendType) {
|
||||
return getFrontendTemplatePath(frontendType, GenConstants.FRONTEND_TYPES_TEMPLATE_NAME);
|
||||
}
|
||||
|
||||
private static String getFrontendIndexTemplatePath(String frontendType) {
|
||||
return getFrontendPageTemplatePath(frontendType, GenConstants.FRONTEND_INDEX_TEMPLATE_PREFIX);
|
||||
}
|
||||
|
||||
private static String getFrontendIndexTreeTemplatePath(String frontendType) {
|
||||
return getFrontendPageTemplatePath(frontendType, GenConstants.FRONTEND_INDEX_TREE_TEMPLATE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析前端模板类型。
|
||||
*
|
||||
* @param frontendType 前端模板类型
|
||||
* @return 已规范化的模板目录名
|
||||
*/
|
||||
private static String resolveFrontendType(String frontendType) {
|
||||
String type = StringUtils.blankToDefault(frontendType, GenConstants.FRONTEND_TYPE_VUE);
|
||||
if (!type.matches("[A-Za-z0-9_-]+")) {
|
||||
throw new ServiceException("前端模板类型仅支持字母、数字、下划线和中划线");
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static String getFrontendTemplatePath(String frontendType, String templateName) {
|
||||
return StringUtils.format("{}/{}/{}", GenConstants.TEMPLATE_ROOT_PATH, resolveFrontendType(frontendType), templateName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取前端页面模板路径,页面输出扩展名由模板文件名决定。
|
||||
*
|
||||
* @param frontendType 前端模板类型
|
||||
* @param templatePrefix 页面模板文件名前缀
|
||||
* @return 页面模板路径
|
||||
*/
|
||||
private static String getFrontendPageTemplatePath(String frontendType, String templatePrefix) {
|
||||
String type = resolveFrontendType(frontendType);
|
||||
String templatePathPattern = getFrontendPageTemplatePathPattern(type, templatePrefix);
|
||||
String resourcePattern = GenConstants.TEMPLATE_RESOURCE_PREFIX + templatePathPattern;
|
||||
try {
|
||||
Resource[] resources = RESOURCE_PATTERN_RESOLVER.getResources(resourcePattern);
|
||||
if (resources.length == 0) {
|
||||
throw new ServiceException(StringUtils.format("未找到前端模板: {}", templatePathPattern));
|
||||
}
|
||||
return Arrays.stream(resources)
|
||||
.map(Resource::getFilename)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.sorted()
|
||||
.findFirst()
|
||||
.map(fileName -> StringUtils.format("{}/{}/{}", GenConstants.TEMPLATE_ROOT_PATH, type, fileName))
|
||||
.orElseThrow(() -> new ServiceException(StringUtils.format("未找到前端模板: {}", templatePathPattern)));
|
||||
} catch (IOException e) {
|
||||
throw new ServiceException(StringUtils.format("读取前端模板失败: {}", templatePathPattern), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getFrontendPageTemplatePathPattern(String frontendType, String templatePrefix) {
|
||||
return StringUtils.format("{}/{}/{}{}", GenConstants.TEMPLATE_ROOT_PATH, frontendType,
|
||||
templatePrefix, GenConstants.TEMPLATE_PAGE_PATTERN_SUFFIX);
|
||||
}
|
||||
|
||||
private static boolean isFrontendPageTemplate(String template, String templatePrefix) {
|
||||
String fileName = getTemplateFileName(template);
|
||||
return fileName.startsWith(templatePrefix + ".") && fileName.endsWith(GenConstants.TEMPLATE_FILE_SUFFIX);
|
||||
}
|
||||
|
||||
private static String getTemplateFileName(String template) {
|
||||
int index = Math.max(template.lastIndexOf('/'), template.lastIndexOf('\\'));
|
||||
return index >= 0 ? template.substring(index + 1) : template;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取包前缀
|
||||
*
|
||||
* @param packageName 包名称
|
||||
* @return 包前缀名称
|
||||
*/
|
||||
public static String getPackagePrefix(String packageName) {
|
||||
int lastIndex = packageName.lastIndexOf(".");
|
||||
return StringUtils.substring(packageName, 0, lastIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列类型获取导入包
|
||||
*
|
||||
* @param genTable 业务表对象
|
||||
* @return 返回需要导入的包列表
|
||||
*/
|
||||
public static HashSet<String> getImportList(GenTable genTable) {
|
||||
List<GenTableColumn> columns = genTable.getColumns();
|
||||
HashSet<String> importList = new HashSet<>();
|
||||
for (GenTableColumn column : columns) {
|
||||
if (needImportColumn(column) && GenConstants.TYPE_DATE.equals(column.getJavaType())) {
|
||||
importList.add("java.time.LocalDateTime");
|
||||
importList.add("com.fasterxml.jackson.annotation.JsonFormat");
|
||||
} else if (needImportColumn(column) && GenConstants.TYPE_BIGDECIMAL.equals(column.getJavaType())) {
|
||||
importList.add("java.math.BigDecimal");
|
||||
}
|
||||
}
|
||||
return importList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断列是否需要生成类型导入。
|
||||
*
|
||||
* @param column 列对象
|
||||
* @return 需要生成类型导入返回 {@code true}
|
||||
*/
|
||||
private static boolean needImportColumn(GenTableColumn column) {
|
||||
return !column.isSuperColumn() || column.isQuery();
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据列类型获取字典组
|
||||
*
|
||||
* @param genTable 业务表对象
|
||||
* @return 返回字典组
|
||||
*/
|
||||
public static String getDicts(GenTable genTable) {
|
||||
List<GenTableColumn> columns = genTable.getColumns();
|
||||
Set<String> dicts = new HashSet<>();
|
||||
addDicts(dicts, columns);
|
||||
return StringUtils.join(dicts, ", ");
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加字典列表
|
||||
*
|
||||
* @param dicts 字典列表
|
||||
* @param columns 列集合
|
||||
*/
|
||||
public static void addDicts(Set<String> dicts, List<GenTableColumn> columns) {
|
||||
for (GenTableColumn column : columns) {
|
||||
if (!column.isSuperColumn() && StringUtils.isNotEmpty(column.getDictType()) && StringUtils.equalsAny(
|
||||
column.getHtmlType(),
|
||||
GenConstants.HTML_SELECT, GenConstants.HTML_RADIO, GenConstants.HTML_CHECKBOX, GenConstants.HTML_SWITCH)) {
|
||||
dicts.add("'" + column.getDictType() + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取权限前缀
|
||||
*
|
||||
* @param moduleName 模块名称
|
||||
* @param businessName 业务名称
|
||||
* @return 返回权限前缀
|
||||
*/
|
||||
public static String getPermissionPrefix(String moduleName, String businessName) {
|
||||
return StringUtils.format("{}:{}", moduleName, businessName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取上级菜单ID字段
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 上级菜单ID字段
|
||||
*/
|
||||
public static String getParentMenuId(Dict paramsObj) {
|
||||
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.PARENT_MENU_ID)
|
||||
&& StringUtils.isNotEmpty(paramsObj.getStr(GenConstants.PARENT_MENU_ID))) {
|
||||
return paramsObj.getStr(GenConstants.PARENT_MENU_ID);
|
||||
}
|
||||
return DEFAULT_PARENT_MENU_ID;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树编码
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 树编码
|
||||
*/
|
||||
public static String getTreeCode(Map<String, Object> paramsObj) {
|
||||
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_CODE)) {
|
||||
return StringUtils.toCamelCase(Convert.toStr(paramsObj.get(GenConstants.TREE_CODE)));
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树父编码
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 树父编码
|
||||
*/
|
||||
public static String getTreeParentCode(Dict paramsObj) {
|
||||
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_PARENT_CODE)) {
|
||||
return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_PARENT_CODE));
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树名称
|
||||
*
|
||||
* @param paramsObj 生成其他选项
|
||||
* @return 树名称
|
||||
*/
|
||||
public static String getTreeName(Dict paramsObj) {
|
||||
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_NAME)) {
|
||||
return StringUtils.toCamelCase(paramsObj.getStr(GenConstants.TREE_NAME));
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取树根节点值。
|
||||
*
|
||||
* @param paramsObj 其他选项
|
||||
* @param treeParentColumn 父节点字段
|
||||
* @return 树根节点值
|
||||
*/
|
||||
public static String getTreeRootValue(Dict paramsObj, GenTableColumn treeParentColumn) {
|
||||
String defaultValue = "0";
|
||||
if (ObjectUtil.isNotNull(treeParentColumn) && StringUtils.equals(treeParentColumn.getJavaType(), GenConstants.TYPE_STRING)) {
|
||||
defaultValue = "0";
|
||||
}
|
||||
if (CollUtil.isNotEmpty(paramsObj) && paramsObj.containsKey(GenConstants.TREE_ROOT_VALUE)) {
|
||||
return StringUtils.blankToDefault(paramsObj.getStr(GenConstants.TREE_ROOT_VALUE), defaultValue);
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取布尔类型生成选项。
|
||||
*
|
||||
* @param paramsObj 生成选项
|
||||
* @param key 选项键
|
||||
* @param defaultValue 默认值
|
||||
* @return 选项值
|
||||
*/
|
||||
private static boolean getBooleanOption(Dict paramsObj, String key, boolean defaultValue) {
|
||||
if (CollUtil.isEmpty(paramsObj) || !paramsObj.containsKey(key)) {
|
||||
return defaultValue;
|
||||
}
|
||||
return Convert.toBool(paramsObj.get(key), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段名查找业务表字段。
|
||||
*
|
||||
* @param genTable 业务表
|
||||
* @param field 字段名称或 Java 属性名
|
||||
* @return 业务表字段
|
||||
*/
|
||||
private static GenTableColumn getColumn(GenTable genTable, String field) {
|
||||
if (StringUtils.isBlank(field) || CollUtil.isEmpty(genTable.getColumns())) {
|
||||
return null;
|
||||
}
|
||||
for (GenTableColumn column : genTable.getColumns()) {
|
||||
if (StringUtils.equalsAny(field, column.getColumnName(), column.getJavaField())) {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据字段配置查找业务表字段列表。
|
||||
*
|
||||
* @param genTable 业务表
|
||||
* @param fieldValues 字段配置值
|
||||
* @return 业务表字段列表
|
||||
*/
|
||||
private static List<GenTableColumn> getColumns(GenTable genTable, Object fieldValues) {
|
||||
List<String> fields = new ArrayList<>();
|
||||
if (fieldValues instanceof Collection<?> collection) {
|
||||
collection.stream().map(Convert::toStr).forEach(fields::add);
|
||||
} else if (ObjectUtil.isNotNull(fieldValues)) {
|
||||
fields.addAll(StringUtils.str2List(Convert.toStr(fieldValues), StringUtils.SEPARATOR, true, true));
|
||||
}
|
||||
List<GenTableColumn> columns = new ArrayList<>();
|
||||
for (String field : fields) {
|
||||
GenTableColumn column = getColumn(genTable, field);
|
||||
if (ObjectUtil.isNotNull(column)) {
|
||||
columns.add(column);
|
||||
}
|
||||
}
|
||||
return columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 Java 字面量。
|
||||
*
|
||||
* @param column 业务表字段
|
||||
* @param value 字段值
|
||||
* @return Java 字面量
|
||||
*/
|
||||
private static String getJavaLiteral(GenTableColumn column, String value) {
|
||||
if (ObjectUtil.isNull(column) || StringUtils.isBlank(value)) {
|
||||
return "null";
|
||||
}
|
||||
if (StringUtils.equals(column.getJavaType(), GenConstants.TYPE_LONG)) {
|
||||
return value + "L";
|
||||
}
|
||||
if (StringUtils.equals(column.getJavaType(), GenConstants.TYPE_FLOAT)) {
|
||||
return value + "F";
|
||||
}
|
||||
if (StringUtils.equals(column.getJavaType(), GenConstants.TYPE_DOUBLE)) {
|
||||
return value + "D";
|
||||
}
|
||||
if (StringUtils.equals(column.getJavaType(), GenConstants.TYPE_BIGDECIMAL)) {
|
||||
return "new java.math.BigDecimal(\"" + value + "\")";
|
||||
}
|
||||
if (StringUtils.equals(column.getJavaType(), GenConstants.TYPE_INTEGER)) {
|
||||
return value;
|
||||
}
|
||||
return "\"" + value + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* 转换为 TypeScript 字面量。
|
||||
*
|
||||
* @param column 业务表字段
|
||||
* @param value 字段值
|
||||
* @return TypeScript 字面量
|
||||
*/
|
||||
private static String getTsLiteral(GenTableColumn column, String value) {
|
||||
if (ObjectUtil.isNull(column) || StringUtils.isBlank(value)) {
|
||||
return "undefined";
|
||||
}
|
||||
if (StringUtils.equalsAny(column.getJavaType(), GenConstants.TYPE_LONG, GenConstants.TYPE_INTEGER,
|
||||
GenConstants.TYPE_DOUBLE, GenConstants.TYPE_FLOAT, GenConstants.TYPE_BIGDECIMAL)) {
|
||||
return value;
|
||||
}
|
||||
return "'" + value + "'";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package org.dromara.gen.util.template;
|
||||
|
||||
import cn.hutool.extra.template.Template;
|
||||
import cn.hutool.extra.template.TemplateEngine;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 基于路径命名的模板委托实现
|
||||
*
|
||||
* @author 秋辞未寒
|
||||
*/
|
||||
public class PathNamedTemplate implements Template {
|
||||
|
||||
@Getter
|
||||
private final String pathName;
|
||||
|
||||
private final Template delegate;
|
||||
|
||||
/**
|
||||
* 创建基于路径命名的模板。
|
||||
*
|
||||
* @param pathName 路径名称
|
||||
* @param delegate 委托模板
|
||||
*/
|
||||
private PathNamedTemplate(String pathName, Template delegate) {
|
||||
this.pathName = pathName;
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染模板到字符输出器。
|
||||
*
|
||||
* @param bindingMap 模板绑定数据
|
||||
* @param writer 字符输出器
|
||||
*/
|
||||
@Override
|
||||
public void render(Map<?, ?> bindingMap, Writer writer) {
|
||||
delegate.render(bindingMap, writer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染模板到输出流。
|
||||
*
|
||||
* @param bindingMap 模板绑定数据
|
||||
* @param out 输出流
|
||||
*/
|
||||
@Override
|
||||
public void render(Map<?, ?> bindingMap, OutputStream out) {
|
||||
delegate.render(bindingMap, out);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染模板到文件。
|
||||
*
|
||||
* @param bindingMap 模板绑定数据
|
||||
* @param file 输出文件
|
||||
*/
|
||||
@Override
|
||||
public void render(Map<?, ?> bindingMap, File file) {
|
||||
delegate.render(bindingMap, file);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染模板为字符串。
|
||||
*
|
||||
* @param bindingMap 模板绑定数据
|
||||
* @return 渲染结果
|
||||
*/
|
||||
@Override
|
||||
public String render(Map<?, ?> bindingMap) {
|
||||
return delegate.render(bindingMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建带路径名称的模板。
|
||||
*
|
||||
* @param pathName 路径名称
|
||||
* @param delegate 模板对象
|
||||
* @return 带路径名称的模板
|
||||
*/
|
||||
public static PathNamedTemplate form(String pathName, Template delegate) {
|
||||
return new PathNamedTemplate(pathName, delegate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板引擎和路径名称构建模板。
|
||||
*
|
||||
* @param templateEngine 模板引擎
|
||||
* @param pathName 路径名称
|
||||
* @return 带路径名称的模板
|
||||
*/
|
||||
public static PathNamedTemplate form(TemplateEngine templateEngine, String pathName) {
|
||||
return new PathNamedTemplate(pathName, templateEngine.getTemplate(pathName));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模板引擎和路径名称集合构建模板映射。
|
||||
*
|
||||
* @param templateEngine 模板引擎
|
||||
* @param pathNames 路径名称集合
|
||||
* @return 模板映射
|
||||
*/
|
||||
public static Map<String, PathNamedTemplate> form(TemplateEngine templateEngine, Set<String> pathNames) {
|
||||
Map<String, PathNamedTemplate> result = new HashMap<>();
|
||||
for (String pathName : pathNames) {
|
||||
PathNamedTemplate pathNamedTemplate = form(templateEngine, pathName);
|
||||
result.put(pathName, pathNamedTemplate);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package ${packageName}.domain.bo;
|
||||
|
||||
import ${packageName}.domain.${ClassName};
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
<#if hasBetween>
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
</#if>
|
||||
import lombok.Data;
|
||||
import jakarta.validation.constraints.*;
|
||||
<#list importList as import>
|
||||
import ${import};
|
||||
</#list>
|
||||
|
||||
/**
|
||||
* ${functionName}业务对象 ${tableName}
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
@Data
|
||||
@AutoMapper(target = ${ClassName}.class, reverseConvertGenerate = false)
|
||||
public class ${ClassName}Bo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
<#list columns as column>
|
||||
<#if column.query || (!table.isSuperColumn(column.javaField) && (column.insert || column.edit))>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
<#if column.insert && column.edit>
|
||||
<#assign Group = "AddGroup.class, EditGroup.class">
|
||||
<#elseif column.insert>
|
||||
<#assign Group = "AddGroup.class">
|
||||
<#elseif column.edit>
|
||||
<#assign Group = "EditGroup.class">
|
||||
</#if>
|
||||
<#if column.required && !table.isSuperColumn(column.javaField) && (column.insert || column.edit)>
|
||||
<#if column.javaType == 'String'>
|
||||
@NotBlank(message = "${column.columnComment}不能为空", groups = { ${Group} })
|
||||
<#else>
|
||||
@NotNull(message = "${column.columnComment}不能为空", groups = { ${Group} })
|
||||
</#if>
|
||||
</#if>
|
||||
private ${column.javaType} ${column.javaField};
|
||||
|
||||
</#if>
|
||||
</#list>
|
||||
<#if hasBetween>
|
||||
/**
|
||||
* 查询参数
|
||||
*/
|
||||
private Map<String, Object> params = new HashMap<>();
|
||||
</#if>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
package ${packageName}.controller;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
<#if enableExport>
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
</#if>
|
||||
import jakarta.validation.constraints.*;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.dromara.common.redis.annotation.RepeatSubmit;
|
||||
import org.dromara.common.log.annotation.Log;
|
||||
import org.dromara.common.web.core.BaseController;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.validate.AddGroup;
|
||||
import org.dromara.common.core.validate.EditGroup;
|
||||
import org.dromara.common.log.enums.BusinessType;
|
||||
<#if enableExport>
|
||||
import org.dromara.common.excel.utils.ExcelBuilder;
|
||||
</#if>
|
||||
import ${packageName}.domain.vo.${ClassName}Vo;
|
||||
import ${packageName}.domain.bo.${ClassName}Bo;
|
||||
import ${packageName}.service.I${ClassName}Service;
|
||||
<#if table.crud>
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
<#elseif table.tree>
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* ${functionName}
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/${moduleName}/${businessName}")
|
||||
public class ${ClassName}Controller extends BaseController {
|
||||
|
||||
private final I${ClassName}Service ${className}Service;
|
||||
|
||||
/**
|
||||
* 查询${functionName}列表
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:list")
|
||||
@GetMapping("/list")
|
||||
<#if table.crud>
|
||||
public R<PageResult<${ClassName}Vo>> list(${ClassName}Bo bo, PageQuery pageQuery) {
|
||||
return R.ok(${className}Service.queryPageList(bo, pageQuery));
|
||||
}
|
||||
<#elseif table.tree>
|
||||
public R<List<${ClassName}Vo>> list(${ClassName}Bo bo) {
|
||||
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
|
||||
return R.ok(list);
|
||||
}
|
||||
</#if>
|
||||
|
||||
<#if enableExport>
|
||||
/**
|
||||
* 导出${functionName}列表
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:export")
|
||||
@Log(title = "${functionName}", businessType = BusinessType.EXPORT)
|
||||
@PostMapping("/export")
|
||||
public void export(${ClassName}Bo bo, HttpServletResponse response) {
|
||||
List<${ClassName}Vo> list = ${className}Service.queryList(bo);
|
||||
ExcelBuilder.of(list, ${ClassName}Vo.class).sheetName("${functionName}").toResponse(response);
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 获取${functionName}详细信息
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:query")
|
||||
@GetMapping("/{${pkColumn.javaField}}")
|
||||
public R<${ClassName}Vo> getInfo(@NotNull(message = "主键不能为空")
|
||||
@PathVariable ${pkColumn.javaType} ${pkColumn.javaField}) {
|
||||
return R.ok(${className}Service.queryById(${pkColumn.javaField}));
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增${functionName}
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:add")
|
||||
@Log(title = "${functionName}", businessType = BusinessType.INSERT)
|
||||
@RepeatSubmit()
|
||||
@PostMapping()
|
||||
public R<Void> add(@Validated(AddGroup.class) @RequestBody ${ClassName}Bo bo) {
|
||||
<#if enableUnique>
|
||||
if (!${className}Service.checkUnique(bo)) {
|
||||
return R.fail("新增${functionName}失败,组合唯一字段已存在");
|
||||
}
|
||||
</#if>
|
||||
return toAjax(${className}Service.insertByBo(bo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改${functionName}
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:edit")
|
||||
@Log(title = "${functionName}", businessType = BusinessType.UPDATE)
|
||||
@RepeatSubmit()
|
||||
@PutMapping()
|
||||
public R<Void> edit(@Validated(EditGroup.class) @RequestBody ${ClassName}Bo bo) {
|
||||
<#if enableUnique>
|
||||
if (!${className}Service.checkUnique(bo)) {
|
||||
return R.fail("修改${functionName}失败,组合唯一字段已存在");
|
||||
}
|
||||
</#if>
|
||||
return toAjax(${className}Service.updateByBo(bo));
|
||||
}
|
||||
|
||||
<#if enableStatus>
|
||||
/**
|
||||
* 修改${functionName}状态
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:edit")
|
||||
@Log(title = "${functionName}", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/changeStatus")
|
||||
public R<Void> changeStatus(@RequestBody ${ClassName}Bo bo) {
|
||||
return toAjax(${className}Service.updateStatus(bo.get${pkColumn.capJavaField}(), bo.get${statusColumn.capJavaField}()));
|
||||
}
|
||||
</#if>
|
||||
|
||||
<#if enableSort>
|
||||
/**
|
||||
* 调整${functionName}排序
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:edit")
|
||||
@Log(title = "${functionName}", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/updateSort")
|
||||
public R<Void> updateSort(@RequestBody ${ClassName}Bo bo) {
|
||||
return toAjax(${className}Service.updateSort(bo.get${pkColumn.capJavaField}(), bo.get${sortColumn.capJavaField}()));
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 删除${functionName}
|
||||
*
|
||||
* @param ${pkColumn.javaField}s 主键串
|
||||
*/
|
||||
@SaCheckPermission("${permissionPrefix}:remove")
|
||||
@Log(title = "${functionName}", businessType = BusinessType.DELETE)
|
||||
@DeleteMapping("/{${pkColumn.javaField}s}")
|
||||
public R<Void> remove(@NotEmpty(message = "主键不能为空")
|
||||
@PathVariable ${pkColumn.javaType}[] ${pkColumn.javaField}s) {
|
||||
return toAjax(${className}Service.deleteWithValidByIds(List.of(${pkColumn.javaField}s), true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package ${packageName}.domain;
|
||||
|
||||
import org.dromara.common.mybatis.core.domain.BaseEntity;
|
||||
import com.baomidou.mybatisplus.annotation.*;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
<#list importList as import>
|
||||
import ${import};
|
||||
</#list>
|
||||
|
||||
import java.io.Serial;
|
||||
|
||||
/**
|
||||
* ${functionName}对象 ${tableName}
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@TableName("${tableName}")
|
||||
public class ${ClassName} extends BaseEntity {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
<#list columns as column>
|
||||
<#if !table.isSuperColumn(column.javaField)>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
<#if column.javaField=='delFlag'>
|
||||
@TableLogic
|
||||
</#if>
|
||||
<#if column.javaField=='version'>
|
||||
@Version
|
||||
</#if>
|
||||
<#if column.pk>
|
||||
@TableId(value = "${column.columnName}")
|
||||
<#elseif column.needTableField>
|
||||
@TableField(value = "${column.columnName}")
|
||||
</#if>
|
||||
private ${column.javaType} ${column.javaField};
|
||||
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package ${packageName}.mapper;
|
||||
|
||||
import ${packageName}.domain.${ClassName};
|
||||
import ${packageName}.domain.vo.${ClassName}Vo;
|
||||
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
|
||||
|
||||
/**
|
||||
* ${functionName}Mapper接口
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
public interface ${ClassName}Mapper extends BaseMapperPlus<${ClassName}, ${ClassName}Vo> {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
package ${packageName}.service;
|
||||
|
||||
import ${packageName}.domain.vo.${ClassName}Vo;
|
||||
import ${packageName}.domain.bo.${ClassName}Bo;
|
||||
<#if table.crud>
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
</#if>
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* ${functionName}Service接口
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
public interface I${ClassName}Service {
|
||||
|
||||
/**
|
||||
* 查询${functionName}
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
* @return ${functionName}
|
||||
*/
|
||||
${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField});
|
||||
|
||||
<#if table.crud>
|
||||
/**
|
||||
* 分页查询${functionName}列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return ${functionName}分页列表
|
||||
*/
|
||||
PageResult<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery);
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 查询符合条件的${functionName}列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return ${functionName}列表
|
||||
*/
|
||||
List<${ClassName}Vo> queryList(${ClassName}Bo bo);
|
||||
|
||||
<#if enableUnique>
|
||||
/**
|
||||
* 校验${functionName}是否满足组合唯一约束
|
||||
*
|
||||
* @param bo ${functionName}
|
||||
* @return 是否唯一
|
||||
*/
|
||||
boolean checkUnique(${ClassName}Bo bo);
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 新增${functionName}
|
||||
*
|
||||
* @param bo ${functionName}
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
Boolean insertByBo(${ClassName}Bo bo);
|
||||
|
||||
/**
|
||||
* 修改${functionName}
|
||||
*
|
||||
* @param bo ${functionName}
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateByBo(${ClassName}Bo bo);
|
||||
|
||||
<#if enableStatus>
|
||||
/**
|
||||
* 修改${functionName}状态
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
* @param status 状态值
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateStatus(${pkColumn.javaType} ${pkColumn.javaField}, ${statusColumn.javaType} status);
|
||||
</#if>
|
||||
|
||||
<#if enableSort>
|
||||
/**
|
||||
* 调整${functionName}排序
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
* @param sortValue 排序值
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
Boolean updateSort(${pkColumn.javaType} ${pkColumn.javaField}, ${sortColumn.javaType} sortValue);
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 校验并批量删除${functionName}信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
package ${packageName}.service.impl;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import org.dromara.common.core.utils.MapstructUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
<#if table.crud>
|
||||
import org.dromara.common.core.domain.PageResult;
|
||||
import org.dromara.common.mybatis.core.page.PageQuery;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
</#if>
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
<#if enableUnique>
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
</#if>
|
||||
import org.dromara.common.mybatis.core.query.QueryBuilder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import ${packageName}.domain.bo.${ClassName}Bo;
|
||||
import ${packageName}.domain.vo.${ClassName}Vo;
|
||||
import ${packageName}.domain.${ClassName};
|
||||
import ${packageName}.mapper.${ClassName}Mapper;
|
||||
import ${packageName}.service.I${ClassName}Service;
|
||||
<#if table.tree>
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
</#if>
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* ${functionName}Service业务层处理
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@Service
|
||||
public class ${ClassName}ServiceImpl implements I${ClassName}Service {
|
||||
|
||||
private final ${ClassName}Mapper ${className}Mapper;
|
||||
|
||||
/**
|
||||
* 查询${functionName}
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
* @return ${functionName}
|
||||
*/
|
||||
@Override
|
||||
public ${ClassName}Vo queryById(${pkColumn.javaType} ${pkColumn.javaField}) {
|
||||
return ${className}Mapper.selectVoById(${pkColumn.javaField});
|
||||
}
|
||||
|
||||
<#if table.crud>
|
||||
/**
|
||||
* 分页查询${functionName}列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @param pageQuery 分页参数
|
||||
* @return ${functionName}分页列表
|
||||
*/
|
||||
@Override
|
||||
public PageResult<${ClassName}Vo> queryPageList(${ClassName}Bo bo, PageQuery pageQuery) {
|
||||
LambdaQueryWrapper<${ClassName}> lqw = buildQueryWrapper(bo);
|
||||
Page<${ClassName}Vo> result = ${className}Mapper.selectVoPage(pageQuery.build(), lqw);
|
||||
return PageResult.build(result.getRecords(), result.getTotal());
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 查询符合条件的${functionName}列表
|
||||
*
|
||||
* @param bo 查询条件
|
||||
* @return ${functionName}列表
|
||||
*/
|
||||
@Override
|
||||
public List<${ClassName}Vo> queryList(${ClassName}Bo bo) {
|
||||
LambdaQueryWrapper<${ClassName}> lqw = buildQueryWrapper(bo);
|
||||
return ${className}Mapper.selectVoList(lqw);
|
||||
}
|
||||
|
||||
<#if enableUnique>
|
||||
/**
|
||||
* 校验${functionName}是否满足组合唯一约束
|
||||
*
|
||||
* @param bo ${functionName}
|
||||
* @return 是否唯一
|
||||
*/
|
||||
@Override
|
||||
public boolean checkUnique(${ClassName}Bo bo) {
|
||||
boolean hasUniqueValue = true;
|
||||
<#list uniqueColumns as column>
|
||||
<#if column.javaType == 'String'>
|
||||
hasUniqueValue = hasUniqueValue && StringUtils.isNotBlank(bo.get${column.capJavaField}());
|
||||
<#else>
|
||||
hasUniqueValue = hasUniqueValue && bo.get${column.capJavaField}() != null;
|
||||
</#if>
|
||||
</#list>
|
||||
if (!hasUniqueValue) {
|
||||
return true;
|
||||
}
|
||||
LambdaQueryWrapper<${ClassName}> lqw = Wrappers.lambdaQuery();
|
||||
<#list uniqueColumns as column>
|
||||
lqw.eq(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}());
|
||||
</#list>
|
||||
lqw.ne(bo.get${pkColumn.capJavaField}() != null, ${ClassName}::get${pkColumn.capJavaField}, bo.get${pkColumn.capJavaField}());
|
||||
return !${className}Mapper.exists(lqw);
|
||||
}
|
||||
</#if>
|
||||
|
||||
private LambdaQueryWrapper<${ClassName}> buildQueryWrapper(${ClassName}Bo bo) {
|
||||
<#if hasBetween>
|
||||
Map<String, Object> params = bo.getParams();
|
||||
</#if>
|
||||
return QueryBuilder.lambda(${ClassName}.class)
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#assign queryType = column.queryType>
|
||||
<#assign javaType = column.javaType>
|
||||
<#assign AttrName = column.capJavaField>
|
||||
<#assign mpMethod = column.queryType?lower_case>
|
||||
<#if queryType != 'BETWEEN'>
|
||||
<#if javaType == 'String'>
|
||||
<#assign condition = 'StringUtils.isNotBlank(bo.get'+AttrName+'())'>
|
||||
<#if queryType == 'LIKE'>
|
||||
.likeIfText(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#elseif queryType == 'EQ'>
|
||||
.eqIfText(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#elseif queryType == 'NE'>
|
||||
.neIfText(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#else>
|
||||
.${mpMethod}(${condition}, ${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
</#if>
|
||||
<#else>
|
||||
<#assign condition = 'bo.get'+AttrName+'() != null'>
|
||||
<#if queryType == 'EQ'>
|
||||
.eqIfPresent(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#elseif queryType == 'NE'>
|
||||
.neIfPresent(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#elseif queryType == 'GT'>
|
||||
.gtIfPresent(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#elseif queryType == 'LT'>
|
||||
.ltIfPresent(${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
<#else>
|
||||
.${mpMethod}(${condition}, ${ClassName}::get${column.capJavaField}, bo.get${column.capJavaField}())
|
||||
</#if>
|
||||
</#if>
|
||||
<#else>
|
||||
.betweenParams(${ClassName}::get${column.capJavaField}, params, "begin${column.capJavaField}", "end${column.capJavaField}")
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<#if table.tree && "" != treeAncestorsField>
|
||||
.orderByAsc(${ClassName}::get${treeAncestorsCap})
|
||||
</#if>
|
||||
<#if table.tree && "" != treeParentCode>
|
||||
.orderByAsc(${ClassName}::get${treeParentCap})
|
||||
</#if>
|
||||
<#if table.tree && "" != treeOrderField>
|
||||
.orderByAsc(${ClassName}::get${treeOrderCap})
|
||||
<#elseif enableSort>
|
||||
.orderByAsc(${ClassName}::get${sortColumn.capJavaField})
|
||||
</#if>
|
||||
.orderByAsc(${ClassName}::get${pkColumn.capJavaField})
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增${functionName}
|
||||
*
|
||||
* @param bo ${functionName}
|
||||
* @return 是否新增成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean insertByBo(${ClassName}Bo bo) {
|
||||
${ClassName} add = MapstructUtils.convert(bo, ${ClassName}.class);
|
||||
<#if table.tree>
|
||||
fillTreeMetaBeforeSave(add, false);
|
||||
</#if>
|
||||
validEntityBeforeSave(add);
|
||||
boolean flag = ${className}Mapper.insert(add) > 0;
|
||||
if (flag) {
|
||||
bo.set${pkColumn.capJavaField}(add.get${pkColumn.capJavaField}());
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改${functionName}
|
||||
*
|
||||
* @param bo ${functionName}
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateByBo(${ClassName}Bo bo) {
|
||||
${ClassName} update = MapstructUtils.convert(bo, ${ClassName}.class);
|
||||
<#if table.tree>
|
||||
fillTreeMetaBeforeSave(update, true);
|
||||
</#if>
|
||||
validEntityBeforeSave(update);
|
||||
return ${className}Mapper.updateById(update) > 0;
|
||||
}
|
||||
|
||||
<#if enableStatus>
|
||||
/**
|
||||
* 修改${functionName}状态
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
* @param status 状态值
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateStatus(${pkColumn.javaType} ${pkColumn.javaField}, ${statusColumn.javaType} status) {
|
||||
return ${className}Mapper.lambda()
|
||||
.set(${ClassName}::get${statusColumn.capJavaField}, status)
|
||||
.eq(${ClassName}::get${pkColumn.capJavaField}, ${pkColumn.javaField})
|
||||
.update();
|
||||
}
|
||||
</#if>
|
||||
|
||||
<#if enableSort>
|
||||
/**
|
||||
* 调整${functionName}排序
|
||||
*
|
||||
* @param ${pkColumn.javaField} 主键
|
||||
* @param sortValue 排序值
|
||||
* @return 是否修改成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean updateSort(${pkColumn.javaType} ${pkColumn.javaField}, ${sortColumn.javaType} sortValue) {
|
||||
return ${className}Mapper.lambda()
|
||||
.set(${ClassName}::get${sortColumn.capJavaField}, sortValue)
|
||||
.eq(${ClassName}::get${pkColumn.capJavaField}, ${pkColumn.javaField})
|
||||
.update();
|
||||
}
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 保存前的数据校验
|
||||
*/
|
||||
private void validEntityBeforeSave(${ClassName} entity) {
|
||||
// 可在此扩展通用业务校验
|
||||
}
|
||||
|
||||
<#if table.tree>
|
||||
private void fillTreeMetaBeforeSave(${ClassName} entity, boolean updateMode) {
|
||||
<#if "" != treeParentCode>
|
||||
if (entity.get${treeParentCap}() == null) {
|
||||
entity.set${treeParentCap}(${treeRootValueJavaLiteral});
|
||||
}
|
||||
if (ObjectUtil.equal(entity.get${pkColumn.capJavaField}(), entity.get${treeParentCap}())) {
|
||||
throw new ServiceException("${functionName}父节点不能选择自身");
|
||||
}
|
||||
<#if "" != treeAncestorsField>
|
||||
${ClassName} parent = null;
|
||||
if (!ObjectUtil.equal(entity.get${treeParentCap}(), ${treeRootValueJavaLiteral})) {
|
||||
parent = ${className}Mapper.selectById(entity.get${treeParentCap}());
|
||||
if (ObjectUtil.isNull(parent)) {
|
||||
throw new ServiceException("${functionName}父节点不存在");
|
||||
}
|
||||
}
|
||||
if (updateMode && entity.get${pkColumn.capJavaField}() != null && ObjectUtil.isNotNull(parent)
|
||||
&& containsAncestor(parent.get${treeAncestorsCap}(), entity.get${pkColumn.capJavaField}())) {
|
||||
throw new ServiceException("不能选择当前节点或其子节点作为父节点");
|
||||
}
|
||||
String newAncestors = resolveAncestors(entity.get${treeParentCap}(), parent);
|
||||
if (updateMode && entity.get${pkColumn.capJavaField}() != null) {
|
||||
${ClassName} oldEntity = ${className}Mapper.selectById(entity.get${pkColumn.capJavaField}());
|
||||
if (ObjectUtil.isNull(oldEntity)) {
|
||||
throw new ServiceException("${functionName}不存在,无法修改");
|
||||
}
|
||||
String oldAncestors = oldEntity.get${treeAncestorsCap}();
|
||||
entity.set${treeAncestorsCap}(newAncestors);
|
||||
if (!StringUtils.equals(oldAncestors, newAncestors)) {
|
||||
updateChildrenAncestors(entity.get${pkColumn.capJavaField}(), newAncestors, oldAncestors);
|
||||
}
|
||||
} else {
|
||||
entity.set${treeAncestorsCap}(newAncestors);
|
||||
}
|
||||
</#if>
|
||||
</#if>
|
||||
}
|
||||
<#if "" != treeAncestorsField>
|
||||
|
||||
private String resolveAncestors(${treeParentColumn.javaType} parentId, ${ClassName} parent) {
|
||||
if (ObjectUtil.equal(parentId, ${treeRootValueJavaLiteral})) {
|
||||
return "${treeRootValue}";
|
||||
}
|
||||
String parentAncestors = parent.get${treeAncestorsCap}();
|
||||
if (StringUtils.isBlank(parentAncestors)) {
|
||||
return String.valueOf(parentId);
|
||||
}
|
||||
return parentAncestors + StringUtils.SEPARATOR + parentId;
|
||||
}
|
||||
|
||||
private void updateChildrenAncestors(${pkColumn.javaType} currentId, String newAncestors, String oldAncestors) {
|
||||
List<${ClassName}> children = ${className}Mapper.lambda()
|
||||
.select(${ClassName}::get${pkColumn.capJavaField}, ${ClassName}::get${treeAncestorsCap})
|
||||
.findInSet(currentId, ${ClassName}::get${treeAncestorsCap})
|
||||
.list();
|
||||
List<${ClassName}> updateList = new ArrayList<>();
|
||||
for (${ClassName} child : children) {
|
||||
String ancestors = child.get${treeAncestorsCap}();
|
||||
if (StringUtils.isBlank(ancestors)) {
|
||||
continue;
|
||||
}
|
||||
${ClassName} update = new ${ClassName}();
|
||||
update.set${pkColumn.capJavaField}(child.get${pkColumn.capJavaField}());
|
||||
update.set${treeAncestorsCap}(StringUtils.replaceOnce(ancestors, oldAncestors, newAncestors));
|
||||
updateList.add(update);
|
||||
}
|
||||
if (!updateList.isEmpty()) {
|
||||
${className}Mapper.updateBatchById(updateList);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsAncestor(String ancestors, ${pkColumn.javaType} nodeId) {
|
||||
for (String item : StringUtils.splitList(ancestors)) {
|
||||
if (StringUtils.equals(item, String.valueOf(nodeId))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</#if>
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 校验并批量删除${functionName}信息
|
||||
*
|
||||
* @param ids 待删除的主键集合
|
||||
* @param isValid 是否进行有效性校验
|
||||
* @return 是否删除成功
|
||||
*/
|
||||
@Override
|
||||
public Boolean deleteWithValidByIds(Collection<${pkColumn.javaType}> ids, Boolean isValid) {
|
||||
if (isValid) {
|
||||
// 可在此扩展删除前业务校验
|
||||
}
|
||||
return ${className}Mapper.deleteByIds(ids) > 0;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package ${packageName}.domain.vo;
|
||||
|
||||
<#list importList as import>
|
||||
import ${import};
|
||||
</#list>
|
||||
import ${packageName}.domain.${ClassName};
|
||||
import org.apache.fesod.sheet.annotation.ExcelIgnoreUnannotated;
|
||||
import org.apache.fesod.sheet.annotation.ExcelProperty;
|
||||
import org.dromara.common.excel.annotation.ExcelDictFormat;
|
||||
import org.dromara.common.excel.convert.ExcelDictConvert;
|
||||
import org.dromara.common.translation.annotation.Translation;
|
||||
import org.dromara.common.translation.constant.TransConstant;
|
||||
import io.github.linpeilie.annotations.AutoMapper;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* ${functionName}视图对象 ${tableName}
|
||||
*
|
||||
* @author ${author}
|
||||
* @date ${datetime}
|
||||
*/
|
||||
@Data
|
||||
@ExcelIgnoreUnannotated
|
||||
@AutoMapper(target = ${ClassName}.class)
|
||||
public class ${ClassName}Vo implements Serializable {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
<#list columns as column>
|
||||
<#if column.list>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
<#assign parentheseIndex = column.columnComment?index_of("(")>
|
||||
<#if column.dictType?has_content>
|
||||
@ExcelProperty(value = "${column.columnLabel}", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(dictType = "${column.dictType}")
|
||||
<#elseif parentheseIndex != -1>
|
||||
@ExcelProperty(value = "${column.columnLabel}", converter = ExcelDictConvert.class)
|
||||
@ExcelDictFormat(readConverterExp = "${column.readConverterExp()}")
|
||||
<#else>
|
||||
@ExcelProperty(value = "${column.columnLabel}")
|
||||
</#if>
|
||||
private ${column.javaType} ${column.javaField};
|
||||
|
||||
<#if column.htmlType == "imageUpload">
|
||||
/**
|
||||
* ${column.columnComment}Url
|
||||
*/
|
||||
@Translation(type = TransConstant.OSS_ID_TO_URL, mapper = "${column.javaField}")
|
||||
private String ${column.javaField}Url;
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import type { <#if !table.tree>PageResult, </#if> R } from '@/api/types';
|
||||
import request from '@/api/request';
|
||||
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from './types';
|
||||
|
||||
/**
|
||||
* 查询${functionName}列表
|
||||
*/
|
||||
export function list${BusinessName}(query?: ${BusinessName}Query) {
|
||||
return request<R<<#if table.tree>${BusinessName}VO[]<#else> PageResult<${BusinessName}VO></#if>>>({
|
||||
url: '/${moduleName}/${businessName}/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询${functionName}详细
|
||||
*/
|
||||
export function get${BusinessName}(${pkColumn.javaField}: string | number) {
|
||||
return request<R<${BusinessName}VO>>({
|
||||
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增${functionName}
|
||||
*/
|
||||
export function add${BusinessName}(data: ${BusinessName}Form) {
|
||||
return request<R>({
|
||||
url: '/${moduleName}/${businessName}',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改${functionName}
|
||||
*/
|
||||
export function update${BusinessName}(data: ${BusinessName}Form) {
|
||||
return request<R>({
|
||||
url: '/${moduleName}/${businessName}',
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
<#if enableStatus>
|
||||
/**
|
||||
* 修改${functionName}状态
|
||||
*/
|
||||
export function change${BusinessName}Status(
|
||||
${pkColumn.javaField}: string | number,
|
||||
${statusField}: <#if statusColumn.javaType == 'Boolean'>boolean<#elseif statusColumn.javaType == 'Integer' || statusColumn.javaType == 'Long'>number<#else> string</#if>
|
||||
) {
|
||||
return request<R>({
|
||||
url: '/${moduleName}/${businessName}/changeStatus',
|
||||
method: 'put',
|
||||
data: {
|
||||
${pkColumn.javaField},
|
||||
${statusField}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</#if>
|
||||
<#if enableSort>
|
||||
/**
|
||||
* 调整${functionName}排序
|
||||
*/
|
||||
export function update${BusinessName}Sort(
|
||||
${pkColumn.javaField}: string | number,
|
||||
${sortField}: <#if sortColumn.javaType == 'LocalDateTime' || sortColumn.javaType == 'String'>string<#else> number</#if>
|
||||
) {
|
||||
return request<R>({
|
||||
url: '/${moduleName}/${businessName}/updateSort',
|
||||
method: 'put',
|
||||
data: {
|
||||
${pkColumn.javaField},
|
||||
${sortField}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
</#if>
|
||||
/**
|
||||
* 删除${functionName}
|
||||
*/
|
||||
export function del${BusinessName}(${pkColumn.javaField}: string | number | Array<string | number>) {
|
||||
return request<R>({
|
||||
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,424 @@
|
||||
import { DeleteOutlined<#if enableExport>, DownloadOutlined</#if>, EditOutlined, PlusOutlined, SortAscendingOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
ModalForm,
|
||||
PageContainer,
|
||||
<#if needCheckbox>
|
||||
ProFormCheckbox,
|
||||
</#if>
|
||||
<#if needDateField>
|
||||
ProFormDateTimePicker,
|
||||
</#if>
|
||||
<#if needDigit>
|
||||
ProFormDigit,
|
||||
</#if>
|
||||
<#if needSelect>
|
||||
ProFormSelect,
|
||||
</#if>
|
||||
<#if needTextArea>
|
||||
ProFormTextArea,
|
||||
</#if>
|
||||
ProFormText,
|
||||
ProFormTreeSelect,
|
||||
ProTable,
|
||||
type ActionType,
|
||||
type ProColumns
|
||||
} from '@ant-design/pro-components';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { Button, Form, message<#if enableStatus || needSwitchField>, Switch</#if><#if enableSort>, InputNumber</#if> } from 'antd';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
|
||||
import {
|
||||
add${BusinessName},
|
||||
<#if enableStatus>
|
||||
change${BusinessName}Status,
|
||||
</#if>
|
||||
del${BusinessName},
|
||||
get${BusinessName},
|
||||
list${BusinessName},
|
||||
<#if enableSort>
|
||||
update${BusinessName}Sort,
|
||||
</#if>
|
||||
update${BusinessName}
|
||||
} from '@/api/${moduleName}/${businessName}';
|
||||
<#if needDict>
|
||||
import DictTag from '@/components/common/DictTag';
|
||||
</#if>
|
||||
<#if needFileUpload>
|
||||
import FileUpload from '@/components/common/FileUpload';
|
||||
</#if>
|
||||
<#if needImagePreview>
|
||||
import ImagePreview from '@/components/common/ImagePreview';
|
||||
</#if>
|
||||
<#if needImageUpload>
|
||||
import ImageUpload from '@/components/common/ImageUpload';
|
||||
</#if>
|
||||
<#if needEditor>
|
||||
import RichTextEditor from '@/components/common/RichTextEditor';
|
||||
</#if>
|
||||
import RowActions from '@/components/common/RowActions';
|
||||
<#if needDict>
|
||||
import { useDict } from '@/hooks/useDict';
|
||||
</#if>
|
||||
<#if enableExport>
|
||||
import { useTableExport } from '@/hooks/useTableExport';
|
||||
</#if>
|
||||
import { useTableScroll } from '@/hooks/useTableScroll';
|
||||
import { useTreeTableExpand } from '@/hooks/useTreeTableExpand';
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
<#if needDict>
|
||||
import { dictOptions } from '@/utils/dict';
|
||||
</#if>
|
||||
<#if enableStatus>
|
||||
import { confirmAction } from '@/utils/modal';
|
||||
</#if>
|
||||
import { hasPermi } from '@/utils/permission';
|
||||
import { filterTree<#if needDateField>, formatDateTimeFields</#if>, handleTree<#if needDateField>, toDayjsFields</#if> } from '@/utils/ruoyi';
|
||||
|
||||
const default${BusinessName}Form: ${BusinessName}Form = {
|
||||
${treeParentCode}: ${treeRootValueTsLiteral},
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && !column.pk && column.htmlType == "checkbox">
|
||||
${column.javaField}: [],
|
||||
</#if>
|
||||
</#list>
|
||||
};
|
||||
|
||||
interface ${BusinessName}SelectNode {
|
||||
title: string;
|
||||
value: string | number;
|
||||
children?: ${BusinessName}SelectNode[];
|
||||
}
|
||||
|
||||
function toTreeSelectData(nodes: ${BusinessName}VO[]): ${BusinessName}SelectNode[] {
|
||||
return nodes.map(node => ({
|
||||
title: String(node.${treeName} || ''),
|
||||
value: node.${treeCode},
|
||||
children: node.children ? toTreeSelectData(node.children) : undefined
|
||||
}));
|
||||
}
|
||||
|
||||
<#if enableStatus>
|
||||
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
|
||||
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
|
||||
|
||||
</#if>
|
||||
export default function ${BusinessName}Page() {
|
||||
const actionRef = useRef<ActionType | undefined>(undefined);
|
||||
const { tableScroll } = useTableScroll(1000);
|
||||
const [form] = Form.useForm<${BusinessName}Form>();
|
||||
const userInfo = useUserStore(state => state.userInfo);
|
||||
<#if needDict>
|
||||
const dicts = useDict(${dicts});
|
||||
</#if>
|
||||
const [treeOptions, setTreeOptions] = useState<${BusinessName}VO[]>([]);
|
||||
const [tableRows, setTableRows] = useState<${BusinessName}VO[]>([]);
|
||||
const { expandAll, expandedRowKeys, onExpandedRowsChange, syncExpandedRows, toggleExpandAll } =
|
||||
useTreeTableExpand<${BusinessName}VO>(row => row.${treeCode});
|
||||
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
|
||||
const [modalTitle, setModalTitle] = useState('');
|
||||
<#if enableExport>
|
||||
const { updateExportParams, exportFile } = useTableExport();
|
||||
</#if>
|
||||
|
||||
const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']);
|
||||
const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']);
|
||||
const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']);
|
||||
<#if enableExport>
|
||||
const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']);
|
||||
</#if>
|
||||
const treeSelectData = useMemo(
|
||||
() => [{ title: '顶级节点', value: ${treeRootValueTsLiteral}, children: toTreeSelectData(treeOptions) }],
|
||||
[treeOptions]
|
||||
);
|
||||
|
||||
const loadTreeOptions = async (excludeId?: string | number) => {
|
||||
const res = await list${BusinessName}();
|
||||
const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}');
|
||||
setTreeOptions(excludeId ? filterTree(rows, node => node.${treeCode} !== excludeId) : rows);
|
||||
};
|
||||
|
||||
const openAdd = async (row?: ${BusinessName}VO) => {
|
||||
await loadTreeOptions();
|
||||
form.resetFields();
|
||||
form.setFieldsValue({ ...default${BusinessName}Form, ${treeParentCode}: row?.${treeCode} || treeRootValueTsLiteral });
|
||||
setModalTitle('添加${functionName}');
|
||||
openModal();
|
||||
};
|
||||
|
||||
const openEdit = async (row: ${BusinessName}VO) => {
|
||||
await loadTreeOptions(row.${treeCode});
|
||||
const res = await get${BusinessName}(row.${pkColumn.javaField});
|
||||
const data = <#if needDateField>toDayjsFields({ ...res.data }, [
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "datetime">
|
||||
'${column.javaField}',
|
||||
</#if>
|
||||
</#list>
|
||||
])<#else>{ ...res.data }</#if>;
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
|
||||
if (typeof data.${column.javaField} === 'string') {
|
||||
data.${column.javaField} = data.${column.javaField}.split(',');
|
||||
}
|
||||
</#if>
|
||||
</#list>
|
||||
form.resetFields();
|
||||
form.setFieldsValue(data);
|
||||
setModalTitle('修改${functionName}');
|
||||
openModal();
|
||||
};
|
||||
|
||||
const submitForm = async (values: ${BusinessName}Form) => {
|
||||
const submitValues = <#if needDateField>formatDateTimeFields({ ...values }, [
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "datetime">
|
||||
'${column.javaField}',
|
||||
</#if>
|
||||
</#list>
|
||||
])<#else>{ ...values }</#if>;
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
|
||||
if (Array.isArray(submitValues.${column.javaField})) {
|
||||
submitValues.${column.javaField} = submitValues.${column.javaField}.join(',');
|
||||
}
|
||||
</#if>
|
||||
</#list>
|
||||
submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues);
|
||||
message.success('操作成功');
|
||||
form.resetFields();
|
||||
actionRef.current?.reload();
|
||||
return true;
|
||||
};
|
||||
|
||||
const remove = async (row: ${BusinessName}VO) => {
|
||||
await del${BusinessName}(row.${pkColumn.javaField});
|
||||
message.success('删除成功');
|
||||
actionRef.current?.reload();
|
||||
};
|
||||
|
||||
<#if enableStatus>
|
||||
const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => {
|
||||
const previousStatus = row.${statusField};
|
||||
const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue;
|
||||
const text = checked ? '启用' : '停用';
|
||||
try {
|
||||
await confirmAction(`确认要"${r'${text}'}"吗?`);
|
||||
await change${BusinessName}Status(row.${pkColumn.javaField}, status);
|
||||
message.success(`${r'${text}'}成功`);
|
||||
actionRef.current?.reload();
|
||||
} catch {
|
||||
row.${statusField} = previousStatus;
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
};
|
||||
|
||||
</#if>
|
||||
<#if enableSort>
|
||||
const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => {
|
||||
await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0);
|
||||
message.success('排序更新成功');
|
||||
actionRef.current?.reload();
|
||||
};
|
||||
|
||||
</#if>
|
||||
const columns: ProColumns<${BusinessName}VO>[] = [
|
||||
<#list columns as column>
|
||||
<#if column.list && !column.pk>
|
||||
<#if enableStatus && statusField == column.javaField>
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
valueType: 'select',
|
||||
width: 100,
|
||||
<#if column.dictType?has_content>
|
||||
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
|
||||
</#if>
|
||||
render: (_, row) => (
|
||||
<Switch
|
||||
checked={row.${column.javaField} === ${statusField}ActiveValue}
|
||||
disabled={!canEdit}
|
||||
onChange={checked => handleStatusChange(row, checked)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
<#elseif enableSort && sortField == column.javaField>
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
search: false,
|
||||
width: 130,
|
||||
render: (_, row) => <InputNumber min={0} value={row.${column.javaField} as number} onChange={value => handleSortChange(row, value)} />
|
||||
},
|
||||
<#elseif column.htmlType == "datetime">
|
||||
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}', valueType: 'dateTime', search: <#if column.query>true<#else> false</#if>, width: 170 },
|
||||
<#elseif column.htmlType == "imageUpload">
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}Url',
|
||||
search: false,
|
||||
width: 100,
|
||||
render: (_, row) => <ImagePreview src={row.${column.javaField}Url || String(row.${column.javaField} || '')} width={50} height={50} />
|
||||
},
|
||||
<#elseif column.dictColumn>
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
valueType: 'select',
|
||||
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
|
||||
render: (_, row) => <DictTag options={dicts.${column.dictType}} value={row.${column.javaField}} />
|
||||
},
|
||||
<#elseif column.htmlType == "switch">
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
valueType: 'select',
|
||||
width: 100,
|
||||
render: (_, row) => <Switch checked={row.${column.javaField} === ${column.switchActiveValue}} disabled />
|
||||
},
|
||||
<#else>
|
||||
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}'<#if !column.query>, search: false</#if> },
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 130,
|
||||
fixed: 'right',
|
||||
render: (_, row) => (
|
||||
<RowActions
|
||||
actions={[
|
||||
canEdit && { key: 'edit', label: '修改', icon: <EditOutlined />, onClick: () => openEdit(row) },
|
||||
canAdd && { key: 'add', label: '新增', icon: <PlusOutlined />, onClick: () => openAdd(row) },
|
||||
canRemove && {
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: <DeleteOutlined />,
|
||||
danger: true,
|
||||
confirm: `是否确认删除${functionName}编号为"${r'${row.'}${pkColumn.javaField}${r'}'}"的数据项?`,
|
||||
onClick: () => remove(row)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="${functionName}">
|
||||
<ProTable<${BusinessName}VO, ${BusinessName}Query>
|
||||
actionRef={actionRef}
|
||||
rowKey="${treeCode}"
|
||||
columns={columns}
|
||||
scroll={tableScroll}
|
||||
pagination={false}
|
||||
search={{ labelWidth: 90 }}
|
||||
expandable={{
|
||||
expandedRowKeys,
|
||||
onExpandedRowsChange
|
||||
}}
|
||||
request={async params => {
|
||||
const res = await list${BusinessName}(params);
|
||||
const rows = handleTree<${BusinessName}VO>(res.data || [], '${treeCode}', '${treeParentCode}');
|
||||
setTableRows(rows);
|
||||
syncExpandedRows(rows, expandAll);
|
||||
<#if enableExport>
|
||||
updateExportParams(params);
|
||||
</#if>
|
||||
return { data: rows, total: rows.length, success: true };
|
||||
}}
|
||||
toolbar={{ title: '${functionName}列表' }}
|
||||
toolBarRender={() => [
|
||||
canAdd && (
|
||||
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={() => openAdd()}>
|
||||
新增
|
||||
</Button>
|
||||
),
|
||||
<Button
|
||||
key="expand"
|
||||
icon={<SortAscendingOutlined />}
|
||||
onClick={() => toggleExpandAll(tableRows)}
|
||||
>
|
||||
展开/折叠
|
||||
</Button><#if enableExport>,
|
||||
canExport && (
|
||||
<Button
|
||||
key="export"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => exportFile('/${moduleName}/${businessName}/export', () => `${businessName}_${r'${Date.now()}'}.xlsx`)}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
)</#if>
|
||||
]}
|
||||
/>
|
||||
|
||||
<ModalForm<${BusinessName}Form>
|
||||
title={modalTitle}
|
||||
open={modalOpen}
|
||||
width={560}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={default${BusinessName}Form}
|
||||
modalProps={{ destroyOnHidden: true, onCancel: closeModal }}
|
||||
onOpenChange={open => !open && closeModal()}
|
||||
onFinish={submitForm}
|
||||
>
|
||||
<ProFormText name="${pkColumn.javaField}" hidden />
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && !column.pk>
|
||||
<#if column.javaField == treeParentCode>
|
||||
<ProFormTreeSelect
|
||||
name="${column.javaField}"
|
||||
label="${column.columnLabel}"
|
||||
<#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>
|
||||
fieldProps={{
|
||||
allowClear: true,
|
||||
treeDefaultExpandAll: true,
|
||||
treeData: treeSelectData,
|
||||
placeholder: '请选择${column.columnLabel}'
|
||||
}}
|
||||
/>
|
||||
<#elseif column.htmlType == "input">
|
||||
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "textarea">
|
||||
<ProFormTextArea name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "inputNumber">
|
||||
<ProFormDigit name="${column.javaField}" label="${column.columnLabel}" min={0} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif (column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch") && column.dictType?has_content>
|
||||
<ProFormSelect name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
|
||||
<ProFormCheckbox.Group name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "switch">
|
||||
<Form.Item
|
||||
name="${column.javaField}"
|
||||
label="${column.columnLabel}"
|
||||
valuePropName="checked"
|
||||
getValueProps={value => ({ checked: value === ${column.switchActiveValue} })}
|
||||
normalize={checked => (checked ? ${column.switchActiveValue} : ${column.switchInactiveValue})}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<#elseif column.htmlType == "datetime">
|
||||
<ProFormDateTimePicker name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "imageUpload">
|
||||
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
|
||||
<ImageUpload />
|
||||
</Form.Item>
|
||||
<#elseif column.htmlType == "fileUpload">
|
||||
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
|
||||
<FileUpload />
|
||||
</Form.Item>
|
||||
<#elseif column.htmlType == "editor">
|
||||
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
|
||||
<RichTextEditor />
|
||||
</Form.Item>
|
||||
<#else>
|
||||
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
import { DeleteOutlined<#if enableExport>, DownloadOutlined</#if>, EditOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
ModalForm,
|
||||
PageContainer,
|
||||
<#if needCheckbox>
|
||||
ProFormCheckbox,
|
||||
</#if>
|
||||
<#if needDateField>
|
||||
ProFormDateTimePicker,
|
||||
</#if>
|
||||
<#if needDigit>
|
||||
ProFormDigit,
|
||||
</#if>
|
||||
<#if needSelect>
|
||||
ProFormSelect,
|
||||
</#if>
|
||||
<#if needTextArea>
|
||||
ProFormTextArea,
|
||||
</#if>
|
||||
ProFormText,
|
||||
ProTable,
|
||||
type ActionType,
|
||||
type ProColumns
|
||||
} from '@ant-design/pro-components';
|
||||
import { useBoolean } from 'ahooks';
|
||||
import { Button, Form, message, Popconfirm<#if enableStatus || needSwitchField>, Switch</#if><#if enableSort>, InputNumber</#if> } from 'antd';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
|
||||
import {
|
||||
add${BusinessName},
|
||||
<#if enableStatus>
|
||||
change${BusinessName}Status,
|
||||
</#if>
|
||||
del${BusinessName},
|
||||
get${BusinessName},
|
||||
list${BusinessName},
|
||||
<#if enableSort>
|
||||
update${BusinessName}Sort,
|
||||
</#if>
|
||||
update${BusinessName}
|
||||
} from '@/api/${moduleName}/${businessName}';
|
||||
<#if needDict>
|
||||
import DictTag from '@/components/common/DictTag';
|
||||
</#if>
|
||||
<#if needFileUpload>
|
||||
import FileUpload from '@/components/common/FileUpload';
|
||||
</#if>
|
||||
<#if needImagePreview>
|
||||
import ImagePreview from '@/components/common/ImagePreview';
|
||||
</#if>
|
||||
<#if needImageUpload>
|
||||
import ImageUpload from '@/components/common/ImageUpload';
|
||||
</#if>
|
||||
<#if needEditor>
|
||||
import RichTextEditor from '@/components/common/RichTextEditor';
|
||||
</#if>
|
||||
import RowActions from '@/components/common/RowActions';
|
||||
<#if needDict>
|
||||
import { useDict } from '@/hooks/useDict';
|
||||
</#if>
|
||||
<#if needDateRange>
|
||||
import { useDateRangeQuery } from '@/hooks/useDateRangeQuery';
|
||||
</#if>
|
||||
<#if enableExport>
|
||||
import { useTableExport } from '@/hooks/useTableExport';
|
||||
</#if>
|
||||
import { useTableScroll } from '@/hooks/useTableScroll';
|
||||
import { useTableSelection } from '@/hooks/useTableSelection';
|
||||
import { useUserStore } from '@/stores/userStore';
|
||||
<#if needDict>
|
||||
import { dictOptions } from '@/utils/dict';
|
||||
</#if>
|
||||
<#if enableStatus>
|
||||
import { confirmAction } from '@/utils/modal';
|
||||
</#if>
|
||||
import { hasPermi } from '@/utils/permission';
|
||||
import { <#if needDateField>formatDateTimeFields, toDayjsFields, </#if> toPageQuery, toTableData } from '@/utils/ruoyi';
|
||||
|
||||
const default${BusinessName}Form: ${BusinessName}Form = {
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && !column.pk && column.htmlType == "checkbox">
|
||||
${column.javaField}: [],
|
||||
</#if>
|
||||
</#list>
|
||||
};
|
||||
|
||||
<#if enableStatus>
|
||||
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
|
||||
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
|
||||
|
||||
</#if>
|
||||
export default function ${BusinessName}Page() {
|
||||
const actionRef = useRef<ActionType | undefined>(undefined);
|
||||
const { tableScroll } = useTableScroll(1000);
|
||||
const [form] = Form.useForm<${BusinessName}Form>();
|
||||
const userInfo = useUserStore(state => state.userInfo);
|
||||
<#if needDict>
|
||||
const dicts = useDict(${dicts});
|
||||
</#if>
|
||||
const { ids, selectedOne, handleSelectionChange, clearSelection } = useTableSelection<${BusinessName}VO>(
|
||||
row => row.${pkColumn.javaField}
|
||||
);
|
||||
const [modalOpen, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false);
|
||||
const [modalTitle, setModalTitle] = useState('');
|
||||
<#if enableExport>
|
||||
const { updateExportParams, exportFile } = useTableExport();
|
||||
</#if>
|
||||
<#list columns as column>
|
||||
<#if column.query && column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
const { applyDateRange: apply${column.capJavaField}DateRange } = useDateRangeQuery('${column.capJavaField}');
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
const canAdd = hasPermi(userInfo, ['${permissionPrefix}:add']);
|
||||
const canEdit = hasPermi(userInfo, ['${permissionPrefix}:edit']);
|
||||
const canRemove = hasPermi(userInfo, ['${permissionPrefix}:remove']);
|
||||
<#if enableExport>
|
||||
const canExport = hasPermi(userInfo, ['${permissionPrefix}:export']);
|
||||
</#if>
|
||||
|
||||
const openAdd = () => {
|
||||
form.resetFields();
|
||||
form.setFieldsValue(default${BusinessName}Form);
|
||||
setModalTitle('添加${functionName}');
|
||||
openModal();
|
||||
};
|
||||
|
||||
const openEdit = async (row?: ${BusinessName}VO) => {
|
||||
const target = row || selectedOne;
|
||||
if (!target?.${pkColumn.javaField}) return;
|
||||
const res = await get${BusinessName}(target.${pkColumn.javaField});
|
||||
const data = <#if needDateField>toDayjsFields({ ...res.data }, [
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "datetime">
|
||||
'${column.javaField}',
|
||||
</#if>
|
||||
</#list>
|
||||
])<#else>{ ...res.data }</#if>;
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
|
||||
if (typeof data.${column.javaField} === 'string') {
|
||||
data.${column.javaField} = data.${column.javaField}.split(',');
|
||||
}
|
||||
</#if>
|
||||
</#list>
|
||||
form.resetFields();
|
||||
form.setFieldsValue(data);
|
||||
setModalTitle('修改${functionName}');
|
||||
openModal();
|
||||
};
|
||||
|
||||
const submitForm = async (values: ${BusinessName}Form) => {
|
||||
const submitValues = <#if needDateField>formatDateTimeFields({ ...values }, [
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "datetime">
|
||||
'${column.javaField}',
|
||||
</#if>
|
||||
</#list>
|
||||
])<#else>{ ...values }</#if>;
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && column.htmlType == "checkbox">
|
||||
if (Array.isArray(submitValues.${column.javaField})) {
|
||||
submitValues.${column.javaField} = submitValues.${column.javaField}.join(',');
|
||||
}
|
||||
</#if>
|
||||
</#list>
|
||||
submitValues.${pkColumn.javaField} ? await update${BusinessName}(submitValues) : await add${BusinessName}(submitValues);
|
||||
message.success('操作成功');
|
||||
form.resetFields();
|
||||
actionRef.current?.reload();
|
||||
return true;
|
||||
};
|
||||
|
||||
const remove = async (row?: ${BusinessName}VO) => {
|
||||
await del${BusinessName}(row?.${pkColumn.javaField} || ids);
|
||||
message.success('删除成功');
|
||||
clearSelection();
|
||||
actionRef.current?.reloadAndRest?.();
|
||||
};
|
||||
|
||||
<#if enableStatus>
|
||||
const handleStatusChange = async (row: ${BusinessName}VO, checked: boolean) => {
|
||||
const previousStatus = row.${statusField};
|
||||
const status = checked ? ${statusField}ActiveValue : ${statusField}InactiveValue;
|
||||
const text = checked ? '启用' : '停用';
|
||||
try {
|
||||
await confirmAction(`确认要"${r'${text}'}"吗?`);
|
||||
await change${BusinessName}Status(row.${pkColumn.javaField}, status);
|
||||
message.success(`${r'${text}'}成功`);
|
||||
actionRef.current?.reload();
|
||||
} catch {
|
||||
row.${statusField} = previousStatus;
|
||||
actionRef.current?.reload();
|
||||
}
|
||||
};
|
||||
|
||||
</#if>
|
||||
<#if enableSort>
|
||||
const handleSortChange = async (row: ${BusinessName}VO, value?: number | null) => {
|
||||
await update${BusinessName}Sort(row.${pkColumn.javaField}, value || 0);
|
||||
message.success('排序更新成功');
|
||||
actionRef.current?.reload();
|
||||
};
|
||||
|
||||
</#if>
|
||||
const columns: ProColumns<${BusinessName}VO>[] = [
|
||||
<#list columns as column>
|
||||
<#if column.list>
|
||||
<#if enableStatus && statusField == column.javaField>
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
valueType: 'select',
|
||||
width: 100,
|
||||
<#if column.dictType?has_content>
|
||||
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
|
||||
</#if>
|
||||
render: (_, row) => (
|
||||
<Switch
|
||||
checked={row.${column.javaField} === ${statusField}ActiveValue}
|
||||
disabled={!canEdit}
|
||||
onChange={checked => handleStatusChange(row, checked)}
|
||||
/>
|
||||
)
|
||||
},
|
||||
<#elseif enableSort && sortField == column.javaField>
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
search: false,
|
||||
width: 130,
|
||||
render: (_, row) => <InputNumber min={0} value={row.${column.javaField} as number} onChange={value => handleSortChange(row, value)} />
|
||||
},
|
||||
<#elseif column.htmlType == "datetime">
|
||||
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}', valueType: 'dateTime', search: <#if column.query && column.queryType != "BETWEEN">true<#else> false</#if>, width: 170 },
|
||||
<#elseif column.htmlType == "imageUpload">
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}Url',
|
||||
search: false,
|
||||
width: 100,
|
||||
render: (_, row) => <ImagePreview src={row.${column.javaField}Url || String(row.${column.javaField} || '')} width={50} height={50} />
|
||||
},
|
||||
<#elseif column.dictColumn>
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
valueType: 'select',
|
||||
fieldProps: { options: dictOptions(dicts.${column.dictType}) },
|
||||
render: (_, row) => <DictTag options={dicts.${column.dictType}} value={row.${column.javaField}} />
|
||||
},
|
||||
<#elseif column.htmlType == "switch">
|
||||
{
|
||||
title: '${column.columnLabel}',
|
||||
dataIndex: '${column.javaField}',
|
||||
valueType: 'select',
|
||||
width: 100,
|
||||
render: (_, row) => <Switch checked={row.${column.javaField} === ${column.switchActiveValue}} disabled />
|
||||
},
|
||||
<#else>
|
||||
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}'<#if !column.query>, search: false</#if> },
|
||||
</#if>
|
||||
</#if>
|
||||
<#if column.query && column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
{ title: '${column.columnLabel}', dataIndex: '${column.javaField}Range', valueType: 'dateTimeRange', hideInTable: true },
|
||||
</#if>
|
||||
</#list>
|
||||
{
|
||||
title: '操作',
|
||||
valueType: 'option',
|
||||
width: 92,
|
||||
fixed: 'right',
|
||||
render: (_, row) => (
|
||||
<RowActions
|
||||
actions={[
|
||||
canEdit && { key: 'edit', label: '修改', icon: <EditOutlined />, onClick: () => openEdit(row) },
|
||||
canRemove && {
|
||||
key: 'delete',
|
||||
label: '删除',
|
||||
icon: <DeleteOutlined />,
|
||||
danger: true,
|
||||
confirm: `是否确认删除${functionName}编号为"${r'${row.'}${pkColumn.javaField}${r'}'}"的数据项?`,
|
||||
onClick: () => remove(row)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<PageContainer title="${functionName}">
|
||||
<ProTable<${BusinessName}VO, ${BusinessName}Query<#if needDateRange> & Record<string, unknown></#if>>
|
||||
actionRef={actionRef}
|
||||
rowKey="${pkColumn.javaField}"
|
||||
columns={columns}
|
||||
scroll={tableScroll}
|
||||
search={{ labelWidth: 90 }}
|
||||
pagination={{ defaultPageSize: 10, showSizeChanger: true }}
|
||||
rowSelection={{ selectedRowKeys: ids, onChange: handleSelectionChange }}
|
||||
request={async params => {
|
||||
<#if needDateRange>
|
||||
let query = toPageQuery(params);
|
||||
<#list columns as column>
|
||||
<#if column.query && column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
query = apply${column.capJavaField}DateRange(query, params.${column.javaField}Range as [string, string] | undefined);
|
||||
delete query.${column.javaField}Range;
|
||||
</#if>
|
||||
</#list>
|
||||
<#else>
|
||||
const query = toPageQuery(params);
|
||||
</#if>
|
||||
<#if enableExport>
|
||||
updateExportParams(query);
|
||||
</#if>
|
||||
const res = await list${BusinessName}(query);
|
||||
return toTableData(res);
|
||||
}}
|
||||
toolbar={{ title: '${functionName}列表' }}
|
||||
toolBarRender={() => [
|
||||
canAdd && (
|
||||
<Button key="add" type="primary" icon={<PlusOutlined />} onClick={openAdd}>
|
||||
新增
|
||||
</Button>
|
||||
),
|
||||
canEdit && (
|
||||
<Button key="edit" disabled={!selectedOne} icon={<EditOutlined />} onClick={() => openEdit()}>
|
||||
修改
|
||||
</Button>
|
||||
),
|
||||
canRemove && (
|
||||
<Popconfirm key="delete" title={`是否确认删除${functionName}编号为"${r'${ids}'}"的数据项?`} onConfirm={() => remove()}>
|
||||
<Button danger disabled={!ids.length} icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
)<#if enableExport>,
|
||||
canExport && (
|
||||
<Button
|
||||
key="export"
|
||||
icon={<DownloadOutlined />}
|
||||
onClick={() => exportFile('/${moduleName}/${businessName}/export', () => `${businessName}_${r'${Date.now()}'}.xlsx`)}
|
||||
>
|
||||
导出
|
||||
</Button>
|
||||
)</#if>
|
||||
]}
|
||||
/>
|
||||
|
||||
<ModalForm<${BusinessName}Form>
|
||||
title={modalTitle}
|
||||
open={modalOpen}
|
||||
width={560}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={default${BusinessName}Form}
|
||||
modalProps={{ destroyOnHidden: true, onCancel: closeModal }}
|
||||
onOpenChange={open => !open && closeModal()}
|
||||
onFinish={submitForm}
|
||||
>
|
||||
<ProFormText name="${pkColumn.javaField}" hidden />
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && !column.pk>
|
||||
<#if column.htmlType == "input">
|
||||
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "textarea">
|
||||
<ProFormTextArea name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "inputNumber">
|
||||
<ProFormDigit name="${column.javaField}" label="${column.columnLabel}" min={0} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif (column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch") && column.dictType?has_content>
|
||||
<ProFormSelect name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
|
||||
<ProFormCheckbox.Group name="${column.javaField}" label="${column.columnLabel}" options={dictOptions(dicts.${column.dictType})} <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "switch">
|
||||
<Form.Item
|
||||
name="${column.javaField}"
|
||||
label="${column.columnLabel}"
|
||||
valuePropName="checked"
|
||||
getValueProps={value => ({ checked: value === ${column.switchActiveValue} })}
|
||||
normalize={checked => (checked ? ${column.switchActiveValue} : ${column.switchInactiveValue})}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<#elseif column.htmlType == "datetime">
|
||||
<ProFormDateTimePicker name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
<#elseif column.htmlType == "imageUpload">
|
||||
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
|
||||
<ImageUpload />
|
||||
</Form.Item>
|
||||
<#elseif column.htmlType == "fileUpload">
|
||||
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
|
||||
<FileUpload />
|
||||
</Form.Item>
|
||||
<#elseif column.htmlType == "editor">
|
||||
<Form.Item name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if>>
|
||||
<RichTextEditor />
|
||||
</Form.Item>
|
||||
<#else>
|
||||
<ProFormText name="${column.javaField}" label="${column.columnLabel}" <#if column.required>rules={[{ required: true, message: '${column.columnLabel}不能为空' }]}</#if> />
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
</ModalForm>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import type { BaseEntity<#if !table.tree>, PageQuery</#if> } from '@/api/types';
|
||||
|
||||
export interface ${BusinessName}VO {
|
||||
<#list columns as column>
|
||||
<#if column.list || column.pk>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
${column.javaField}: ${column.tsType};
|
||||
<#if column.htmlType == "imageUpload">
|
||||
/**
|
||||
* ${column.columnComment}Url
|
||||
*/
|
||||
${column.javaField}Url?: string;
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<#if table.tree>
|
||||
/**
|
||||
* 子对象
|
||||
*/
|
||||
children?: ${BusinessName}VO[];
|
||||
</#if>
|
||||
}
|
||||
|
||||
export interface ${BusinessName}Form extends BaseEntity {
|
||||
<#list columns as column>
|
||||
<#if column.insert || column.edit || column.pk>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
<#if column.htmlType == "checkbox">
|
||||
${column.javaField}?: string | string[];
|
||||
<#else>
|
||||
${column.javaField}?: ${column.tsType};
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
|
||||
export interface ${BusinessName}Query<#if !table.tree> extends PageQuery</#if> {
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
${column.javaField}?: ${column.tsType};
|
||||
</#if>
|
||||
</#list>
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: Record<string, unknown>;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[0]}, '${functionName}', ${parentMenuId}, 1, '${businessName}', '${moduleName}/${businessName}/index', 'N', 'Y', 'C', '0', '0', '${permissionPrefix}:list', '#', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '${functionName}菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, 1, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:query', '#', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, 2, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:add', '#', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, 3, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:edit', '#', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, 4, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:remove', '#', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '');
|
||||
<#if enableExport>
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, 5, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:export', '#', 1761000000000000103, 1761100000000000001, sysdate(), null, null, '');
|
||||
</#if>
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[0]}, '${functionName}', ${parentMenuId}, 1, '${businessName}', '${moduleName}/${businessName}/index', 'N', 'Y', 'C', '0', '0', '${permissionPrefix}:list', '#', 1761000000000000103, 1761100000000000001, sysdate, null, null, '${functionName}菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, 1, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:query', '#', 1761000000000000103, 1761100000000000001, sysdate, null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, 2, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:add', '#', 1761000000000000103, 1761100000000000001, sysdate, null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, 3, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:edit', '#', 1761000000000000103, 1761100000000000001, sysdate, null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, 4, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:remove', '#', 1761000000000000103, 1761100000000000001, sysdate, null, null, '');
|
||||
<#if enableExport>
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, 5, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:export', '#', 1761000000000000103, 1761100000000000001, sysdate, null, null, '');
|
||||
</#if>
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[0]}, '${functionName}', ${parentMenuId}, 1, '${businessName}', '${moduleName}/${businessName}/index', 'N', 'Y', 'C', '0', '0', '${permissionPrefix}:list', '#', 1761000000000000103, 1761100000000000001, now(), null, null, '${functionName}菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, 1, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:query', '#', 1761000000000000103, 1761100000000000001, now(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, 2, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:add', '#', 1761000000000000103, 1761100000000000001, now(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, 3, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:edit', '#', 1761000000000000103, 1761100000000000001, now(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, 4, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:remove', '#', 1761000000000000103, 1761100000000000001, now(), null, null, '');
|
||||
<#if enableExport>
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, 5, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:export', '#', 1761000000000000103, 1761100000000000001, now(), null, null, '');
|
||||
</#if>
|
||||
@@ -0,0 +1,20 @@
|
||||
-- 菜单 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[0]}, '${functionName}', ${parentMenuId}, 1, '${businessName}', '${moduleName}/${businessName}/index', 'N', 'Y', 'C', '0', '0', '${permissionPrefix}:list', '#', 1761000000000000103, 1761100000000000001, getdate(), null, null, '${functionName}菜单');
|
||||
|
||||
-- 按钮 SQL
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[1]}, '${functionName}查询', ${table.menuIds[0]}, 1, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:query', '#', 1761000000000000103, 1761100000000000001, getdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[2]}, '${functionName}新增', ${table.menuIds[0]}, 2, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:add', '#', 1761000000000000103, 1761100000000000001, getdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[3]}, '${functionName}修改', ${table.menuIds[0]}, 3, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:edit', '#', 1761000000000000103, 1761100000000000001, getdate(), null, null, '');
|
||||
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[4]}, '${functionName}删除', ${table.menuIds[0]}, 4, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:remove', '#', 1761000000000000103, 1761100000000000001, getdate(), null, null, '');
|
||||
<#if enableExport>
|
||||
insert into sys_menu (menu_id, menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_dept, create_by, create_time, update_by, update_time, remark)
|
||||
values(${table.menuIds[5]}, '${functionName}导出', ${table.menuIds[0]}, 5, '#', '', 'N', 'Y', 'F', '0', '0', '${permissionPrefix}:export', '#', 1761000000000000103, 1761100000000000001, getdate(), null, null, '');
|
||||
</#if>
|
||||
101
ruoyi-modules/ruoyi-gen/src/main/resources/fm/vue/api.ts.ftl
Normal file
101
ruoyi-modules/ruoyi-gen/src/main/resources/fm/vue/api.ts.ftl
Normal file
@@ -0,0 +1,101 @@
|
||||
import type { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
|
||||
<#if !table.tree>
|
||||
import type { PageResult } from '@/api/types';
|
||||
</#if>
|
||||
import type { AxiosPromise } from '@/utils/api-types';
|
||||
import request from '@/utils/request';
|
||||
|
||||
/**
|
||||
* 查询${functionName}列表
|
||||
* @param query
|
||||
* @returns {*}
|
||||
*/
|
||||
export const list${BusinessName} = (query?: ${BusinessName}Query): AxiosPromise<<#if table.tree>${BusinessName}VO[]<#else>PageResult<${BusinessName}VO></#if>> => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 查询${functionName}详细
|
||||
* @param ${pkColumn.javaField}
|
||||
*/
|
||||
export const get${BusinessName} = (${pkColumn.javaField}: string | number): AxiosPromise<${BusinessName}VO> => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
|
||||
method: 'get'
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 新增${functionName}
|
||||
* @param data
|
||||
*/
|
||||
export const add${BusinessName} = (data: ${BusinessName}Form) => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}',
|
||||
method: 'post',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改${functionName}
|
||||
* @param data
|
||||
*/
|
||||
export const update${BusinessName} = (data: ${BusinessName}Form) => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}',
|
||||
method: 'put',
|
||||
data: data
|
||||
});
|
||||
};
|
||||
|
||||
<#if enableStatus>
|
||||
/**
|
||||
* 修改${functionName}状态
|
||||
* @param ${pkColumn.javaField}
|
||||
* @param status
|
||||
*/
|
||||
export const change${BusinessName}Status = (${pkColumn.javaField}: string | number, status: <#if statusColumn.javaType == 'Boolean'>boolean<#elseif statusColumn.javaType == 'String'>string<#else> number</#if>) => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}/changeStatus',
|
||||
method: 'put',
|
||||
data: {
|
||||
${pkColumn.javaField},
|
||||
${statusField}: status
|
||||
}
|
||||
});
|
||||
};
|
||||
</#if>
|
||||
|
||||
<#if enableSort>
|
||||
/**
|
||||
* 调整${functionName}排序
|
||||
* @param ${pkColumn.javaField}
|
||||
* @param sortValue
|
||||
*/
|
||||
export const update${BusinessName}Sort = (${pkColumn.javaField}: string | number, sortValue: <#if sortColumn.javaType == 'String' || sortColumn.javaType == 'LocalDateTime'>string<#else> number</#if>) => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}/updateSort',
|
||||
method: 'put',
|
||||
data: {
|
||||
${pkColumn.javaField},
|
||||
${sortField}: sortValue
|
||||
}
|
||||
});
|
||||
};
|
||||
</#if>
|
||||
|
||||
/**
|
||||
* 删除${functionName}
|
||||
* @param ${pkColumn.javaField}
|
||||
*/
|
||||
export const del${BusinessName} = (${pkColumn.javaField}: string | number | Array<string | number>) => {
|
||||
return request({
|
||||
url: '/${moduleName}/${businessName}/' + ${pkColumn.javaField},
|
||||
method: 'delete'
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,691 @@
|
||||
<template>
|
||||
<div class="p-2 page-shell ${moduleName}-${businessName}-page">
|
||||
<div class="search-wrap">
|
||||
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
|
||||
<template #header>
|
||||
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
|
||||
<div><h3>筛选条件</h3></div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#if column.htmlType == "input" || column.htmlType == "textarea">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input v-model="queryParams.${column.javaField}" placeholder="请输入${column.columnLabel}" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "inputNumber">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input-number v-model="queryParams.${column.javaField}" controls-position="right" />
|
||||
</el-form-item>
|
||||
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
|
||||
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "switch" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
|
||||
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "switch">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
|
||||
<#if column.javaType == "Boolean">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
<#elseif column.javaType == "Integer" || column.javaType == "Long">
|
||||
<el-option label="开启" :value="0" />
|
||||
<el-option label="关闭" :value="1" />
|
||||
<#else>
|
||||
<el-option label="开启" value="0" />
|
||||
<el-option label="关闭" value="1" />
|
||||
</#if>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable>
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "datetime" && column.queryType != "BETWEEN">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-date-picker clearable
|
||||
v-model="queryParams.${column.javaField}"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="选择${column.columnLabel}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
<el-form-item label="${column.columnLabel}" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange${column.capJavaField}"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" class="table-panel">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
<div class="table-heading">
|
||||
<h3>${functionName}列表</h3>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
|
||||
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
|
||||
<#if enableExport>
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
|
||||
</#if>
|
||||
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<el-table
|
||||
ref="${businessName}TableRef"
|
||||
v-loading="loading"
|
||||
class="data-table"
|
||||
:data="${businessName}List"
|
||||
row-key="${treeCode}"
|
||||
border
|
||||
:default-expand-all="isExpandAll"
|
||||
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
|
||||
>
|
||||
<#assign firstTreeListField = "">
|
||||
<#list columns as tempColumn>
|
||||
<#if !tempColumn.pk && tempColumn.list && "" != tempColumn.javaField && firstTreeListField == "">
|
||||
<#assign firstTreeListField = tempColumn.javaField>
|
||||
</#if>
|
||||
</#list>
|
||||
<#list columns as column>
|
||||
<#if column.pk>
|
||||
<#elseif enableStatus && statusField == column.javaField>
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
|
||||
</#if>
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.${column.javaField}"
|
||||
:active-value="${statusField}ActiveValue"
|
||||
:inactive-value="${statusField}InactiveValue"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif enableSort && sortField == column.javaField>
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" width="160">
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="160">
|
||||
</#if>
|
||||
<template #default="scope">
|
||||
<#if column.javaType == "LocalDateTime">
|
||||
<el-date-picker
|
||||
v-model="scope.row.${column.javaField}"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择${column.columnLabel}"
|
||||
@change="handleSortChange(scope.row)"
|
||||
/>
|
||||
<#else>
|
||||
<el-input-number v-model="scope.row.${column.javaField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
|
||||
</#if>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.htmlType == "switch">
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" width="120">
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="120">
|
||||
</#if>
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.${column.javaField}"
|
||||
<#if column.javaType == "Boolean">
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
<#elseif column.javaType == "Integer" || column.javaType == "Long">
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
<#else>
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
</#if>
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.htmlType == "datetime">
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" width="180">
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="180">
|
||||
</#if>
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.${column.javaField}, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.htmlType == "imageUpload">
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}Url" width="100">
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}Url" width="100">
|
||||
</#if>
|
||||
<template #default="scope">
|
||||
<image-preview :src="scope.row.${column.javaField}Url" :width="50" :height="50"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.dictColumn>
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
|
||||
</#if>
|
||||
<template #default="scope">
|
||||
<#if column.htmlType == "checkbox">
|
||||
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField} ? scope.row.${column.javaField}.split(',') : []"/>
|
||||
<#else>
|
||||
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField}"/>
|
||||
</#if>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && "" != column.javaField>
|
||||
<#if column.javaField == firstTreeListField>
|
||||
<el-table-column label="${column.columnLabel}" prop="${column.javaField}" />
|
||||
<#else>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" />
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<#if enableStatus && !statusColumn.list>
|
||||
<el-table-column label="${statusColumn.columnComment}" align="center" prop="${statusField}">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.${statusField}"
|
||||
:active-value="${statusField}ActiveValue"
|
||||
:inactive-value="${statusField}InactiveValue"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</#if>
|
||||
<#if enableSort && !sortColumn.list>
|
||||
<el-table-column label="${sortColumn.columnComment}" align="center" prop="${sortField}" width="160">
|
||||
<template #default="scope">
|
||||
<#if sortColumn.javaType == "LocalDateTime">
|
||||
<el-date-picker
|
||||
v-model="scope.row.${sortField}"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择${sortColumn.columnComment}"
|
||||
@change="handleSortChange(scope.row)"
|
||||
/>
|
||||
<#else>
|
||||
<el-input-number v-model="scope.row.${sortField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
|
||||
</#if>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</#if>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="新增" placement="top">
|
||||
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['${moduleName}:${businessName}:add']" />
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']" />
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-card>
|
||||
<!-- 添加或修改${functionName}对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && !column.pk>
|
||||
<#if "" != treeParentCode && column.javaField == treeParentCode>
|
||||
<el-form-item label="${column.columnLabel}" prop="${treeParentCode}">
|
||||
<el-tree-select
|
||||
v-model="form.${treeParentCode}"
|
||||
:data="${businessName}Options"
|
||||
:props="{ value: '${treeCode}', label: '${treeName}', children: 'children' } as any"
|
||||
value-key="${treeCode}"
|
||||
placeholder="请选择${column.columnLabel}"
|
||||
check-strictly
|
||||
/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "input">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input v-model="form.${column.javaField}" placeholder="请输入${column.columnLabel}" />
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "inputNumber">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input-number v-model="form.${column.javaField}" controls-position="right" />
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "imageUpload">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<image-upload v-model="form.${column.javaField}"/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "fileUpload">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<file-upload v-model="form.${column.javaField}"/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "editor">
|
||||
<el-form-item label="${column.columnLabel}">
|
||||
<editor v-model="form.${column.javaField}" :min-height="192"/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "select" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
|
||||
<el-option
|
||||
v-for="dict in ${column.dictType}"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
<#if column.javaType == "Integer" || column.javaType == "Long">
|
||||
:value="parseInt(dict.value)"
|
||||
<#else>
|
||||
:value="dict.value"
|
||||
</#if>
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "select" && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-checkbox-group v-model="form.${column.javaField}">
|
||||
<el-checkbox
|
||||
v-for="dict in ${column.dictType}"
|
||||
:key="dict.value"
|
||||
:label="dict.value">
|
||||
{{dict.label}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "checkbox" && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-checkbox-group v-model="form.${column.javaField}">
|
||||
<el-checkbox>请选择字典生成</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "radio" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-radio-group v-model="form.${column.javaField}">
|
||||
<el-radio
|
||||
v-for="dict in ${column.dictType}"
|
||||
:key="dict.value"
|
||||
<#if column.javaType == "Integer" || column.javaType == "Long">
|
||||
:value="parseInt(dict.value)"
|
||||
<#else>
|
||||
:value="dict.value"
|
||||
</#if>
|
||||
>{{dict.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "radio" && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-radio-group v-model="form.${column.javaField}">
|
||||
<el-radio value="1">请选择字典生成</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "switch">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-switch
|
||||
v-model="form.${column.javaField}"
|
||||
<#if column.javaType == "Boolean">
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
<#elseif column.javaType == "Integer" || column.javaType == "Long">
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
<#else>
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
</#if>
|
||||
/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "datetime">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-date-picker clearable
|
||||
v-model="form.${column.javaField}"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择${column.columnLabel}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "textarea">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input v-model="form.${column.javaField}" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="${BusinessName}" lang="ts">
|
||||
import {
|
||||
add${BusinessName},
|
||||
<#if enableStatus>
|
||||
change${BusinessName}Status,
|
||||
</#if>
|
||||
del${BusinessName},
|
||||
get${BusinessName},
|
||||
list${BusinessName},
|
||||
<#if enableSort>
|
||||
update${BusinessName}Sort,
|
||||
</#if>
|
||||
update${BusinessName}
|
||||
} from '@/api/${moduleName}/${businessName}';
|
||||
import { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
|
||||
import { useLoading } from '@/hooks/async/useLoading';
|
||||
import { useFormDialog } from '@/hooks/dialog/useFormDialog';
|
||||
<#if needAddDateRange>
|
||||
import { useDateRangeQuery } from '@/hooks/form/useDateRangeQuery';
|
||||
</#if>
|
||||
import { useSearchReset } from '@/hooks/form/useSearchReset';
|
||||
import { useSearchToggle } from '@/hooks/form/useSearchToggle';
|
||||
import { useTreeTableExpand } from '@/hooks/tree/useTreeTableExpand';
|
||||
<#if needDict>
|
||||
import { useDict } from '@/utils/dict';
|
||||
</#if>
|
||||
import modal from '@/plugins/modal';
|
||||
import { handleTree<#if needParseTime>, parseTime</#if> } from '@/utils/ruoyi';
|
||||
<#if enableExport>
|
||||
import { download as requestDownload } from '@/utils/request';
|
||||
</#if>
|
||||
|
||||
<#if needDict>
|
||||
const { ${dictsNoSymbol} } = toRefs<any>(useDict(${dicts}));
|
||||
</#if>
|
||||
|
||||
<#if enableStatus>
|
||||
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
|
||||
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
|
||||
</#if>
|
||||
|
||||
type ${BusinessName}Option = {
|
||||
${treeCode}: <#if treeParentColumn.javaType == 'String'>string<#else> number</#if>;
|
||||
${treeName}: string;
|
||||
children?: ${BusinessName}Option[];
|
||||
};
|
||||
|
||||
const ${businessName}List = ref<${BusinessName}VO[]>([]);
|
||||
const ${businessName}Options = ref<${BusinessName}Option[]>([]);
|
||||
const all${BusinessName}Options = ref<${BusinessName}Option[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const { showSearch } = useSearchToggle();
|
||||
const { loading, setLoading, withLoading } = useLoading();
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const ${businessName}FormRef = ref<ElFormInstance>();
|
||||
const ${businessName}TableRef = ref<ElTableInstance>();
|
||||
const { isExpandAll, handleToggleExpandAll } = useTreeTableExpand<${BusinessName}VO>({
|
||||
tableRef: ${businessName}TableRef,
|
||||
data: ${businessName}List
|
||||
});
|
||||
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
const {
|
||||
dateRange: dateRange${column.capJavaField},
|
||||
applyDateRange: apply${column.capJavaField}DateRange,
|
||||
resetDateRange: reset${column.capJavaField}DateRange
|
||||
} = useDateRangeQuery('${column.capJavaField}');
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
const initFormData: ${BusinessName}Form = {
|
||||
<#list columns as column>
|
||||
<#if column.insert || column.edit>
|
||||
<#if column.htmlType == "checkbox">
|
||||
${column.javaField}: [],
|
||||
<#else>
|
||||
${column.javaField}: undefined,
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
|
||||
const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
|
||||
form: {...initFormData},
|
||||
queryParams: {
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#if column.htmlType != "datetime" || column.queryType != "BETWEEN">
|
||||
${column.javaField}: undefined,
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
params: {
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
${column.javaField}: undefined,
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
<#list columns as column>
|
||||
<#if column.insert || column.edit>
|
||||
<#if column.required>
|
||||
${column.javaField}: [
|
||||
{ required: true, message: "${column.columnLabel}不能为空", trigger: <#if column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch" || column.htmlType == "inputNumber">"change"<#else>"blur"</#if> }
|
||||
],
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
const { dialog, resetForm: reset, openDialog, showDialog, closeDialog } = useFormDialog({
|
||||
form,
|
||||
formRef: ${businessName}FormRef,
|
||||
initialFormData: initFormData
|
||||
});
|
||||
|
||||
/** 查询${functionName}列表 */
|
||||
const getList = async () => {
|
||||
await withLoading(async () => {
|
||||
<#if needAddDateRange>
|
||||
let params = queryParams.value;
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
params = apply${column.capJavaField}DateRange(params);
|
||||
</#if>
|
||||
</#list>
|
||||
const res = await list${BusinessName}(params);
|
||||
<#else>
|
||||
const res = await list${BusinessName}(queryParams.value);
|
||||
</#if>
|
||||
const data = handleTree<${BusinessName}VO>(res.data, '${treeCode}', '${treeParentCode}');
|
||||
if (data) {
|
||||
${businessName}List.value = data;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 查询${functionName}下拉树结构 */
|
||||
const getTreeselect = async (excludeId?: string | number) => {
|
||||
const res = await list${BusinessName}();
|
||||
const data: ${BusinessName}Option = { ${treeCode}: ${treeRootValueTsLiteral}, ${treeName}: '顶级节点', children: [] };
|
||||
data.children = handleTree<${BusinessName}Option>(res.data, '${treeCode}', '${treeParentCode}');
|
||||
all${BusinessName}Options.value = [data];
|
||||
${businessName}Options.value = excludeId != null ? filterTreeOptions(all${BusinessName}Options.value, excludeId) : all${BusinessName}Options.value;
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
getList();
|
||||
};
|
||||
|
||||
const { resetQuery } = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
resetExtras: () => {
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
reset${column.capJavaField}DateRange();
|
||||
</#if>
|
||||
</#list>
|
||||
},
|
||||
afterReset: () => {
|
||||
handleQuery();
|
||||
}
|
||||
});
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = (row?: Partial<${BusinessName}VO>) => {
|
||||
openDialog('添加${functionName}');
|
||||
getTreeselect();
|
||||
if (row != null && row.${treeCode}) {
|
||||
form.value.${treeParentCode} = row.${treeCode};
|
||||
} else {
|
||||
form.value.${treeParentCode} = ${treeRootValueTsLiteral};
|
||||
}
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row: Partial<${BusinessName}VO>) => {
|
||||
reset();
|
||||
await getTreeselect(row.${treeCode});
|
||||
if (row != null) {
|
||||
form.value.${treeParentCode} = row.${treeParentCode};
|
||||
}
|
||||
const res = await get${BusinessName}(row.${pkColumn.javaField});
|
||||
Object.assign(form.value, res.data);
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "checkbox">
|
||||
form.value.${column.javaField} = form.value.${column.javaField}.split(",");
|
||||
</#if>
|
||||
</#list>
|
||||
showDialog('修改${functionName}');
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
${businessName}FormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "checkbox">
|
||||
form.value.${column.javaField} = form.value.${column.javaField}.join(",");
|
||||
</#if>
|
||||
</#list>
|
||||
if (form.value.${pkColumn.javaField}) {
|
||||
await update${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await add${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
modal.msgSuccess('操作成功');
|
||||
closeDialog();
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row: Partial<${BusinessName}VO>) => {
|
||||
await modal.confirm('是否确认删除${functionName}编号为"' + row.${pkColumn.javaField} + '"的数据项?');
|
||||
setLoading(true);
|
||||
await del${BusinessName}(row.${pkColumn.javaField}).finally(() => setLoading(false));
|
||||
await getList();
|
||||
modal.msgSuccess('删除成功');
|
||||
};
|
||||
|
||||
const filterTreeOptions = (options: ${BusinessName}Option[], excludeId: string | number): ${BusinessName}Option[] => {
|
||||
return options
|
||||
.filter(item => item.${treeCode} !== excludeId)
|
||||
.map(item => ({
|
||||
...item,
|
||||
children: item.children ? filterTreeOptions(item.children, excludeId) : []
|
||||
}));
|
||||
};
|
||||
|
||||
<#if enableStatus>
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: Partial<${BusinessName}VO>) => {
|
||||
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
|
||||
try {
|
||||
await modal.confirm('确认要"' + text + '"吗?');
|
||||
await change${BusinessName}Status(row.${pkColumn.javaField}, row.${statusField});
|
||||
modal.msgSuccess(text + '成功');
|
||||
} catch (err) {
|
||||
row.${statusField} = row.${statusField} === ${statusField}ActiveValue ? ${statusField}InactiveValue : ${statusField}ActiveValue;
|
||||
}
|
||||
};
|
||||
</#if>
|
||||
|
||||
<#if enableSort>
|
||||
/** 排序调整 */
|
||||
const handleSortChange = async (row: Partial<${BusinessName}VO>) => {
|
||||
try {
|
||||
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
|
||||
modal.msgSuccess('排序更新成功');
|
||||
} catch (err) {
|
||||
await getList();
|
||||
}
|
||||
};
|
||||
</#if>
|
||||
|
||||
<#if enableExport>
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
requestDownload(
|
||||
'${moduleName}/${businessName}/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`${businessName}_${r'${new Date().getTime()}'}.xlsx`
|
||||
);
|
||||
};
|
||||
</#if>
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
615
ruoyi-modules/ruoyi-gen/src/main/resources/fm/vue/index.vue.ftl
Normal file
615
ruoyi-modules/ruoyi-gen/src/main/resources/fm/vue/index.vue.ftl
Normal file
@@ -0,0 +1,615 @@
|
||||
<template>
|
||||
<div class="p-2 page-shell ${moduleName}-${businessName}-page">
|
||||
<div class="search-wrap">
|
||||
<el-card shadow="hover" class="search-panel" :class="{ 'is-collapsed': !showSearch }">
|
||||
<template #header>
|
||||
<div class="panel-heading search-panel-toggle" @click.stop="showSearch = !showSearch">
|
||||
<div><h3>筛选条件</h3></div>
|
||||
</div>
|
||||
</template>
|
||||
<el-form ref="queryFormRef" :model="queryParams" :inline="true" class="query-form">
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#if column.htmlType == "input" || column.htmlType == "textarea">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input v-model="queryParams.${column.javaField}" placeholder="请输入${column.columnLabel}" clearable @keyup.enter="handleQuery" />
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "inputNumber">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input-number v-model="queryParams.${column.javaField}" controls-position="right" />
|
||||
</el-form-item>
|
||||
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
|
||||
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "switch" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
|
||||
<el-option v-for="dict in ${column.dictType}" :key="dict.value" :label="dict.label" :value="dict.value"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "switch">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
|
||||
<#if column.javaType == "Boolean">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
<#elseif column.javaType == "Integer" || column.javaType == "Long">
|
||||
<el-option label="开启" :value="0" />
|
||||
<el-option label="关闭" :value="1" />
|
||||
<#else>
|
||||
<el-option label="开启" value="0" />
|
||||
<el-option label="关闭" value="1" />
|
||||
</#if>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif (column.htmlType == "select" || column.htmlType == "radio") && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="queryParams.${column.javaField}" placeholder="请选择${column.columnLabel}" clearable >
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "datetime" && column.queryType != "BETWEEN">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-date-picker clearable
|
||||
v-model="queryParams.${column.javaField}"
|
||||
type="date"
|
||||
value-format="YYYY-MM-DD"
|
||||
placeholder="请选择${column.columnLabel}"
|
||||
/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
|
||||
<el-form-item label="${column.columnLabel}" style="width: 308px">
|
||||
<el-date-picker
|
||||
v-model="dateRange${column.capJavaField}"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
type="daterange"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
:default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
|
||||
/>
|
||||
</el-form-item>
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
||||
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<el-card shadow="hover" class="table-panel">
|
||||
<template #header>
|
||||
<div class="toolbar-shell">
|
||||
<div class="table-heading">
|
||||
<h3>${functionName}列表</h3>
|
||||
</div>
|
||||
<div class="toolbar-actions">
|
||||
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['${moduleName}:${businessName}:add']">新增</el-button>
|
||||
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['${moduleName}:${businessName}:edit']">修改</el-button>
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['${moduleName}:${businessName}:remove']">删除</el-button>
|
||||
<#if enableExport>
|
||||
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['${moduleName}:${businessName}:export']">导出</el-button>
|
||||
</#if>
|
||||
<right-toolbar v-model:show-search="showSearch" :search="false" @query-table="getList"></right-toolbar>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table v-loading="loading" border class="data-table" :data="${businessName}List" @selection-change="handleSelectionChange">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<#list columns as column>
|
||||
<#if column.pk && column.list>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" />
|
||||
<#elseif enableStatus && statusField == column.javaField>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.${column.javaField}"
|
||||
:active-value="${statusField}ActiveValue"
|
||||
:inactive-value="${statusField}InactiveValue"
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif enableSort && sortField == column.javaField>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="160">
|
||||
<template #default="scope">
|
||||
<#if column.javaType == "LocalDateTime">
|
||||
<el-date-picker
|
||||
v-model="scope.row.${column.javaField}"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择${column.columnLabel}"
|
||||
@change="handleSortChange(scope.row)"
|
||||
/>
|
||||
<#else>
|
||||
<el-input-number v-model="scope.row.${column.javaField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
|
||||
</#if>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.htmlType == "switch">
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="120">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.${column.javaField}"
|
||||
<#if column.javaType == "Boolean">
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
<#elseif column.javaType == "Integer" || column.javaType == "Long">
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
<#else>
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
</#if>
|
||||
disabled
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.htmlType == "datetime">
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" width="180">
|
||||
<template #default="scope">
|
||||
<span>{{ parseTime(scope.row.${column.javaField}, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.htmlType == "imageUpload">
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}Url" width="100">
|
||||
<template #default="scope">
|
||||
<image-preview :src="scope.row.${column.javaField}Url" :width="50" :height="50"/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && column.dictColumn>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}">
|
||||
<template #default="scope">
|
||||
<#if column.htmlType == "checkbox">
|
||||
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField} ? scope.row.${column.javaField}.split(',') : []"/>
|
||||
<#else>
|
||||
<dict-tag :options="${column.dictType}" :value="scope.row.${column.javaField}"/>
|
||||
</#if>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<#elseif column.list && "" != column.javaField>
|
||||
<el-table-column label="${column.columnLabel}" align="center" prop="${column.javaField}" />
|
||||
</#if>
|
||||
</#list>
|
||||
<#if enableStatus && !statusColumn.list>
|
||||
<el-table-column label="${statusColumn.columnComment}" align="center" prop="${statusField}">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-model="scope.row.${statusField}"
|
||||
<#if statusColumn.javaType == "Boolean">
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
<#else>
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
</#if>
|
||||
@change="handleStatusChange(scope.row)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</#if>
|
||||
<#if enableSort && !sortColumn.list>
|
||||
<el-table-column label="${sortColumn.columnComment}" align="center" prop="${sortField}" width="160">
|
||||
<template #default="scope">
|
||||
<#if sortColumn.javaType == "LocalDateTime">
|
||||
<el-date-picker
|
||||
v-model="scope.row.${sortField}"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="选择${sortColumn.columnComment}"
|
||||
@change="handleSortChange(scope.row)"
|
||||
/>
|
||||
<#else>
|
||||
<el-input-number v-model="scope.row.${sortField}" controls-position="right" :min="0" @change="handleSortChange(scope.row)" />
|
||||
</#if>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</#if>
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<template #default="scope">
|
||||
<el-tooltip content="修改" placement="top">
|
||||
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['${moduleName}:${businessName}:edit']"></el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['${moduleName}:${businessName}:remove']"></el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||
</el-card>
|
||||
<!-- 添加或修改${functionName}对话框 -->
|
||||
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
|
||||
<el-form ref="${businessName}FormRef" :model="form" :rules="rules" label-width="80px">
|
||||
<#list columns as column>
|
||||
<#if (column.insert || column.edit) && !column.pk>
|
||||
<#if column.htmlType == "input">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input v-model="form.${column.javaField}" placeholder="请输入${column.columnLabel}" />
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "inputNumber">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input-number v-model="form.${column.javaField}" controls-position="right" />
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "imageUpload">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<image-upload v-model="form.${column.javaField}"/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "fileUpload">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<file-upload v-model="form.${column.javaField}"/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "editor">
|
||||
<el-form-item label="${column.columnLabel}">
|
||||
<editor v-model="form.${column.javaField}" :min-height="192"/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "select" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
|
||||
<el-option
|
||||
v-for="dict in ${column.dictType}"
|
||||
:key="dict.value"
|
||||
:label="dict.label"
|
||||
<#if column.javaType == "Integer" || column.javaType == "Long">
|
||||
:value="parseInt(dict.value)"
|
||||
<#else>
|
||||
:value="dict.value"
|
||||
</#if>
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "select" && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-select v-model="form.${column.javaField}" placeholder="请选择${column.columnLabel}">
|
||||
<el-option label="请选择字典生成" value="" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "checkbox" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-checkbox-group v-model="form.${column.javaField}">
|
||||
<el-checkbox
|
||||
v-for="dict in ${column.dictType}"
|
||||
:key="dict.value"
|
||||
:label="dict.value">
|
||||
{{dict.label}}
|
||||
</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "checkbox" && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-checkbox-group v-model="form.${column.javaField}">
|
||||
<el-checkbox>请选择字典生成</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "radio" && column.dictType?has_content>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-radio-group v-model="form.${column.javaField}">
|
||||
<el-radio
|
||||
v-for="dict in ${column.dictType}"
|
||||
:key="dict.value"
|
||||
<#if column.javaType == "Integer" || column.javaType == "Long">
|
||||
:value="parseInt(dict.value)"
|
||||
<#else>
|
||||
:value="dict.value"
|
||||
</#if>
|
||||
>{{dict.label}}</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "radio" && !(column.dictType?has_content)>
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-radio-group v-model="form.${column.javaField}">
|
||||
<el-radio value="1">请选择字典生成</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "switch">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-switch
|
||||
v-model="form.${column.javaField}"
|
||||
<#if column.javaType == "Boolean">
|
||||
:active-value="true"
|
||||
:inactive-value="false"
|
||||
<#elseif column.javaType == "Integer" || column.javaType == "Long">
|
||||
:active-value="0"
|
||||
:inactive-value="1"
|
||||
<#else>
|
||||
active-value="0"
|
||||
inactive-value="1"
|
||||
</#if>
|
||||
/>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "datetime">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-date-picker clearable
|
||||
v-model="form.${column.javaField}"
|
||||
type="datetime"
|
||||
value-format="YYYY-MM-DD HH:mm:ss"
|
||||
placeholder="请选择${column.columnLabel}">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
<#elseif column.htmlType == "textarea">
|
||||
<el-form-item label="${column.columnLabel}" prop="${column.javaField}">
|
||||
<el-input v-model="form.${column.javaField}" type="textarea" placeholder="请输入内容" />
|
||||
</el-form-item>
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||
<el-button @click="cancel">取 消</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup name="${BusinessName}" lang="ts">
|
||||
import {
|
||||
add${BusinessName},
|
||||
<#if enableStatus>
|
||||
change${BusinessName}Status,
|
||||
</#if>
|
||||
del${BusinessName},
|
||||
get${BusinessName},
|
||||
list${BusinessName},
|
||||
<#if enableSort>
|
||||
update${BusinessName}Sort,
|
||||
</#if>
|
||||
update${BusinessName}
|
||||
} from '@/api/${moduleName}/${businessName}';
|
||||
import { ${BusinessName}Form, ${BusinessName}Query, ${BusinessName}VO } from '@/api/${moduleName}/${businessName}/types';
|
||||
import { useLoading } from '@/hooks/async/useLoading';
|
||||
import { useFormDialog } from '@/hooks/dialog/useFormDialog';
|
||||
<#if needAddDateRange>
|
||||
import { useDateRangeQuery } from '@/hooks/form/useDateRangeQuery';
|
||||
</#if>
|
||||
import { useSearchReset } from '@/hooks/form/useSearchReset';
|
||||
import { useSearchToggle } from '@/hooks/form/useSearchToggle';
|
||||
import { useTableSelection } from '@/hooks/table/useTableSelection';
|
||||
<#if needDict>
|
||||
import { useDict } from '@/utils/dict';
|
||||
</#if>
|
||||
<#if needParseTime>
|
||||
import { parseTime } from '@/utils/ruoyi';
|
||||
</#if>
|
||||
import modal from '@/plugins/modal';
|
||||
<#if enableExport>
|
||||
import { download as requestDownload } from '@/utils/request';
|
||||
</#if>
|
||||
|
||||
<#if needDict>
|
||||
const { ${dictsNoSymbol} } = toRefs<any>(useDict(${dicts}));
|
||||
</#if>
|
||||
|
||||
<#if enableStatus>
|
||||
const ${statusField}ActiveValue = <#if statusColumn.javaType == "Boolean">true<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">0<#else>'0'</#if>;
|
||||
const ${statusField}InactiveValue = <#if statusColumn.javaType == "Boolean">false<#elseif statusColumn.javaType == "Integer" || statusColumn.javaType == "Long">1<#else>'1'</#if>;
|
||||
</#if>
|
||||
|
||||
const ${businessName}List = ref<${BusinessName}VO[]>([]);
|
||||
const buttonLoading = ref(false);
|
||||
const { loading, withLoading } = useLoading(true);
|
||||
const { showSearch } = useSearchToggle();
|
||||
const total = ref(0);
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
|
||||
const {
|
||||
dateRange: dateRange${column.capJavaField},
|
||||
applyDateRange: apply${column.capJavaField}DateRange,
|
||||
resetDateRange: reset${column.capJavaField}DateRange
|
||||
} = useDateRangeQuery('${column.capJavaField}');
|
||||
</#if>
|
||||
</#list>
|
||||
|
||||
const queryFormRef = ref<ElFormInstance>();
|
||||
const ${businessName}FormRef = ref<ElFormInstance>();
|
||||
|
||||
const initFormData: ${BusinessName}Form = {
|
||||
<#list columns as column>
|
||||
<#if column.insert || column.edit>
|
||||
<#if column.htmlType == "checkbox">
|
||||
${column.javaField}: [],
|
||||
<#else>
|
||||
${column.javaField}: undefined,
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
const data = reactive<PageData<${BusinessName}Form, ${BusinessName}Query>>({
|
||||
form: { ...initFormData },
|
||||
queryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#if column.htmlType != "datetime" || column.queryType != "BETWEEN">
|
||||
${column.javaField}: undefined,
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
params: {
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN">
|
||||
${column.javaField}: undefined,
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
<#list columns as column>
|
||||
<#if column.insert || column.edit>
|
||||
<#if column.required>
|
||||
${column.javaField}: [
|
||||
{ required: true, message: "${column.columnLabel}不能为空", trigger: <#if column.htmlType == "select" || column.htmlType == "radio" || column.htmlType == "switch" || column.htmlType == "inputNumber">"change"<#else>"blur"</#if> }
|
||||
],
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
});
|
||||
|
||||
const { queryParams, form, rules } = toRefs(data);
|
||||
const { ids, single, multiple, handleSelectionChange } = useTableSelection<${BusinessName}VO>(item => item.${pkColumn.javaField});
|
||||
const { dialog, resetForm: reset, openDialog, showDialog, closeDialog } = useFormDialog({
|
||||
form,
|
||||
formRef: ${businessName}FormRef,
|
||||
initialFormData: initFormData
|
||||
});
|
||||
|
||||
/** 查询${functionName}列表 */
|
||||
const getList = async () => {
|
||||
await withLoading(async () => {
|
||||
<#if needAddDateRange>
|
||||
let params = queryParams.value;
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
|
||||
params = apply${column.capJavaField}DateRange(params);
|
||||
</#if>
|
||||
</#list>
|
||||
const res = await list${BusinessName}(params);
|
||||
<#else>
|
||||
const res = await list${BusinessName}(queryParams.value);
|
||||
</#if>
|
||||
${businessName}List.value = res.data?.rows;
|
||||
total.value = res.data?.total;
|
||||
});
|
||||
};
|
||||
|
||||
/** 取消按钮 */
|
||||
const cancel = () => {
|
||||
reset();
|
||||
closeDialog();
|
||||
};
|
||||
|
||||
/** 搜索按钮操作 */
|
||||
const handleQuery = () => {
|
||||
queryParams.value.pageNum = 1;
|
||||
getList();
|
||||
};
|
||||
|
||||
const { resetQuery } = useSearchReset({
|
||||
queryFormRef,
|
||||
queryParams,
|
||||
pageNumKey: 'pageNum',
|
||||
pageSizeKey: 'pageSize',
|
||||
initialPageSize: 10,
|
||||
resetExtras: () => {
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "datetime" && column.queryType == "BETWEEN" && column.query>
|
||||
reset${column.capJavaField}DateRange();
|
||||
</#if>
|
||||
</#list>
|
||||
},
|
||||
afterReset: () => {
|
||||
handleQuery();
|
||||
}
|
||||
});
|
||||
|
||||
/** 新增按钮操作 */
|
||||
const handleAdd = () => {
|
||||
openDialog('添加${functionName}');
|
||||
};
|
||||
|
||||
/** 修改按钮操作 */
|
||||
const handleUpdate = async (row?: Partial<${BusinessName}VO>) => {
|
||||
reset();
|
||||
const _${pkColumn.javaField} = row?.${pkColumn.javaField} || ids.value[0];
|
||||
const res = await get${BusinessName}(_${pkColumn.javaField});
|
||||
Object.assign(form.value, res.data);
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "checkbox">
|
||||
form.value.${column.javaField} = form.value.${column.javaField}.split(",");
|
||||
</#if>
|
||||
</#list>
|
||||
showDialog('修改${functionName}');
|
||||
};
|
||||
|
||||
/** 提交按钮 */
|
||||
const submitForm = () => {
|
||||
${businessName}FormRef.value?.validate(async (valid: boolean) => {
|
||||
if (valid) {
|
||||
buttonLoading.value = true;
|
||||
<#list columns as column>
|
||||
<#if column.htmlType == "checkbox">
|
||||
form.value.${column.javaField} = form.value.${column.javaField}.join(",");
|
||||
</#if>
|
||||
</#list>
|
||||
if (form.value.${pkColumn.javaField}) {
|
||||
await update${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
|
||||
} else {
|
||||
await add${BusinessName}(form.value).finally(() => (buttonLoading.value = false));
|
||||
}
|
||||
modal.msgSuccess('操作成功');
|
||||
closeDialog();
|
||||
await getList();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/** 删除按钮操作 */
|
||||
const handleDelete = async (row?: Partial<${BusinessName}VO>) => {
|
||||
const _${pkColumn.javaField}s = row?.${pkColumn.javaField} || ids.value;
|
||||
await modal.confirm('是否确认删除${functionName}编号为"' + _${pkColumn.javaField}s + '"的数据项?');
|
||||
await del${BusinessName}(_${pkColumn.javaField}s);
|
||||
modal.msgSuccess('删除成功');
|
||||
await getList();
|
||||
};
|
||||
|
||||
<#if enableExport>
|
||||
/** 导出按钮操作 */
|
||||
const handleExport = () => {
|
||||
requestDownload(
|
||||
'${moduleName}/${businessName}/export',
|
||||
{
|
||||
...queryParams.value
|
||||
},
|
||||
`${businessName}_${r'${new Date().getTime()}'}.xlsx`
|
||||
);
|
||||
};
|
||||
</#if>
|
||||
|
||||
<#if enableStatus>
|
||||
/** 状态修改 */
|
||||
const handleStatusChange = async (row: Partial<${BusinessName}VO>) => {
|
||||
const text = row.${statusField} === ${statusField}ActiveValue ? '启用' : '停用';
|
||||
try {
|
||||
await modal.confirm('确认要"' + text + '"吗?');
|
||||
await change${BusinessName}Status(row.${pkColumn.javaField}, row.${statusField});
|
||||
modal.msgSuccess(text + '成功');
|
||||
} catch (err) {
|
||||
row.${statusField} = row.${statusField} === ${statusField}ActiveValue ? ${statusField}InactiveValue : ${statusField}ActiveValue;
|
||||
}
|
||||
};
|
||||
</#if>
|
||||
|
||||
<#if enableSort>
|
||||
/** 排序调整 */
|
||||
const handleSortChange = async (row: Partial<${BusinessName}VO>) => {
|
||||
try {
|
||||
await update${BusinessName}Sort(row.${pkColumn.javaField}, row.${sortField});
|
||||
modal.msgSuccess('排序更新成功');
|
||||
} catch (err) {
|
||||
await getList();
|
||||
}
|
||||
};
|
||||
</#if>
|
||||
|
||||
onMounted(() => {
|
||||
getList();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
export interface ${BusinessName}VO {
|
||||
<#list columns as column>
|
||||
<#if column.list>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
${column.javaField}: ${column.tsType};
|
||||
<#if column.htmlType == "imageUpload">
|
||||
/**
|
||||
* ${column.columnComment}Url
|
||||
*/
|
||||
${column.javaField}Url: string;
|
||||
</#if>
|
||||
</#if>
|
||||
</#list>
|
||||
<#if table.tree>
|
||||
/**
|
||||
* 子对象
|
||||
*/
|
||||
children: ${BusinessName}VO[];
|
||||
</#if>
|
||||
}
|
||||
|
||||
export interface ${BusinessName}Form extends BaseEntity {
|
||||
<#list columns as column>
|
||||
<#if column.insert || column.edit>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
${column.javaField}?: ${column.tsType};
|
||||
</#if>
|
||||
</#list>
|
||||
}
|
||||
|
||||
export interface ${BusinessName}Query<#if !table.tree> extends PageQuery</#if> {
|
||||
<#list columns as column>
|
||||
<#if column.query>
|
||||
/**
|
||||
* ${column.columnComment}
|
||||
*/
|
||||
${column.javaField}?: ${column.tsType};
|
||||
</#if>
|
||||
</#list>
|
||||
/**
|
||||
* 日期范围参数
|
||||
*/
|
||||
params?: any;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="${packageName}.mapper.${ClassName}Mapper">
|
||||
</mapper>
|
||||
10
ruoyi-modules/ruoyi-gen/src/main/resources/generator.yml
Normal file
10
ruoyi-modules/ruoyi-gen/src/main/resources/generator.yml
Normal file
@@ -0,0 +1,10 @@
|
||||
# 代码生成
|
||||
gen:
|
||||
# 作者
|
||||
author: Lion Li
|
||||
# 默认生成包路径 system 需改成自己的模块名称 如 system monitor tool
|
||||
packageName: org.dromara.system
|
||||
# 自动去除表前缀,默认是false
|
||||
autoRemovePre: false
|
||||
# 表前缀(生成类名不会包含表前缀,多个用逗号分隔)
|
||||
tablePrefix: sys_
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.gen.mapper.GenTableColumnMapper">
|
||||
|
||||
<resultMap type="org.dromara.gen.domain.GenTableColumn" id="GenTableColumnResult">
|
||||
</resultMap>
|
||||
|
||||
</mapper>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper
|
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="org.dromara.gen.mapper.GenTableMapper">
|
||||
<select id="selectTableNameList" resultType="java.lang.String">
|
||||
select table_name from gen_table where data_name = #{dataName,jdbcType=VARCHAR}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,3 @@
|
||||
java包使用 `.` 分割 resource 目录使用 `/` 分割
|
||||
<br>
|
||||
此文件目的 防止文件夹粘连找不到 `xml` 文件
|
||||
35
ruoyi-modules/ruoyi-job/pom.xml
Normal file
35
ruoyi-modules/ruoyi-job/pom.xml
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-modules</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>ruoyi-job</artifactId>
|
||||
|
||||
<description>
|
||||
任务调度
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- 通用工具-->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-json</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 调度模块 -->
|
||||
<dependency>
|
||||
<groupId>org.dromara</groupId>
|
||||
<artifactId>ruoyi-common-job</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.dromara.job.entity;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 账单数据传输对象。
|
||||
*
|
||||
* @param billId 账单 ID
|
||||
* @param billChannel 账单渠道
|
||||
* @param billDate 账单日期
|
||||
* @param billAmount 账单金额
|
||||
*/
|
||||
public record BillDTO(
|
||||
Long billId,
|
||||
String billChannel,
|
||||
String billDate,
|
||||
BigDecimal billAmount
|
||||
) {
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user