!102 少量 bug 快速 fix

Merge pull request !102 from 芋道源码/feature/1.6.1
This commit is contained in:
芋道源码 2022-03-19 18:33:20 +00:00 committed by Gitee
commit e923bc661d
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
64 changed files with 465 additions and 244 deletions

View File

@ -25,7 +25,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.6.0-snapshot</revision>
<revision>1.6.1-snapshot</revision>
<!-- Maven 相关 -->
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>

File diff suppressed because one or more lines are too long

View File

@ -14,7 +14,7 @@
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
<properties>
<revision>1.6.0-snapshot</revision>
<revision>1.6.1-snapshot</revision>
<!-- 统一依赖管理 -->
<spring.boot.version>2.5.10</spring.boot.version>
<!-- Web 相关 -->
@ -28,7 +28,7 @@
<dynamic-datasource.version>3.5.0</dynamic-datasource.version>
<redisson.version>3.16.6</redisson.version>
<!-- Config 配置中心相关 -->
<apollo.version>1.7.0</apollo.version>
<apollo.version>1.9.2</apollo.version>
<!-- Job 定时任务相关 -->
<!-- 服务保障相关 -->
<lock4j.version>2.2.0</lock4j.version>
@ -52,11 +52,12 @@
<velocity.version>2.2</velocity.version>
<screw.version>1.0.5</screw.version>
<guava.version>30.1.1-jre</guava.version>
<guice.version>5.1.0</guice.version>
<transmittable-thread-local.version>2.12.2</transmittable-thread-local.version>
<commons-net.version>3.8.0</commons-net.version>
<jsch.version>0.1.55</jsch.version>
<!-- 三方云服务相关 -->
<s3.version>2.17.147</s3.version>
<minio.version>8.2.2</minio.version>
<aliyun-java-sdk-core.version>4.5.25</aliyun-java-sdk-core.version>
<aliyun-java-sdk-dysmsapi.version>2.1.0</aliyun-java-sdk-dysmsapi.version>
<yunpian-java-sdk.version>1.2.7</yunpian-java-sdk.version>
@ -490,6 +491,12 @@
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>${guice.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId> <!-- 解决 ThreadLocal 父子线程的传值问题 -->
@ -514,9 +521,9 @@
<version>${revision}</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<version>${s3.version}</version>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>${minio.version}</version>
</dependency>
<!-- SMS SDK begin -->

View File

@ -63,8 +63,8 @@
<!-- 三方云服务相关 -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>s3</artifactId>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
</dependency>
<!-- Test 测试相关 -->

View File

@ -20,15 +20,17 @@ public interface FileClient {
* @param content 文件流
* @param path 相对路径
* @return 完整路径 HTTP 访问地址
* @throws Exception 上传文件时抛出 Exception 异常
*/
String upload(byte[] content, String path);
String upload(byte[] content, String path) throws Exception;
/**
* 删除文件
*
* @param path 相对路径
* @throws Exception 删除文件时抛出 Exception 异常
*/
void delete(String path);
void delete(String path) throws Exception;
/**
* 获得文件的内容
@ -36,6 +38,6 @@ public interface FileClient {
* @param path 相对路径
* @return 文件的内容
*/
byte[] getContent(String path);
byte[] getContent(String path) throws Exception;
}

View File

@ -1,19 +1,14 @@
package cn.iocoder.yudao.framework.file.core.client.s3;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.iocoder.yudao.framework.file.core.client.AbstractFileClient;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.DeleteObjectRequest;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import io.minio.*;
import java.net.URI;
import java.io.ByteArrayInputStream;
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_QINIU;
import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.ENDPOINT_ALIYUN;
/**
* 基于 S3 协议的文件客户端实现 MinIO阿里云腾讯云七牛云华为云等云服务
@ -24,7 +19,7 @@ import static cn.iocoder.yudao.framework.file.core.client.s3.S3FileClientConfig.
*/
public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
private S3Client client;
private MinioClient client;
public S3FileClient(Long id, S3FileClientConfig config) {
super(id, config);
@ -34,34 +29,27 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
protected void doInit() {
// 补全 domain
if (StrUtil.isEmpty(config.getDomain())) {
config.setDomain(createDomain());
config.setDomain(buildDomain());
}
// 初始化客户端
client = S3Client.builder()
.serviceConfiguration(sb -> sb.pathStyleAccessEnabled(false) // 关闭路径风格
.chunkedEncodingEnabled(false)) // 禁用 chunk
.endpointOverride(createURI()) // 上传地址
.region(Region.of(config.getRegion())) // Region
.credentialsProvider(StaticCredentialsProvider.create( // 认证密钥
AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret())))
.overrideConfiguration(cb -> cb.addExecutionInterceptor(new S3ModifyPathInterceptor(config.getBucket())))
client = MinioClient.builder()
.endpoint(buildEndpointURL()) // Endpoint URL
.region(buildRegion()) // Region
.credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥
.build();
}
/**
* 基于 endpoint 构建调用云服务的 URI 地址
* 基于 endpoint 构建调用云服务的 URL 地址
*
* @return URI 地址
*/
private URI createURI() {
String uri;
// 如果是七牛无需拼接 bucket
if (config.getEndpoint().contains(ENDPOINT_QINIU)) {
uri = StrUtil.format("https://{}", config.getEndpoint());
} else {
uri = StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
private String buildEndpointURL() {
// 如果已经是 http 或者 https则不进行拼接.主要适配 MinIO
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
return config.getEndpoint();
}
return URI.create(uri);
return StrUtil.format("https://{}", config.getEndpoint());
}
/**
@ -69,35 +57,56 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
*
* @return Domain 地址
*/
private String createDomain() {
private String buildDomain() {
// 如果已经是 http 或者 https则不进行拼接.主要适配 MinIO
if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {
return StrUtil.format("{}/{}", config.getEndpoint(), config.getBucket());
}
// 阿里云腾讯云华为云都适合七牛云比较特殊必须有自定义域名
return StrUtil.format("https://{}.{}", config.getBucket(), config.getEndpoint());
}
/**
* 基于 bucket 构建 region 地区
*
* @return region 地区
*/
private String buildRegion() {
// 阿里云必须有 region否则会报错
if (config.getEndpoint().contains(ENDPOINT_ALIYUN)) {
return StrUtil.subBefore(config.getEndpoint(), '.', false)
.replaceAll("-internal", ""); // 去除内网 Endpoint 的后缀
}
return null;
}
@Override
public String upload(byte[] content, String path) {
public String upload(byte[] content, String path) throws Exception {
// 执行上传
PutObjectRequest.Builder request = PutObjectRequest.builder()
client.putObject(PutObjectArgs.builder()
.bucket(config.getBucket()) // bucket 必须传递
.key(path); // 相对路径作为 key
client.putObject(request.build(), RequestBody.fromBytes(content));
.object(path) // 相对路径作为 key
.stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容
.build());
// 拼接返回路径
return config.getDomain() + "/" + path;
}
@Override
public void delete(String path) {
DeleteObjectRequest.Builder request = DeleteObjectRequest.builder()
public void delete(String path) throws Exception {
client.removeObject(RemoveObjectArgs.builder()
.bucket(config.getBucket()) // bucket 必须传递
.key(path); // 相对路径作为 key
client.deleteObject(request.build());
.object(path) // 相对路径作为 key
.build());
}
@Override
public byte[] getContent(String path) {
GetObjectRequest.Builder request = GetObjectRequest.builder()
public byte[] getContent(String path) throws Exception {
GetObjectResponse response = client.getObject(GetObjectArgs.builder()
.bucket(config.getBucket()) // bucket 必须传递
.key(path); // 相对路径作为 key
return client.getObjectAsBytes(request.build()).asByteArray();
.object(path) // 相对路径作为 key
.build());
return IoUtil.readBytes(response);
}
}

View File

@ -18,37 +18,28 @@ import javax.validation.constraints.NotNull;
public class S3FileClientConfig implements FileClientConfig {
public static final String ENDPOINT_QINIU = "qiniucs.com";
public static final String ENDPOINT_ALIYUN = "aliyuncs.com";
/**
* 节点地址
* 1. MinIO
* 1. MinIOhttps://www.iocoder.cn/Spring-Boot/MinIO 例如说http://127.0.0.1:9000
* 2. 阿里云https://help.aliyun.com/document_detail/31837.html
* 3. 腾讯云
* 3. 腾讯云https://cloud.tencent.com/document/product/436/6224
* 4. 七牛云https://developer.qiniu.com/kodo/4088/s3-access-domainname
* 5. 华为云
* 5. 华为云https://developer.huaweicloud.com/endpoint?OBS
*/
@NotNull(message = "endpoint 不能为空")
private String endpoint;
/**
* 自定义域名
* 1. MinIO
* 1. MinIO通过 Nginx 配置
* 2. 阿里云https://help.aliyun.com/document_detail/31836.html
* 3. 腾讯云https://cloud.tencent.com/document/product/436/11142
* 4. 七牛云https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name
* 5. 华为云
* 5. 华为云https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html
*/
@URL(message = "domain 必须是 URL 格式")
private String domain;
/**
* 区域
* 1. MinIO
* 2. 阿里云https://help.aliyun.com/document_detail/31837.html
* 3. 腾讯云
* 4. 七牛云https://developer.qiniu.com/kodo/4088/s3-access-domainname
* 5. 华为云
*/
@NotNull(message = "region 不能为空")
private String region;
/**
* 存储 Bucket
*/
@ -57,11 +48,11 @@ public class S3FileClientConfig implements FileClientConfig {
/**
* 访问 Key
* 1. MinIO
* 2. 阿里云
* 1. MinIOhttps://www.iocoder.cn/Spring-Boot/MinIO
* 2. 阿里云https://ram.console.aliyun.com/manage/ak
* 3. 腾讯云https://console.cloud.tencent.com/cam/capi
* 4. 七牛云https://portal.qiniu.com/user/key
* 5. 华为云
* 5. 华为云https://support.huaweicloud.com/qs-obs/obs_qs_0005.html
*/
@NotNull(message = "accessKey 不能为空")
private String accessKey;

View File

@ -1,36 +0,0 @@
package cn.iocoder.yudao.framework.file.core.client.s3;
import software.amazon.awssdk.core.interceptor.Context;
import software.amazon.awssdk.core.interceptor.ExecutionAttributes;
import software.amazon.awssdk.core.interceptor.ExecutionInterceptor;
import software.amazon.awssdk.http.SdkHttpRequest;
/**
* S3 修改路径的拦截器移除多余的 Bucket 前缀
* 如果不使用该拦截器希望上传的路径是 /tudou.jpg 会被添加成 /bucket/tudou.jpg
*
* @author 芋道源码
*/
public class S3ModifyPathInterceptor implements ExecutionInterceptor {
private final String bucket;
public S3ModifyPathInterceptor(String bucket) {
this.bucket = "/" + bucket;
}
@Override
public SdkHttpRequest modifyHttpRequest(Context.ModifyHttpRequest context, ExecutionAttributes executionAttributes) {
SdkHttpRequest request = context.httpRequest();
SdkHttpRequest.Builder rb = SdkHttpRequest.builder().protocol(request.protocol()).host(request.host()).port(request.port())
.method(request.method()).headers(request.headers()).rawQueryParameters(request.rawQueryParameters());
// 移除 path 前的 bucket 路径
if (request.encodedPath().startsWith(bucket)) {
rb.encodedPath(request.encodedPath().substring(bucket.length()));
} else {
rb.encodedPath(request.encodedPath());
}
return rb.build();
}
}

View File

@ -3,11 +3,13 @@ package cn.iocoder.yudao.framework.file.core.client.ftp;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.ftp.FtpMode;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class FtpFileClientTest {
@Test
@Disabled
public void test() {
// 创建客户端
FtpFileClientConfig config = new FtpFileClientConfig();

View File

@ -2,11 +2,13 @@ package cn.iocoder.yudao.framework.file.core.client.local;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class LocalFileClientTest {
@Test
@Disabled
public void test() {
// 创建客户端
LocalFileClientConfig config = new LocalFileClientConfig();

View File

@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.file.core.client.s3;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
@ -11,9 +10,25 @@ import javax.validation.Validation;
public class S3FileClientTest {
@Test
@Disabled // MinIO如果要集成测试可以注释本行
public void testMinIO() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
config.setAccessKey("admin");
config.setAccessSecret("password");
config.setBucket("yudaoyuanma");
config.setDomain(null);
// 默认 9000 endpoint
config.setEndpoint("http://127.0.0.1:9000");
// 执行上传
testExecuteUpload(config);
}
@Test
@Disabled // 阿里云 OSS如果要集成测试可以注释本行
public void testAliyun() {
public void testAliyun() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
config.setAccessKey(System.getenv("ALIYUN_ACCESS_KEY"));
@ -29,7 +44,7 @@ public class S3FileClientTest {
@Test
@Disabled // 腾讯云 COS如果要集成测试可以注释本行
public void testQCloud() {
public void testQCloud() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
config.setAccessKey(System.getenv("QCLOUD_ACCESS_KEY"));
@ -38,7 +53,6 @@ public class S3FileClientTest {
config.setDomain(null); // 如果有自定义域名则可以设置http://tengxun-oss.iocoder.cn
// 默认上海的 endpoint
config.setEndpoint("cos.ap-shanghai.myqcloud.com");
config.setRegion("ap-shanghai");
// 执行上传
testExecuteUpload(config);
@ -46,7 +60,7 @@ public class S3FileClientTest {
@Test
@Disabled // 七牛云存储如果要集成测试可以注释本行
public void testQiniu() {
public void testQiniu() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
// config.setAccessKey(System.getenv("QINIU_ACCESS_KEY"));
@ -62,11 +76,24 @@ public class S3FileClientTest {
testExecuteUpload(config);
}
private void testExecuteUpload(S3FileClientConfig config) {
// 补全配置
if (config.getRegion() == null) {
config.setRegion(StrUtil.subBefore(config.getEndpoint(), '.', false));
}
@Test
@Disabled // 华为云存储如果要集成测试可以注释本行
public void testHuaweiCloud() throws Exception {
S3FileClientConfig config = new S3FileClientConfig();
// 配置成你自己的
// config.setAccessKey(System.getenv("HUAWEI_CLOUD_ACCESS_KEY"));
// config.setAccessSecret(System.getenv("HUAWEI_CLOUD_SECRET_KEY"));
config.setBucket("yudao");
config.setDomain(null); // 如果有自定义域名则可以设置
// 默认上海的 endpoint
config.setEndpoint("obs.cn-east-3.myhuaweicloud.com");
// 执行上传
testExecuteUpload(config);
}
private void testExecuteUpload(S3FileClientConfig config) throws Exception {
// 校验配置
ValidationUtils.validate(Validation.buildDefaultValidatorFactory().getValidator(), config);
// 创建 Client
S3FileClient client = new S3FileClient(0L, config);
@ -77,9 +104,9 @@ public class S3FileClientTest {
String fullPath = client.upload(content, path);
System.out.println("访问地址:" + fullPath);
// 读取文件
if (false) {
if (true) {
byte[] bytes = client.getContent(path);
System.out.println("文件内容:" + bytes);
System.out.println("文件内容:" + bytes.length);
}
// 删除文件
if (false) {

View File

@ -2,11 +2,13 @@ package cn.iocoder.yudao.framework.file.core.client.sftp;
import cn.hutool.core.io.resource.ResourceUtil;
import cn.hutool.core.util.IdUtil;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
public class SftpFileClientTest {
@Test
@Disabled
public void test() {
// 创建客户端
SftpFileClientConfig config = new SftpFileClientConfig();

View File

@ -22,7 +22,6 @@ public interface BpmMessageService {
*/
void sendMessageWhenProcessInstanceApprove(@Valid BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO);
/**
* 发送流程实例被不通过的消息
*

View File

@ -5,7 +5,6 @@ import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
import cn.iocoder.yudao.framework.datapermission.core.dept.rule.DeptDataPermissionRule;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmTaskAssignRuleDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmUserGroupDO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmTaskAssignRuleTypeEnum;
@ -69,11 +68,13 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
public BpmUserTaskActivityBehavior(UserTask userTask) {
super(userTask);
}
public void setScripts(List<BpmTaskAssignScript> scripts) {
this.scriptMap = convertMap(scripts, script -> script.getEnum().getId());
}
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
protected void handleAssignments(TaskService taskService, String assignee, String owner, List<String> candidateUsers, List<String> candidateGroups, TaskEntity task, ExpressionManager expressionManager, DelegateExecution execution, ProcessEngineConfigurationImpl processEngineConfiguration) {
// 第一步获得任务的规则
BpmTaskAssignRuleDO rule = getTaskRule(task);
@ -98,7 +99,6 @@ public class BpmUserTaskActivityBehavior extends UserTaskActivityBehavior {
return taskRules.get(0);
}
@VisibleForTesting
Set<Long> calculateTaskCandidateUsers(TaskEntity task, BpmTaskAssignRuleDO rule) {
Set<Long> assigneeUserIds = null;
if (Objects.equals(BpmTaskAssignRuleTypeEnum.ROLE.getType(), rule.getType())) {

View File

@ -15,7 +15,6 @@ import java.util.Set;
@Component
public class BpmTaskAssignLeaderX2Script extends BpmTaskAssignLeaderAbstractScript {
@Override
@DataPermission(enable = false) // 不需要处理数据权限 不然会有问题查询不到数据
public Set<Long> calculateTaskCandidateUsers(TaskEntity task) {

View File

@ -15,7 +15,7 @@ public interface FileApi {
* @param content 文件内容
* @return 文件路径
*/
default String createFile(byte[] content) {
default String createFile(byte[] content) throws Exception {
return createFile(IdUtil.fastUUID(), content);
}
@ -26,6 +26,6 @@ public interface FileApi {
* @param content 文件内容
* @return 文件路径
*/
String createFile(String path, byte[] content);
String createFile(String path, byte[] content) throws Exception;
}

View File

@ -1,6 +1,5 @@
package cn.iocoder.yudao.module.infra.api.file;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.infra.service.file.FileService;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
@ -20,7 +19,7 @@ public class FileApiImpl implements FileApi {
private FileService fileService;
@Override
public String createFile(String path, byte[] content) {
public String createFile(String path, byte[] content) throws Exception {
return fileService.createFile(path, content);
}

View File

@ -82,7 +82,7 @@ public class FileConfigController {
@GetMapping("/test")
@ApiOperation("测试文件配置是否正确")
@PreAuthorize("@ss.hasPermission('infra:file-config:query')")
public CommonResult<String> testFileConfig(@RequestParam("id") Long id) {
public CommonResult<String> testFileConfig(@RequestParam("id") Long id) throws Exception {
String url = fileConfigService.testFileConfig(id);
return success(url);
}

View File

@ -23,7 +23,6 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -44,7 +43,7 @@ public class FileController {
@ApiImplicitParam(name = "path", value = "文件路径", example = "yudaoyuanma.png", dataTypeClass = String.class)
})
public CommonResult<String> uploadFile(@RequestParam("file") MultipartFile file,
@RequestParam("path") String path) throws IOException {
@RequestParam("path") String path) throws Exception {
return success(fileService.createFile(path, IoUtil.readBytes(file.getInputStream())));
}
@ -52,7 +51,7 @@ public class FileController {
@ApiOperation("删除文件")
@ApiImplicitParam(name = "id", value = "编号", required = true, dataTypeClass = Long.class)
@PreAuthorize("@ss.hasPermission('infra:file:delete')")
public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) {
public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
fileService.deleteFile(id);
return success(true);
}
@ -65,7 +64,7 @@ public class FileController {
})
public void getFileContent(HttpServletResponse response,
@PathVariable("configId") Long configId,
@PathVariable("path") String path) throws IOException {
@PathVariable("path") String path) throws Exception {
byte[] content = fileService.getFileContent(configId, path);
if (content == null) {
log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);

View File

@ -82,7 +82,7 @@ public class CodegenServiceImpl implements CodegenService {
table.setAuthor(userApi.getUser(userId).getNickname());
codegenTableMapper.insert(table);
// 构建 CodegenColumnDO 数组插入到 DB
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(table.getId(), schemaColumns);
codegenColumnMapper.insertBatch(columns);
return table.getId();
}
@ -196,7 +196,7 @@ public class CodegenServiceImpl implements CodegenService {
}
// 插入新增的字段
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(schemaColumns);
List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, schemaColumns);
codegenColumnMapper.insertBatch(columns);
// 删除不存在的字段
if (CollUtil.isNotEmpty(deleteColumnIds)) {

View File

@ -133,9 +133,12 @@ public class CodegenBuilder {
table.setTemplateType(CodegenTemplateTypeEnum.CRUD.getType());
}
public List<CodegenColumnDO> buildColumns(List<SchemaColumnDO> schemaColumns) {
public List<CodegenColumnDO> buildColumns(Long tableId, List<SchemaColumnDO> schemaColumns) {
List<CodegenColumnDO> columns = CodegenConvert.INSTANCE.convertList(schemaColumns);
columns.forEach(this::initColumnDefault);
for (CodegenColumnDO column : columns) {
column.setTableId(tableId);
initColumnDefault(column);
}
return columns;
}

View File

@ -82,7 +82,7 @@ public interface FileConfigService {
* @param id 编号
* @return 文件 URL
*/
String testFileConfig(Long id);
String testFileConfig(Long id) throws Exception;
/**
* 获得指定编号的文件客户端

View File

@ -225,7 +225,7 @@ public class FileConfigServiceImpl implements FileConfigService {
}
@Override
public String testFileConfig(Long id) {
public String testFileConfig(Long id) throws Exception {
// 校验存在
this.validateFileConfigExists(id);
// 上传文件

View File

@ -26,14 +26,14 @@ public interface FileService {
* @param content 文件内容
* @return 文件路径
*/
String createFile(String path, byte[] content);
String createFile(String path, byte[] content) throws Exception;
/**
* 删除文件
*
* @param id 编号
*/
void deleteFile(Long id);
void deleteFile(Long id) throws Exception;
/**
* 获得文件内容
@ -42,6 +42,6 @@ public interface FileService {
* @param path 文件路径
* @return 文件内容
*/
byte[] getFileContent(Long configId, String path);
byte[] getFileContent(Long configId, String path) throws Exception;
}

View File

@ -35,7 +35,7 @@ public class FileServiceImpl implements FileService {
}
@Override
public String createFile(String path, byte[] content) {
public String createFile(String path, byte[] content) throws Exception {
// 上传到文件存储器
FileClient client = fileConfigService.getMasterFileClient();
Assert.notNull(client, "客户端(master) 不能为空");
@ -53,7 +53,7 @@ public class FileServiceImpl implements FileService {
}
@Override
public void deleteFile(Long id) {
public void deleteFile(Long id) throws Exception {
// 校验存在
FileDO file = this.validateFileExists(id);
@ -75,7 +75,7 @@ public class FileServiceImpl implements FileService {
}
@Override
public byte[] getFileContent(Long configId, String path) {
public byte[] getFileContent(Long configId, String path) throws Exception {
FileClient client = fileConfigService.getFileClient(configId);
Assert.notNull(client, "客户端({}) 不能为空", configId);
return client.getContent(path);

View File

@ -228,7 +228,7 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
}
@Test
public void testFileConfig() {
public void testFileConfig() throws Exception {
// mock 数据
FileConfigDO dbFileConfig = randomFileConfigDO().setMaster(false);
fileConfigMapper.insert(dbFileConfig);// @Sql: 先插入出一条存在的数据

View File

@ -70,7 +70,7 @@ public class FileServiceTest extends BaseDbUnitTest {
}
@Test
public void testCreateFile_success() {
public void testCreateFile_success() throws Exception {
// 准备参数
String path = randomString();
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
@ -95,7 +95,7 @@ public class FileServiceTest extends BaseDbUnitTest {
}
@Test
public void testDeleteFile_success() {
public void testDeleteFile_success() throws Exception {
// mock 数据
FileDO dbFile = randomPojo(FileDO.class, o -> o.setConfigId(10L).setPath("tudou.jpg"));
fileMapper.insert(dbFile);// @Sql: 先插入出一条存在的数据
@ -123,7 +123,7 @@ public class FileServiceTest extends BaseDbUnitTest {
}
@Test
public void testGetFileContent() {
public void testGetFileContent() throws Exception {
// 准备参数
Long configId = 10L;
String path = "tudou.jpg";

View File

@ -16,9 +16,8 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.*;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;
@ -44,7 +43,7 @@ public class AppUserController {
@PutMapping("/update-avatar")
@ApiOperation("修改用户头像")
@PreAuthenticated
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws IOException {
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws Exception {
if (file.isEmpty()) {
throw exception(FILE_IS_EMPTY);
}

View File

@ -60,7 +60,7 @@ public interface MemberUserService {
* @param inputStream 头像文件
* @return 头像url
*/
String updateUserAvatar(Long userId, InputStream inputStream);
String updateUserAvatar(Long userId, InputStream inputStream) throws Exception;
/**
* 修改手机

View File

@ -100,7 +100,7 @@ public class MemberUserServiceImpl implements MemberUserService {
}
@Override
public String updateUserAvatar(Long userId, InputStream avatarFile) {
public String updateUserAvatar(Long userId, InputStream avatarFile) throws Exception {
this.checkUserExists(userId);
// 创建文件
String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));

View File

@ -74,7 +74,7 @@ public class MemberUserServiceImplTest extends BaseDbAndRedisUnitTest {
}
@Test
public void testUpdateAvatar_success(){
public void testUpdateAvatar_success() throws Exception {
// mock 数据
MemberUserDO dbUser = randomUserDO();
userMapper.insert(dbUser);

View File

@ -29,7 +29,6 @@ import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.io.IOException;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@ -99,7 +98,7 @@ public class UserProfileController {
@PutMapping("/update-avatar")
@ApiOperation("上传用户个人头像")
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws IOException {
public CommonResult<String> updateUserAvatar(@RequestParam("avatarFile") MultipartFile file) throws Exception {
if (file.isEmpty()) {
throw ServiceExceptionUtil.exception(FILE_IS_EMPTY);
}

View File

@ -1,12 +1,12 @@
package cn.iocoder.yudao.module.system.dal.mysql.permission;
import cn.iocoder.yudao.framework.mybatis.core.enums.SqlConstants;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.util.Date;
import java.util.List;
@ -28,9 +28,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
}
default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
return selectOne(new LambdaQueryWrapper<MenuDO>().select(MenuDO::getId)
.gt(MenuDO::getUpdateTime, maxUpdateTime).last(SqlConstants.LIMIT1)) != null;
}
@Select("SELECT id FROM system_menu WHERE update_time > #{maxUpdateTime} LIMIT 1")
MenuDO selectExistsByUpdateTimeAfter(Date maxUpdateTime);
}

View File

@ -7,9 +7,8 @@ import cn.iocoder.yudao.framework.mybatis.core.query.QueryWrapperX;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.springframework.lang.Nullable;
import java.util.Collection;
@ -45,10 +44,7 @@ public interface RoleMapper extends BaseMapperX<RoleDO> {
return selectList(new LambdaQueryWrapperX<RoleDO>().inIfPresent(RoleDO::getStatus, statuses));
}
@InterceptorIgnore(tenantLine = "true") // 该方法忽略多租户原因该方法被异步 task 调用此时获取不到租户编号
default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) {
return selectOne(new QueryWrapper<RoleDO>().select("id")
.gt("update_time", maxUpdateTime).last("LIMIT 1")) != null;
}
@Select("SELECT id FROM system_role WHERE update_time > #{maxUpdateTime} LIMIT 1")
RoleDO selectExistsByUpdateTimeAfter(Date maxUpdateTime);
}

View File

@ -120,7 +120,7 @@ public class MenuServiceImpl implements MenuService {
if (maxUpdateTime == null) { // 如果更新时间为空说明 DB 一定有新数据
log.info("[loadMenuIfUpdate][首次加载全量菜单]");
} else { // 判断数据库中是否有更新的菜单
if (!menuMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
if (menuMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
return null;
}
log.info("[loadMenuIfUpdate][增量加载全量菜单]");

View File

@ -116,7 +116,7 @@ public class RoleServiceImpl implements RoleService {
if (maxUpdateTime == null) { // 如果更新时间为空说明 DB 一定有新数据
log.info("[loadRoleIfUpdate][首次加载全量角色]");
} else { // 判断数据库中是否有更新的角色
if (!roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) {
if (roleMapper.selectExistsByUpdateTimeAfter(maxUpdateTime) == null) {
return null;
}
log.info("[loadRoleIfUpdate][增量加载全量角色]");

View File

@ -64,7 +64,7 @@ public interface AdminUserService {
* @param id 用户 id
* @param avatarFile 头像文件
*/
String updateUserAvatar(Long id, InputStream avatarFile);
String updateUserAvatar(Long id, InputStream avatarFile) throws Exception;
/**
* 修改密码

View File

@ -63,6 +63,7 @@ public class AdminUserServiceImpl implements AdminUserService {
private FileApi fileApi;
@Override
public Long createUser(UserCreateReqVO reqVO) {
// 校验账户配合
tenantService.handleTenantInfo(tenant -> {
@ -118,7 +119,7 @@ public class AdminUserServiceImpl implements AdminUserService {
}
@Override
public String updateUserAvatar(Long id, InputStream avatarFile) {
public String updateUserAvatar(Long id, InputStream avatarFile) throws Exception {
this.checkUserExists(id);
// 存储文件
String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));

View File

@ -196,7 +196,7 @@ public class UserServiceImplTest extends BaseDbUnitTest {
}
@Test
public void testUpdateUserAvatar_success() {
public void testUpdateUserAvatar_success() throws Exception {
// mock 数据
AdminUserDO dbUser = randomAdminUserDO();
userMapper.insert(dbUser);

View File

@ -13,3 +13,9 @@ VUE_APP_BASE_API = 'http://127.0.0.1:48080'
PUBLIC_PATH = '/admin-ui/'
# 二级部署路径
VUE_APP_APP_NAME ='/admin-ui/'
# 多租户的开关
VUE_APP_TENANT_ENABLE = true
# 文档的开关
VUE_APP_DOC_ENABLE = true

View File

@ -13,3 +13,6 @@ VUE_CLI_BABEL_TRANSPILE_MODULES = true
# 多租户的开关
VUE_APP_TENANT_ENABLE = true
# 文档的开关
VUE_APP_DOC_ENABLE = true

View File

@ -12,3 +12,8 @@ PUBLIC_PATH = 'http://my-pi.com:8888/yudao-admin/'
# 二级部署路径
VUE_APP_APP_NAME ='yudao-admin'
# 多租户的开关
VUE_APP_TENANT_ENABLE = true
# 文档的开关
VUE_APP_DOC_ENABLE = false

View File

@ -11,3 +11,9 @@ VUE_APP_BASE_API = 'http://api-dashboard.yudao.iocoder.cn'
# 静态资源地址
PUBLIC_PATH = 'http://static.yudao.iocoder.cn/'
# 多租户的开关
VUE_APP_TENANT_ENABLE = true
# 文档的开关
VUE_APP_DOC_ENABLE = false

View File

@ -1,6 +1,6 @@
{
"name": "yudao-ui-admin",
"version": "1.6.0-snapshot",
"version": "1.6.1-snapshot",
"description": "芋道管理系统",
"author": "芋道",
"license": "MIT",

View File

@ -0,0 +1,25 @@
<template>
<el-alert v-if="enable()" :title="'【' + title + '】文档地址:' + url" type="success" show-icon />
</template>
<script>
import {getDocEnable} from "@/utils/ruoyi";
export default {
name: "DocAlert",
props: {
title: String,
url: String,
},
methods: {
enable: function () {
return getDocEnable();
}
}
};
</script>
<style scoped>
.el-alert--success.is-light {
margin-bottom: 10px;
}
</style>

View File

@ -41,10 +41,12 @@ Vue.prototype.handleTree = handleTree
// 全局组件挂载
Vue.component('DictTag', DictTag)
Vue.component('DocAlert', DocAlert)
Vue.component('Pagination', Pagination)
Vue.component('RightToolbar', RightToolbar)
// 字典标签组件
import DictTag from '@/components/DictTag'
import DocAlert from '@/components/DocAlert'
// 头部标签插件
import VueMeta from 'vue-meta'

View File

@ -175,7 +175,7 @@ export function getNowDateTime(timeStr) {
* 获得租户功能是否开启
*/
export function getTenantEnable() {
console.log("enable: " + process.env.VUE_APP_TENANT_ENABLE)
// console.log("enable: " + process.env.VUE_APP_TENANT_ENABLE)
if (process.env.VUE_APP_TENANT_ENABLE === "true") {
return true;
}
@ -184,3 +184,16 @@ export function getTenantEnable() {
}
return process.env.VUE_APP_TENANT_ENABLE || true;
}
/**
* 获得文档是否开启
*/
export function getDocEnable() {
if (process.env.VUE_APP_DOC_ENABLE === "true") {
return true;
}
if (process.env.VUE_APP_DOC_ENABLE === "false") {
return false;
}
return process.env.VUE_APP_DOC_ENABLE || false;
}

View File

@ -1,5 +1,7 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 列表 -->
<el-table v-loading="loading" :data="list">
<el-table-column label="定义编号" align="center" prop="id" width="400" />

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
@ -24,7 +25,7 @@
<el-table-column label="流程发起人" align="center" prop="processInstance.startUserNickname" width="120" />
<el-table-column label="结果" align="center" prop="result">
<template slot-scope="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.result"/>
<dict-tag :type="DICT_TYPE.BPM_PROCESS_INSTANCE_RESULT" :value="scope.row.result"/>
</template>
</el-table-column>
<el-table-column label="审批意见" align="center" prop="comment" width="200" />

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="工作流" url="https://doc.iocoder.cn/bpm" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">

View File

@ -44,9 +44,9 @@
<div class="center-board">
<div class="action-bar">
<el-button icon="el-icon-video-play" type="text" @click="run">
运行
</el-button>
<!-- <el-button icon="el-icon-video-play" type="text" @click="run">-->
<!-- 运行-->
<!-- </el-button>-->
<el-button icon="el-icon-view" type="text" @click="showJson">
查看json
</el-button>

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="代码生成" url="https://doc.iocoder.cn/new-feature/" />
<!-- 操作工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="表名称" prop="tableName">

View File

@ -1,16 +1,11 @@
<template>
<div class="app-container">
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="文件路径" prop="path">
<el-input v-model="queryParams.path" placeholder="请输入文件路径" clearable size="small" @keyup.enter.native="handleQuery"/>
</el-form-item>
<el-form-item label="文件类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择文件类型" clearable size="small">
<el-option label="请选择字典生成" value="" />
</el-select>
</el-form-item>
<el-form-item label="创建时间">
<el-date-picker v-model="dateRangeCreateTime" size="small" style="width: 240px" value-format="yyyy-MM-dd"
type="daterange" range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" />

View File

@ -1,6 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="上传下载" url="https://doc.iocoder.cn/file/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="配置名" prop="name">
@ -51,7 +51,7 @@
<span>{{ parseTime(scope.row.createTime) }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="240">
<template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['infra:file-config:update']">修改</el-button>
@ -109,9 +109,6 @@
<el-form-item v-if="form.storage === 20" label="节点地址" prop="config.endpoint">
<el-input v-model="form.config.endpoint" placeholder="请输入节点地址" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="区域" prop="config.region">
<el-input v-model="form.config.region" placeholder="请输入区域" />
</el-form-item>
<el-form-item v-if="form.storage === 20" label="存储 bucket" prop="config.bucket">
<el-input v-model="form.config.bucket" placeholder="请输入 bucket" />
</el-form-item>
@ -190,7 +187,6 @@ export default {
password: [{ required: true, message: "密码不能为空", trigger: "blur" }],
mode: [{ required: true, message: "连接模式不能为空", trigger: "change" }],
endpoint: [{ required: true, message: "节点地址不能为空", trigger: "blur" }],
region: [{ required: true, message: "区域名不能为空", trigger: "blur" }],
bucket: [{ required: true, message: "存储 bucket 不能为空", trigger: "blur" }],
accessKey: [{ required: true, message: "accessKey 不能为空", trigger: "blur" }],
accessSecret: [{ required: true, message: "accessSecret 不能为空", trigger: "blur" }],

View File

@ -87,7 +87,7 @@ export default {
// debugger
getTenantIdByName(value).then(res => {
const tenantId = res.data;
if (tenantId >= 0) {
if (tenantId && tenantId >= 0) {
//
Cookies.set("tenantId", tenantId);
callback();

View File

@ -1,5 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch">
<el-form-item label="菜单名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入菜单名称" clearable size="small" @keyup.enter.native="handleQuery"/>

View File

@ -1,5 +1,7 @@
<template>
<div class="app-container">
<doc-alert title="功能权限" url="https://doc.iocoder.cn/resource-permission" />
<doc-alert title="数据权限" url="https://doc.iocoder.cn/data-permission" />
<el-form :model="queryParams" ref="queryForm" v-show="showSearch" :inline="true">
<el-form-item label="角色名称" prop="name">
<el-input v-model="queryParams.name" placeholder="请输入角色名称" clearable size="small" style="width: 240px"

View File

@ -1,6 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="SaaS 多租户" url="https://doc.iocoder.cn/saas-tenant/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="租户名" prop="name">

View File

@ -1,6 +1,6 @@
<template>
<div class="app-container">
<doc-alert title="SaaS 多租户" url="https://doc.iocoder.cn/saas-tenant/" />
<!-- 搜索工作栏 -->
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
<el-form-item label="套餐名" prop="name">