This commit is contained in:
limengnan 2025-06-17 10:36:20 +08:00
commit 5a3623f82d
34 changed files with 1383 additions and 406 deletions

30
backend/.env.example Normal file
View File

@ -0,0 +1,30 @@
# 数据库配置
DB_URL=jdbc:mysql://localhost:3306/stdproject?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
DB_USERNAME=root
DB_PASSWORD=your_password_here
# 表前缀(可选)
DB_TABLE_PREFIX=
# CORS配置
CORS_ALLOWED_ORIGINS=http://localhost:3000,http://localhost:8080
CORS_MAX_AGE=3600
# JWT配置
JWT_ENABLED=false
JWT_SECRET=YourJWTSecretKeyForStdProjectBackendApplicationWhichIsVeryLongAndSecure2024!@#$%^&*()
JWT_EXPIRATION=1800000
JWT_REFRESH_EXPIRATION=604800000
# 日志配置
LOG_LEVEL_ROOT=INFO
LOG_LEVEL_APP=DEBUG
LOG_LEVEL_SECURITY=WARN
LOG_LEVEL_SQL=WARN
LOG_LEVEL_SQL_PARAMS=WARN
# MyBatis日志配置
MYBATIS_LOG_IMPL=org.apache.ibatis.logging.nologging.NoLoggingImpl
# Spring配置
SPRING_PROFILES_ACTIVE=dev

Binary file not shown.

View File

@ -21,8 +21,9 @@
<mybatis-spring.version>3.0.3</mybatis-spring.version>
<mybatis-plus.version>3.5.6</mybatis-plus.version>
<jjwt.version>0.11.5</jjwt.version>
<springdoc.version>2.0.2</springdoc.version>
<calcite-core.version>1.35.18</calcite-core.version>
<springdoc.version>2.3.0</springdoc.version>
<calcite-core.version>1.32.0</calcite-core.version>
<guava.version>33.0.0-jre</guava.version>
</properties>
<dependencies>
<dependency>
@ -57,6 +58,13 @@
<version>2.0.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/sdk-bundle-2.0.jar</systemPath>
</dependency>
<dependency>
<groupId>org.apache.calcite</groupId>
<artifactId>calcite-core</artifactId>
<version>${calcite-core.version}</version> <!-- 请根据需要选择适当的版本 -->
</dependency>
<dependency>
@ -64,6 +72,7 @@
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
@ -76,6 +85,7 @@
<version>8.0.30</version> <!-- Ensure this version is compatible with MySQL 8.0 -->
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
@ -107,27 +117,31 @@
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.34</version> <!-- 推荐使用最新稳定版本 -->
<scope>provided</scope>
</dependency>
<!-- 胡图工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.8</version>
</dependency>
</dependencies>
<build>
@ -147,4 +161,4 @@
</plugins>
</build>
</project>
</project>

View File

@ -0,0 +1,12 @@
package com.stdproject.common;
import lombok.Data;
@Data
public class FilterCondition {
private String field;
private String operator; // eq, ne, gt, ge, lt, le
private Object value;
}

View File

@ -1,5 +1,8 @@
package com.stdproject.common;
import cn.hutool.http.useragent.Browser;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.stdproject.entity.AppOptLog;
import com.stdproject.service.IAppOptLogService;
@ -83,14 +86,14 @@ public class OperationLogAspect {
// 创建操作日志对象
AppOptLog optLog = new AppOptLog();
// 设置基本信息
optLog.setOpttype(operationLog.type());
optLog.setModule(operationLog.module());
optLog.setDescription(operationLog.description());
optLog.setMethod(request.getMethod() + " " + request.getRequestURI());
optLog.setRequestip(getIpAddress(request));
optLog.setBrowser(request.getHeader("User-Agent"));
optLog.setBrowser(getBrowser(request));
optLog.setLogtime(LocalDateTime.now());
// 设置操作用户
@ -109,7 +112,7 @@ public class OperationLogAspect {
if (operationLog.recordParams()) {
String methodParams = getMethodParams(joinPoint);
optLog.setParams(methodParams);
}
}
// 保存日志
appOptLogService.save(optLog);
@ -147,6 +150,12 @@ public class OperationLogAspect {
}
}
public static String getBrowser(HttpServletRequest request) {
UserAgent userAgent = UserAgentUtil.parse(request.getHeader("User-Agent"));
Browser browser = userAgent.getBrowser();
return browser.getName();
}
/**
* 获取客户端IP地址
*/
@ -169,4 +178,4 @@ public class OperationLogAspect {
}
return ip;
}
}
}

View File

@ -4,7 +4,7 @@ import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
/**
* 分页查询请求类
@ -43,6 +43,11 @@ public class PageRequest {
*/
private String keyword;
/**
* 条件过滤器
*/
private List<FilterCondition> filters;
/**
* 获取偏移量
*
@ -69,4 +74,4 @@ public class PageRequest {
public boolean isDesc() {
return "desc".equalsIgnoreCase(orderDirection);
}
}
}

View File

@ -0,0 +1,92 @@
package com.stdproject.common;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.util.StringUtils;
import java.util.Collection;
import java.util.Set;
/**
* @Date: 2025/6/5 9:21
* @Description:
*/
public class QueryWrapperBuilder {
public static <T> QueryWrapper<T> buildQueryWrapper(PageRequest pageRequest) {
QueryWrapper<T> queryWrapper = new QueryWrapper<>();
// 过滤条件
if (pageRequest.getFilters() != null && !pageRequest.getFilters().isEmpty()) {
for (FilterCondition condition : pageRequest.getFilters()) {
String field = condition.getField();
String op = condition.getOperator();
Object val = condition.getValue();
if (!StringUtils.hasText(field) || ObjectUtil.isEmpty(val)) continue;
switch (op.toLowerCase()) {
case "eq":
queryWrapper.eq(field, val);
break;
case "like":
queryWrapper.like(field, val);
break;
case "left_like":
queryWrapper.likeLeft(field, val);
break;
case "right_like":
queryWrapper.likeRight(field, val);
break;
case "ne":
queryWrapper.ne(field, val);
break;
case "gt":
queryWrapper.gt(field, val);
break;
case "ge":
queryWrapper.ge(field, val);
break;
case "lt":
queryWrapper.lt(field, val);
break;
case "le":
queryWrapper.le(field, val);
break;
case "in":
if (val instanceof Collection) {
queryWrapper.in(field, (Collection<?>) val);
}
break;
case "notin":
if (val instanceof Collection) {
queryWrapper.notIn(field, (Collection<?>) val);
}
break;
default:
// 忽略不支持的操作符
break;
}
}
}
// 排序字段白名单校验
// String orderBy = pageRequest.getOrderBy();
// if (StringUtils.hasText(orderBy) && isAllowedOrderField(orderBy)) {
// if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
// queryWrapper.orderByAsc(orderBy);
// } else {
// queryWrapper.orderByDesc(orderBy);
// }
// }
return queryWrapper;
}
private static final Set<String> ALLOWED_ORDER_FIELDS = Set.of(
"username", "nickname", "email", "phone", "lastmodifydate", "age"
);
private static boolean isAllowedOrderField(String field) {
return ALLOWED_ORDER_FIELDS.contains(field);
}
}

View File

@ -34,21 +34,29 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private WebConfig webConfig;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
String jwt = getJwtFromRequest(request);
// 拦截器中校验
if (StringUtils.hasText(jwt) && webConfig.timedCache().get(jwt) != null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Token 已失效");
return;
}
if (StringUtils.hasText(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) {
String username = jwtUtils.getUsernameFromToken(jwt);
if (StringUtils.hasText(username)) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtUtils.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken authentication =
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
@ -58,7 +66,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
} catch (Exception e) {
log.error("无法设置用户认证: {}", e.getMessage());
}
filterChain.doFilter(request, response);
}

View File

@ -0,0 +1,25 @@
package com.stdproject.config;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
/**
* 国际化配置类
* 解决 io.gisbi.i18n.Translator.messageSource null 的问题
*/
@Configuration
public class MessageSourceConfig {
@Bean
public MessageSource messageSource() {
ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
messageSource.setBasenames("messages", "i18n/messages");
messageSource.setDefaultEncoding("UTF-8");
messageSource.setUseCodeAsDefaultMessage(true);
messageSource.setCacheSeconds(3600);
return messageSource;
}
}

View File

@ -52,7 +52,7 @@ public class SecurityConfig {
// 公开路径配置
private static final String[] PUBLIC_PATHS = {
"/auth/**",
"/api/auth/**",
"/api/public/**",
"/swagger-ui/**",
"/v3/api-docs/**",
@ -133,19 +133,18 @@ public class SecurityConfig {
// 异常处理配置
.exceptionHandling(ex -> ex
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
)
// 安全头配置
.headers(headers -> headers
// 禁用iframe嵌入,防止点击劫持攻击
.frameOptions().deny()
// 设置X-Content-Type-Options头为nosniff,防止MIME类型嗅探攻击
.contentTypeOptions().and()
// 设置Referrer Policy为strict-origin-when-cross-origin
// 跨域请求时只发送源(origin),同源请求发送完整referrer
.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
// 注意已移除HSTS配置不再强制使用HTTPS
);
// 安全头配置
// .headers(headers -> headers
// // 禁用iframe嵌入,防止点击劫持攻击
// .frameOptions().deny()
// // 设置X-Content-Type-Options头为nosniff,防止MIME类型嗅探攻击
// .contentTypeOptions()
// // 设置Referrer Policy为strict-origin-when-cross-origin
// // 跨域请求时只发送源(origin),同源请求发送完整referrer
// .referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
// // 注意已移除HSTS配置不再强制使用HTTPS
// )
// 根据配置决定是否启用JWT认证
if (jwtEnabled) {

View File

@ -1,5 +1,9 @@
package com.stdproject.config;
import cn.hutool.cache.Cache;
import cn.hutool.cache.CacheUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@ -12,6 +16,18 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${spring.security.jwt.expiration-ms}")
private Long jwtExpirationMs;
/**
* 用户缓存
*/
@Bean
public Cache<String, Object> timedCache() {
// 创建定时缓存缓存时间与JWT过期时间一致
return CacheUtil.newTimedCache(jwtExpirationMs);
}
/**
* 静态资源处理
*/
@ -20,9 +36,9 @@ public class WebConfig implements WebMvcConfigurer {
// Swagger UI 静态资源
registry.addResourceHandler("/swagger-ui/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/swagger-ui/");
// 其他静态资源
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
}
}
}

View File

@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
/**
* <p>
@ -41,7 +42,7 @@ public class AppDictionaryController {
public Result<IPage<AppDictionary>> page(@RequestBody @Valid PageRequest pageRequest) {
Page<AppDictionary> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
// 关键字搜索
if (StringUtils.hasText(pageRequest.getKeyword())) {
queryWrapper.and(wrapper -> wrapper
@ -50,7 +51,7 @@ public class AppDictionaryController {
.or().like("dictdata", pageRequest.getKeyword())
);
}
// 排序
if (StringUtils.hasText(pageRequest.getOrderBy())) {
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
@ -61,7 +62,7 @@ public class AppDictionaryController {
} else {
queryWrapper.orderByAsc("dictcode", "orderno");
}
IPage<AppDictionary> result = appDictionaryService.page(page, queryWrapper);
return Result.success(result);
}
@ -72,7 +73,7 @@ public class AppDictionaryController {
public Result<List<AppDictionary>> list() {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("dictcode", "orderno");
List<AppDictionary> dictionaries = appDictionaryService.list(queryWrapper);
return Result.success(dictionaries);
}
@ -92,7 +93,7 @@ public class AppDictionaryController {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dictcode", dictCode);
queryWrapper.orderByAsc("orderno");
List<AppDictionary> dictionaries = appDictionaryService.list(queryWrapper);
return Result.success(dictionaries);
}
@ -106,7 +107,7 @@ public class AppDictionaryController {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dictcode", dictCode);
queryWrapper.eq("dictdata", dictData);
AppDictionary dictionary = appDictionaryService.getOne(queryWrapper);
return Result.success(dictionary);
}
@ -123,18 +124,18 @@ public class AppDictionaryController {
if (existDictionary != null) {
return Result.error("该字典编码下的字典值已存在");
}
// 设置默认值
if (appDictionary.getOrderno() == null) {
// 获取同一字典编码下的最大序号
QueryWrapper<AppDictionary> orderQuery = new QueryWrapper<>();
orderQuery.eq("dictcode", appDictionary.getDictcode());
orderQuery.eq("app_id", appDictionary.getAppId());
orderQuery.orderByDesc("orderno");
orderQuery.last("LIMIT 1");
AppDictionary lastDict = appDictionaryService.getOne(orderQuery);
appDictionary.setOrderno(lastDict != null ? lastDict.getOrderno() + 1 : 1);
}
appDictionary.setLastmodifydate(LocalDateTime.now());
boolean success = appDictionaryService.save(appDictionary);
return success ? Result.success("新增成功") : Result.error("新增失败");
@ -149,7 +150,7 @@ public class AppDictionaryController {
if (existDictionary == null) {
return Result.error("数据字典不存在");
}
// 如果修改了字典编码或字典值检查新的组合是否已被其他字典项使用
if (!existDictionary.getDictcode().equals(appDictionary.getDictcode()) ||
!existDictionary.getDictdata().equals(appDictionary.getDictdata())) {
@ -162,7 +163,7 @@ public class AppDictionaryController {
return Result.error("该字典编码下的字典数据已被其他字典项使用");
}
}
appDictionary.setLastmodifydate(LocalDateTime.now());
boolean success = appDictionaryService.updateById(appDictionary);
return success ? Result.success("修改成功") : Result.error("修改失败");
@ -190,7 +191,7 @@ public class AppDictionaryController {
public Result<String> deleteByDictCode(@Parameter(description = "字典编码") @PathVariable String dictCode) {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dictcode", dictCode);
boolean success = appDictionaryService.remove(queryWrapper);
return success ? Result.success("删除成功") : Result.error("删除失败");
}
@ -202,7 +203,7 @@ public class AppDictionaryController {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("dictname", dictName);
AppDictionary dictionary = appDictionaryService.getOne(queryWrapper);
if (dictionary != null) {
return Result.success(dictionary.getDictdata());
}
@ -216,13 +217,13 @@ public class AppDictionaryController {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.select("DISTINCT dictcode");
queryWrapper.orderByAsc("dictcode");
List<AppDictionary> dictionaries = appDictionaryService.list(queryWrapper);
List<String> dictCodes = dictionaries.stream()
.map(AppDictionary::getDictcode)
.distinct()
.collect(java.util.stream.Collectors.toList());
return Result.success(dictCodes);
}
@ -244,14 +245,14 @@ public class AppDictionaryController {
@Operation(summary = "根据字典编码分组查询字典项")
@GetMapping("/grouped")
@OperationLog(type = "06", module = "数据字典管理", description = "根据字典编码分组查询字典项")
public Result<java.util.Map<String, List<AppDictionary>>> getGroupedDictionaries() {
public Result<Map<String, List<AppDictionary>>> getGroupedDictionaries() {
QueryWrapper<AppDictionary> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("dictcode", "orderno");
List<AppDictionary> allDictionaries = appDictionaryService.list(queryWrapper);
java.util.Map<String, List<AppDictionary>> groupedDict = allDictionaries.stream()
.collect(java.util.stream.Collectors.groupingBy(AppDictionary::getDictcode));
return Result.success(groupedDict);
}
@ -267,4 +268,4 @@ public class AppDictionaryController {
public Integer getOrderno() { return orderno; }
public void setOrderno(Integer orderno) { this.orderno = orderno; }
}
}
}

View File

@ -1,6 +1,8 @@
package com.stdproject.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.stdproject.common.OperationLog;
@ -8,6 +10,7 @@ import com.stdproject.common.PageRequest;
import com.stdproject.common.Result;
import com.stdproject.entity.AppMenu;
import com.stdproject.service.IAppMenuService;
import com.stdproject.service.IAppUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -37,22 +40,25 @@ public class AppMenuController {
@Autowired
private IAppMenuService appMenuService;
@Autowired
private IAppUserService appUserService;
@Operation(summary = "分页查询菜单列表")
@PostMapping("/page")
@OperationLog(type = "06", module = "菜单管理", description = "分页查询菜单列表")
public Result<IPage<AppMenu>> page(@RequestBody @Valid PageRequest pageRequest) {
Page<AppMenu> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
// 关键字搜索
if (StringUtils.hasText(pageRequest.getKeyword())) {
queryWrapper.and(wrapper -> wrapper
.like("name", pageRequest.getKeyword())
.or().like("code", pageRequest.getKeyword())
.or().like("url", pageRequest.getKeyword())
.like("name", pageRequest.getKeyword())
.or().like("code", pageRequest.getKeyword())
.or().like("url", pageRequest.getKeyword())
);
}
// 排序
if (StringUtils.hasText(pageRequest.getOrderBy())) {
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
@ -63,7 +69,7 @@ public class AppMenuController {
} else {
queryWrapper.orderByAsc("parentid", "orderno");
}
IPage<AppMenu> result = appMenuService.page(page, queryWrapper);
return Result.success(result);
}
@ -74,7 +80,7 @@ public class AppMenuController {
public Result<List<AppMenu>> list() {
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByAsc("parentid", "orderno");
List<AppMenu> menus = appMenuService.list(queryWrapper);
return Result.success(menus);
}
@ -82,10 +88,12 @@ public class AppMenuController {
@Operation(summary = "获取菜单树形结构")
@GetMapping("/tree")
@OperationLog(type = "06", module = "菜单管理", description = "获取菜单树形结构")
public Result<List<MenuTreeNode>> getMenuTree() {
public Result<List<MenuTreeNode>> getMenuTree(@RequestParam String appId,@RequestParam String name) {
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(StrUtil.isNotBlank(appId),"app_id", appId);
queryWrapper.like(StrUtil.isNotBlank(name),"name", name);
queryWrapper.orderByAsc("parentid", "orderno");
List<AppMenu> allMenus = appMenuService.list(queryWrapper);
List<MenuTreeNode> tree = buildMenuTree(allMenus, "0");
return Result.success(tree);
@ -106,7 +114,7 @@ public class AppMenuController {
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("parentid", parentId);
queryWrapper.orderByAsc("orderno");
List<AppMenu> children = appMenuService.list(queryWrapper);
return Result.success(children);
}
@ -122,7 +130,7 @@ public class AppMenuController {
if (existMenu != null) {
return Result.error("菜单编号已存在");
}
// 检查父级菜单是否存在如果不是顶级菜单
if (!"0".equals(appMenu.getParentid())) {
AppMenu parentMenu = appMenuService.getById(appMenu.getParentid());
@ -130,7 +138,7 @@ public class AppMenuController {
return Result.error("父级菜单不存在");
}
}
// 设置默认值
if (appMenu.getOrderno() == null) {
// 获取同级菜单的最大序号
@ -141,20 +149,21 @@ public class AppMenuController {
AppMenu lastMenu = appMenuService.getOne(orderQuery);
appMenu.setOrderno(lastMenu != null ? lastMenu.getOrderno() + 1 : 1);
}
if (!StringUtils.hasText(appMenu.getIsdisplay())) {
appMenu.setIsdisplay("1");
}
if (!StringUtils.hasText(appMenu.getIslink())) {
appMenu.setIslink("0");
}
if (!StringUtils.hasText(appMenu.getType())) {
appMenu.setType("01");
}
appMenu.setLastmodifydate(LocalDateTime.now());
appMenu.setLastmodifier(appUserService.getCurrentUsername());
boolean success = appMenuService.save(appMenu);
return success ? Result.success("新增成功") : Result.error("新增失败");
}
@ -168,7 +177,7 @@ public class AppMenuController {
if (existMenu == null) {
return Result.error("菜单不存在");
}
// 如果修改了菜单编号检查新编号是否已被其他菜单使用
if (!existMenu.getCode().equals(appMenu.getCode())) {
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
@ -179,27 +188,55 @@ public class AppMenuController {
return Result.error("菜单编号已被其他菜单使用");
}
}
// 如果修改了父级菜单检查是否会形成循环引用
if (!existMenu.getParentid().equals(appMenu.getParentid())) {
if (isCircularReference(appMenu.getId(), appMenu.getParentid())) {
return Result.error("不能将菜单移动到自己的子菜单下");
}
// 检查父级菜单是否存在如果不是顶级菜单
if (!"0".equals(appMenu.getParentid())) {
if (!"0".equals(existMenu.getParentid())) {
AppMenu parentMenu = appMenuService.getById(appMenu.getParentid());
if (parentMenu == null) {
return Result.error("父级菜单不存在");
}
}
}
appMenu.setLastmodifydate(LocalDateTime.now());
appMenu.setLastmodifier(appUserService.getCurrentUsername());
boolean success = appMenuService.updateById(appMenu);
return success ? Result.success("修改成功") : Result.error("修改失败");
}
@Operation(summary = "更新菜单及按钮是否有效")
@PutMapping("/display/{id}")
@OperationLog(type = "02", module = "菜单管理", description = "更新菜单及按钮是否有效")
public Result<String> setIsDisplay(
@Parameter(description = "菜单ID") @PathVariable String id,
@RequestParam String isDisplay) {
LambdaUpdateWrapper<AppMenu> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(AppMenu::getId, id).set(AppMenu::getIsdisplay, isDisplay).set(AppMenu::getLastmodifydate,
LocalDateTime.now()).set(AppMenu::getLastmodifier, appUserService.getCurrentUsername());
boolean success = appMenuService.update(updateWrapper);
return success ? Result.success("更新成功") : Result.error("更新失败");
}
@Operation(summary = "更新关联模块ID")
@PutMapping("/module/{id}")
@OperationLog(type = "02", module = "菜单管理", description = "更新关联模块ID")
public Result<String> setModuleId(
@Parameter(description = "菜单ID") @PathVariable String id,
@RequestParam String moduleId) {
LambdaUpdateWrapper<AppMenu> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(AppMenu::getId, id).set(AppMenu::getModuleId, moduleId).set(AppMenu::getLastmodifydate,
LocalDateTime.now()).set(AppMenu::getLastmodifier, appUserService.getCurrentUsername());
boolean success = appMenuService.update(updateWrapper);
return success ? Result.success("更新成功") : Result.error("更新失败");
}
@Operation(summary = "删除菜单")
@DeleteMapping("/{id}")
@OperationLog(type = "03", module = "菜单管理", description = "删除菜单")
@ -211,7 +248,7 @@ public class AppMenuController {
if (childCount > 0) {
return Result.error("存在子菜单,无法删除");
}
boolean success = appMenuService.removeById(id);
return success ? Result.success("删除成功") : Result.error("删除失败");
}
@ -229,7 +266,7 @@ public class AppMenuController {
return Result.error("存在子菜单的菜单无法删除");
}
}
boolean success = appMenuService.removeByIds(ids);
return success ? Result.success("批量删除成功") : Result.error("批量删除失败");
}
@ -241,7 +278,7 @@ public class AppMenuController {
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", type);
queryWrapper.orderByAsc("parentid", "orderno");
List<AppMenu> menus = appMenuService.list(queryWrapper);
return Result.success(menus);
}
@ -253,7 +290,7 @@ public class AppMenuController {
QueryWrapper<AppMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("isdisplay", "1");
queryWrapper.orderByAsc("parentid", "orderno");
List<AppMenu> displayMenus = appMenuService.list(queryWrapper);
List<MenuTreeNode> tree = buildMenuTree(displayMenus, "0");
return Result.success(tree);
@ -279,24 +316,24 @@ public class AppMenuController {
*/
private List<MenuTreeNode> buildMenuTree(List<AppMenu> allMenus, String parentId) {
return allMenus.stream()
.filter(menu -> parentId.equals(menu.getParentid()))
.map(menu -> {
MenuTreeNode node = new MenuTreeNode();
node.setId(menu.getId());
node.setCode(menu.getCode());
node.setName(menu.getName());
node.setIcon(menu.getIcon());
node.setUrl(menu.getUrl());
node.setType(menu.getType());
node.setIslink(menu.getIslink());
node.setIsdisplay(menu.getIsdisplay());
node.setOrderno(menu.getOrderno());
node.setParentid(menu.getParentid());
node.setModuleId(menu.getModuleId());
node.setChildren(buildMenuTree(allMenus, menu.getId()));
return node;
})
.collect(Collectors.toList());
.filter(menu -> parentId.equals(menu.getParentid()))
.map(menu -> {
MenuTreeNode node = new MenuTreeNode();
node.setId(menu.getId());
node.setCode(menu.getCode());
node.setName(menu.getName());
node.setIcon(menu.getIcon());
node.setUrl(menu.getUrl());
node.setType(menu.getType());
node.setIslink(menu.getIslink());
node.setIsdisplay(menu.getIsdisplay());
node.setOrderno(menu.getOrderno());
node.setParentid(menu.getParentid());
node.setModuleId(menu.getModuleId());
node.setChildren(buildMenuTree(allMenus, menu.getId()));
return node;
})
.collect(Collectors.toList());
}
/**
@ -306,16 +343,16 @@ public class AppMenuController {
if ("0".equals(newParentId)) {
return false;
}
if (menuId.equals(newParentId)) {
return true;
}
AppMenu parentMenu = appMenuService.getById(newParentId);
if (parentMenu == null) {
return false;
}
return isCircularReference(menuId, parentMenu.getParentid());
}
@ -323,6 +360,7 @@ public class AppMenuController {
* 菜单树节点
*/
public static class MenuTreeNode {
private String id;
private String code;
private String name;
@ -337,42 +375,125 @@ public class AppMenuController {
private List<MenuTreeNode> children = new ArrayList<>();
// Getters and Setters
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getCode() { return code; }
public void setCode(String code) { this.code = code; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getIcon() { return icon; }
public void setIcon(String icon) { this.icon = icon; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
public String getType() { return type; }
public void setType(String type) { this.type = type; }
public String getIslink() { return islink; }
public void setIslink(String islink) { this.islink = islink; }
public String getIsdisplay() { return isdisplay; }
public void setIsdisplay(String isdisplay) { this.isdisplay = isdisplay; }
public Integer getOrderno() { return orderno; }
public void setOrderno(Integer orderno) { this.orderno = orderno; }
public String getParentid() { return parentid; }
public void setParentid(String parentid) { this.parentid = parentid; }
public String getModuleId() { return moduleId; }
public void setModuleId(String moduleId) { this.moduleId = moduleId; }
public List<MenuTreeNode> getChildren() { return children; }
public void setChildren(List<MenuTreeNode> children) { this.children = children; }
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getIslink() {
return islink;
}
public void setIslink(String islink) {
this.islink = islink;
}
public String getIsdisplay() {
return isdisplay;
}
public void setIsdisplay(String isdisplay) {
this.isdisplay = isdisplay;
}
public Integer getOrderno() {
return orderno;
}
public void setOrderno(Integer orderno) {
this.orderno = orderno;
}
public String getParentid() {
return parentid;
}
public void setParentid(String parentid) {
this.parentid = parentid;
}
public String getModuleId() {
return moduleId;
}
public void setModuleId(String moduleId) {
this.moduleId = moduleId;
}
public List<MenuTreeNode> getChildren() {
return children;
}
public void setChildren(List<MenuTreeNode> children) {
this.children = children;
}
}
/**
* 菜单排序请求
*/
public static class MenuOrderRequest {
private String id;
private Integer orderno;
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public Integer getOrderno() { return orderno; }
public void setOrderno(Integer orderno) { this.orderno = orderno; }
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Integer getOrderno() {
return orderno;
}
public void setOrderno(Integer orderno) {
this.orderno = orderno;
}
}
}
}

View File

@ -1,5 +1,6 @@
package com.stdproject.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -12,12 +13,14 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
/**
@ -31,6 +34,7 @@ import java.util.List;
@Tag(name = "操作日志管理", description = "操作日志查询、操作日志删除等功能")
@RestController
@RequestMapping("/api/optlog")
@Slf4j
public class AppOptLogController {
@Autowired
@ -42,16 +46,16 @@ public class AppOptLogController {
public Result<IPage<AppOptLog>> page(@RequestBody @Valid PageRequest pageRequest) {
Page<AppOptLog> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
// 关键字搜索
if (StringUtils.hasText(pageRequest.getKeyword())) {
queryWrapper.and(wrapper -> wrapper
.like("username", pageRequest.getKeyword())
.or().like("module", pageRequest.getKeyword())
.or().like("description", pageRequest.getKeyword())
.like("username", pageRequest.getKeyword())
.or().like("module", pageRequest.getKeyword())
.or().like("description", pageRequest.getKeyword())
);
}
// 排序
if (StringUtils.hasText(pageRequest.getOrderBy())) {
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
@ -62,7 +66,7 @@ public class AppOptLogController {
} else {
queryWrapper.orderByDesc("logtime");
}
IPage<AppOptLog> result = appOptLogService.page(page, queryWrapper);
return Result.success(result);
}
@ -85,7 +89,7 @@ public class AppOptLogController {
queryWrapper.eq("username", username);
queryWrapper.orderByDesc("logtime");
queryWrapper.last("LIMIT " + limit);
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
return Result.success(logs);
}
@ -100,7 +104,7 @@ public class AppOptLogController {
queryWrapper.eq("type", type);
queryWrapper.orderByDesc("logtime");
queryWrapper.last("LIMIT " + limit);
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
return Result.success(logs);
}
@ -113,9 +117,9 @@ public class AppOptLogController {
@Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("module", module);
queryWrapper.orderByDesc("opttime");
queryWrapper.orderByDesc("logtime");
queryWrapper.last("LIMIT " + limit);
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
return Result.success(logs);
}
@ -128,10 +132,10 @@ public class AppOptLogController {
@Parameter(description = "结束时间") @RequestParam @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") LocalDateTime endTime,
@Parameter(description = "限制条数") @RequestParam(defaultValue = "1000") Integer limit) {
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
queryWrapper.between("opttime", startTime, endTime);
queryWrapper.orderByDesc("opttime");
queryWrapper.between("logtime", startTime, endTime);
queryWrapper.orderByDesc("logtime");
queryWrapper.last("LIMIT " + limit);
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
return Result.success(logs);
}
@ -144,14 +148,13 @@ public class AppOptLogController {
@Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) {
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("ip", ip);
queryWrapper.orderByDesc("opttime");
queryWrapper.orderByDesc("logtime");
queryWrapper.last("LIMIT " + limit);
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
return Result.success(logs);
}
@Operation(summary = "删除操作日志")
@DeleteMapping("/{id}")
@OperationLog(type = "03", module = "操作日志管理", description = "删除操作日志")
@ -172,12 +175,21 @@ public class AppOptLogController {
@DeleteMapping("/clean/{days}")
@OperationLog(type = "03", module = "操作日志管理", description = "清理历史操作日志")
public Result<String> cleanOldLogs(@Parameter(description = "保留天数") @PathVariable Integer days) {
LocalDateTime cutoffTime = LocalDateTime.now().minusDays(days);
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
queryWrapper.lt("logtime", cutoffTime);
boolean success = appOptLogService.remove(queryWrapper);
return success ? Result.success("清理成功") : Result.error("清理失败");
try {
// 参数校验
if (days == null || days < 0 || days > 3650) {
return Result.error("保留天数必须在0到3650之间");
}
LocalDateTime cutoff = LocalDateTime.now().minusDays(days);
LambdaQueryWrapper<AppOptLog> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.lt(AppOptLog::getLogtime, cutoff);
boolean success = appOptLogService.remove(queryWrapper);
return success ? Result.success("清理成功") : Result.error("清理失败");
} catch (Exception e) {
// 记录异常日志以便排查
log.error("清理操作日志失败", e);
return Result.error("系统异常,请稍后再试");
}
}
@Operation(summary = "获取操作日志统计信息")
@ -186,31 +198,31 @@ public class AppOptLogController {
public Result<LogStatistics> getStatistics() {
// 总日志数
long totalCount = appOptLogService.count();
// 今日日志数
LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
QueryWrapper<AppOptLog> todayQuery = new QueryWrapper<>();
todayQuery.ge("opttime", todayStart);
todayQuery.ge("logtime", todayStart);
long todayCount = appOptLogService.count(todayQuery);
// 本周日志数
LocalDateTime weekStart = LocalDateTime.now().minusDays(7);
LocalDateTime weekStart = LocalDateTime.now().minusDays(7).withNano(0);
QueryWrapper<AppOptLog> weekQuery = new QueryWrapper<>();
weekQuery.ge("logtime", weekStart);
long weekCount = appOptLogService.count(weekQuery);
// 本月日志数
LocalDateTime monthStart = LocalDateTime.now().minusDays(30);
LocalDateTime monthStart = LocalDateTime.now().minusDays(30).withNano(0);
QueryWrapper<AppOptLog> monthQuery = new QueryWrapper<>();
monthQuery.ge("logtime", monthStart);
long monthCount = appOptLogService.count(monthQuery);
LogStatistics statistics = new LogStatistics();
statistics.setTotalCount(totalCount);
statistics.setTodayCount(todayCount);
statistics.setWeekCount(weekCount);
statistics.setMonthCount(monthCount);
return Result.success(statistics);
}
@ -222,7 +234,7 @@ public class AppOptLogController {
QueryWrapper<AppOptLog> queryWrapper = new QueryWrapper<>();
queryWrapper.orderByDesc("logtime");
queryWrapper.last("LIMIT " + limit);
List<AppOptLog> logs = appOptLogService.list(queryWrapper);
return Result.success(logs);
}
@ -231,18 +243,42 @@ public class AppOptLogController {
* 日志统计信息
*/
public static class LogStatistics {
private Long totalCount;
private Long todayCount;
private Long weekCount;
private Long monthCount;
public Long getTotalCount() { return totalCount; }
public void setTotalCount(Long totalCount) { this.totalCount = totalCount; }
public Long getTodayCount() { return todayCount; }
public void setTodayCount(Long todayCount) { this.todayCount = todayCount; }
public Long getWeekCount() { return weekCount; }
public void setWeekCount(Long weekCount) { this.weekCount = weekCount; }
public Long getMonthCount() { return monthCount; }
public void setMonthCount(Long monthCount) { this.monthCount = monthCount; }
public Long getTotalCount() {
return totalCount;
}
public void setTotalCount(Long totalCount) {
this.totalCount = totalCount;
}
public Long getTodayCount() {
return todayCount;
}
public void setTodayCount(Long todayCount) {
this.todayCount = todayCount;
}
public Long getWeekCount() {
return weekCount;
}
public void setWeekCount(Long weekCount) {
this.weekCount = weekCount;
}
public Long getMonthCount() {
return monthCount;
}
public void setMonthCount(Long monthCount) {
this.monthCount = monthCount;
}
}
}
}

View File

@ -1,21 +1,27 @@
package com.stdproject.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.stdproject.common.OperationLog;
import com.stdproject.common.Result;
import com.stdproject.entity.AppOrganization;
import com.stdproject.service.IAppOrganizationService;
import com.stdproject.service.IAppUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* <p>
@ -39,21 +45,24 @@ public class AppOrganizationController {
@Operation(summary = "查询公司结构树")
@GetMapping("/getCompanyTree")
@OperationLog(type = "06", module = "组织管理", description = "查询公司结构树")
public Result<List<AppOrganization>> getCompanyTree() {
public Result<List<OrganizationTreeNode>> getCompanyTree(@RequestParam String appId) {
QueryWrapper<AppOrganization> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("orgtype", "01");
queryWrapper.eq(StrUtil.isNotBlank(appId),"app_id", appId);
queryWrapper.eq("isvaild", "1");
queryWrapper.orderByAsc("orgcode");
queryWrapper.orderByAsc("orgcode");
List<AppOrganization> children = appOrganizationService.list(queryWrapper);
return Result.success(children);
List<OrganizationTreeNode> organizationTreeNodes = buildOrganizationTree(children, "0");
return Result.success(organizationTreeNodes);
}
@Operation(summary = "查询部门列表")
@Operation(summary = "查询公司/部门列表")
@GetMapping("/getDepartmentList")
@OperationLog(type = "06", module = "组织管理", description = "查询部门列表")
public Result<List<AppOrganization>> getDepartmentList(@RequestParam String parentid,@RequestParam String keystr) {
@OperationLog(type = "06", module = "组织管理", description = "查询公司/部门列表")
public Result<List<AppOrganization>> getDepartmentList(@RequestParam String appId, @RequestParam String orgtype,
@RequestParam String parentid, @RequestParam String keystr) {
QueryWrapper<AppOrganization> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("orgtype", "02");
queryWrapper.eq(StrUtil.isNotBlank(appId), "app_id", appId);
queryWrapper.eq(StrUtil.isNotBlank(orgtype), "orgtype", orgtype);
queryWrapper.eq("parentid", parentid);
queryWrapper.eq("isvaild", "1");
if (StringUtils.hasText(keystr)) {
@ -68,8 +77,6 @@ public class AppOrganizationController {
return Result.success(children);
}
@Operation(summary = "根据ID查询组织详情")
@GetMapping("getDetailById/{id}")
@OperationLog(type = "06", module = "组织管理", description = "根据ID查询组织详情")
@ -78,7 +85,6 @@ public class AppOrganizationController {
return Result.success(organization);
}
@Operation(summary = "新增组织")
@PostMapping("add")
@OperationLog(type = "01", module = "组织管理", description = "新增组织")
@ -115,7 +121,7 @@ public class AppOrganizationController {
if (parent == null || !"1".equals(parent.getIsvaild())) {
return Result.error("父级组织不存在或已禁用");
}
if ("02".equals(parent.getOrgtype())) {
return Result.error("部门下不能有子组织");
}
@ -126,6 +132,19 @@ public class AppOrganizationController {
return success ? Result.success("修改成功") : Result.error("修改失败");
}
@Operation(summary = "设置组织是否有效")
@PutMapping("/isValid/{id}")
@OperationLog(type = "02", module = "组织管理", description = "设置组织是否有效")
public Result<String> setIsValid(
@Parameter(description = "组织ID") @PathVariable String id,
@RequestParam String isValid) {
LambdaUpdateWrapper<AppOrganization> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(AppOrganization::getId, id).set(AppOrganization::getIsvaild, isValid).set(AppOrganization::getLastmodifydate,
LocalDateTime.now()).set(AppOrganization::getLastmodifier, appUserService.getCurrentUsername());
boolean success = appOrganizationService.update(updateWrapper);
return success ? Result.success("设置成功") : Result.error("设置失败");
}
@Operation(summary = "删除组织")
@PostMapping("delete/{id}")
@OperationLog(type = "03", module = "组织管理", description = "删除组织")
@ -137,7 +156,7 @@ public class AppOrganizationController {
/**
* 根据组织类型和父节点生成新的组织编码
*
* @param orgType 组织类型"01"表示公司"02"表示部门
* @param orgType 组织类型"01"表示公司"02"表示部门
* @param parentId 父节点ID新增部门时使用
* @return 新的组织编码
*/
@ -156,7 +175,7 @@ public class AppOrganizationController {
String parentCode = parentOrg != null ? parentOrg.getOrgcode() : "01";
// 获取当前父节点下的最大子节点编码
String maxCompanyCode = getMaxCodeByParentId("01",parentId);
String maxCompanyCode = getMaxCodeByParentId("01", parentId);
int nextLevel = 1;
if (maxCompanyCode != null && !maxCompanyCode.isEmpty()) {
@ -182,7 +201,7 @@ public class AppOrganizationController {
}
// 获取该父节点下最大部门编号
String maxDeptCodeStr = getMaxCodeByParentId("02",parentId);
String maxDeptCodeStr = getMaxCodeByParentId("02", parentId);
int nextSeq = 1;
if (maxDeptCodeStr != null && !maxDeptCodeStr.isEmpty()) {
@ -203,8 +222,7 @@ public class AppOrganizationController {
}
}
private String getMaxCodeByParentId(String orgType,String parentId) {
private String getMaxCodeByParentId(String orgType, String parentId) {
LambdaQueryWrapper<AppOrganization> queryWrapper = new LambdaQueryWrapper<AppOrganization>()
.eq(AppOrganization::getOrgtype, orgType)
.eq(AppOrganization::getParentid, parentId)
@ -217,5 +235,99 @@ public class AppOrganizationController {
// 如果列表不为空返回第一个记录的orgcode
return list.isEmpty() ? null : list.get(0).getOrgcode();
}
/**
* 构建菜单树
*/
private List<OrganizationTreeNode> buildOrganizationTree(List<AppOrganization> allOrganizations, String parentId) {
return allOrganizations.stream()
.filter(organization -> parentId.equals(organization.getParentid()))
.map(organization -> {
OrganizationTreeNode node = new OrganizationTreeNode();
node.setId(organization.getId());
node.setAppId(organization.getAppId());
node.setOrgtype(organization.getOrgtype());
node.setOrgcode(organization.getOrgcode());
node.setOrgname(organization.getOrgname());
node.setParentid(organization.getParentid());
node.setManager(organization.getManager());
node.setDescription(organization.getDescription());
node.setAddress(organization.getAddress());
node.setContactPhone(organization.getContactPhone());
node.setContactPerson(organization.getContactPerson());
node.setIsvaild(organization.getIsvaild());
node.setChildren(buildOrganizationTree(allOrganizations, organization.getId()));
return node;
})
.collect(Collectors.toList());
}
}
/**
* 组织树节点
*/
@Data
public static class OrganizationTreeNode {
/**
* ID
*/
private String id;
/**
* 应用ID 关联应用系统
*/
private String appId;
/**
* 组织类型01-公司 02-部门
*/
private String orgtype;
/**
* 组织编号
*/
private String orgcode;
/**
* 组织名称
*/
private String orgname;
/**
* 上级ID
*/
private String parentid;
/**
* 组织负责人
*/
private String manager;
/**
* 组织详情
*/
private String description;
/**
* 联系地址
*/
private String address;
/**
* 联系电话
*/
private String contactPhone;
/**
* 联系人
*/
private String contactPerson;
/**
* 是否有效 1- 0-
*/
private String isvaild;
private List<OrganizationTreeNode> children = new ArrayList<>();
}
}

View File

@ -1,5 +1,6 @@
package com.stdproject.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
@ -41,32 +42,33 @@ public class AppRoleController {
@Autowired
private IAppRoleService appRoleService;
@Autowired
private IAppRoleMenuService appRoleMenuService;
@Autowired
private IAppRoleUserService appRoleUserService;
@Operation(summary = "分页查询角色列表")
@PostMapping("/page")
@PostMapping("/page/{appId}")
@OperationLog(type = "06", module = "角色管理", description = "分页查询角色列表")
public Result<IPage<AppRole>> page(@RequestBody @Valid PageRequest pageRequest) {
public Result<IPage<AppRole>> page(@Parameter(description = "用户ID") @PathVariable String appId,
@RequestBody @Valid PageRequest pageRequest) {
Page<AppRole> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
QueryWrapper<AppRole> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(StrUtil.isNotBlank(appId),"app_id",appId);
// 关键字搜索
if (StringUtils.hasText(pageRequest.getKeyword())) {
queryWrapper.and(wrapper -> wrapper
.like("rolename", pageRequest.getKeyword())
.or().like("rolecode", pageRequest.getKeyword())
.or().like("description", pageRequest.getKeyword())
.like("rolename", pageRequest.getKeyword())
.or().like("rolecode", pageRequest.getKeyword())
.or().like("description", pageRequest.getKeyword())
);
}
// 只查询有效的角色
queryWrapper.eq("isvaild", "1");
// 排序
if (StringUtils.hasText(pageRequest.getOrderBy())) {
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
@ -77,7 +79,7 @@ public class AppRoleController {
} else {
queryWrapper.orderByAsc("rolecode");
}
IPage<AppRole> result = appRoleService.page(page, queryWrapper);
return Result.success(result);
}
@ -89,7 +91,7 @@ public class AppRoleController {
QueryWrapper<AppRole> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("isvaild", "1");
queryWrapper.orderByAsc("rolecode");
List<AppRole> roles = appRoleService.list(queryWrapper);
return Result.success(roles);
}
@ -101,6 +103,7 @@ public class AppRoleController {
AppRole role = appRoleService.getById(id);
return Result.success(role);
}
@Operation(summary = "新增角色")
@PostMapping
@OperationLog(type = "01", module = "角色管理", description = "新增角色")
@ -133,6 +136,7 @@ public class AppRoleController {
/**
* 获取最大的角色编号
*
* @return 最大角色编号
*/
private String getMaxRoleCode() {
@ -154,7 +158,7 @@ public class AppRoleController {
if (existRole == null) {
return Result.error("角色不存在");
}
// 如果修改了角色名称检查新名称是否已被其他角色使用
if (!existRole.getRolename().equals(appRole.getRolename())) {
QueryWrapper<AppRole> nameQuery = new QueryWrapper<>();
@ -183,29 +187,27 @@ public class AppRoleController {
if (userCount > 0) {
return Result.error("存在用户关联此角色,无法删除");
}
// 软删除设置为无效
AppRole role = appRoleService.getById(id);
if (role != null) {
role.setIsvaild("0");
role.setLastmodifydate(LocalDateTime.now());
boolean success = appRoleService.updateById(role);
if (success) {
// 同时删除角色菜单关联
QueryWrapper<AppRoleMenu> menuQuery = new QueryWrapper<>();
menuQuery.eq("roleid", id);
appRoleMenuService.remove(menuQuery);
}
return success ? Result.success("删除成功") : Result.error("删除失败");
}
return Result.error("角色不存在");
}
@Operation(summary = "根据角色类型查询角色列表")
@GetMapping("/type/{type}")
@OperationLog(type = "06", module = "角色管理", description = "根据角色类型查询角色列表")
@ -213,7 +215,7 @@ public class AppRoleController {
QueryWrapper<AppRole> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("type", type);
queryWrapper.eq("isvaild", "1");
queryWrapper.orderByAsc("rolecode");
queryWrapper.orderByAsc("rolecode");
List<AppRole> roles = appRoleService.list(queryWrapper);
return Result.success(roles);
}
@ -224,18 +226,18 @@ public class AppRoleController {
public Result<String> assignMenus(
@Parameter(description = "角色ID") @PathVariable String roleId,
@RequestBody List<String> menuIds) {
// 检查角色是否存在
AppRole role = appRoleService.getById(roleId);
if (role == null || !"1".equals(role.getIsvaild())) {
return Result.error("角色不存在或已禁用");
}
// 先删除原有的菜单权限
QueryWrapper<AppRoleMenu> deleteQuery = new QueryWrapper<>();
deleteQuery.eq("roleid", roleId);
appRoleMenuService.remove(deleteQuery);
// 添加新的菜单权限
if (menuIds != null && !menuIds.isEmpty()) {
List<AppRoleMenu> roleMenus = menuIds.stream().map(menuId -> {
@ -245,11 +247,11 @@ public class AppRoleController {
roleMenu.setAppId(role.getAppId());
return roleMenu;
}).collect(Collectors.toList());
boolean success = appRoleMenuService.saveBatch(roleMenus);
return success ? Result.success("权限分配成功") : Result.error("权限分配失败");
}
return Result.success("权限分配成功");
}
@ -259,12 +261,12 @@ public class AppRoleController {
public Result<List<String>> getRoleMenus(@Parameter(description = "角色ID") @PathVariable String roleId) {
QueryWrapper<AppRoleMenu> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("roleid", roleId);
List<AppRoleMenu> roleMenus = appRoleMenuService.list(queryWrapper);
List<String> menuIds = roleMenus.stream()
.map(AppRoleMenu::getMenuid)
.collect(Collectors.toList());
.map(AppRoleMenu::getMenuid)
.collect(Collectors.toList());
return Result.success(menuIds);
}
@ -274,18 +276,18 @@ public class AppRoleController {
public Result<String> assignUsers(
@Parameter(description = "角色ID") @PathVariable String roleId,
@RequestBody List<String> userIds) {
// 检查角色是否存在
AppRole role = appRoleService.getById(roleId);
if (role == null || !"1".equals(role.getIsvaild())) {
return Result.error("角色不存在或已禁用");
}
// 先删除原有的用户角色关联
QueryWrapper<AppRoleUser> deleteQuery = new QueryWrapper<>();
deleteQuery.eq("roleid", roleId);
appRoleUserService.remove(deleteQuery);
// 添加新的用户角色关联
if (userIds != null && !userIds.isEmpty()) {
List<AppRoleUser> roleUsers = userIds.stream().map(userId -> {
@ -295,11 +297,11 @@ public class AppRoleController {
roleUser.setAppId(role.getAppId());
return roleUser;
}).collect(Collectors.toList());
boolean success = appRoleUserService.saveBatch(roleUsers);
return success ? Result.success("用户分配成功") : Result.error("用户分配失败");
}
return Result.success("用户分配成功");
}
@ -309,12 +311,12 @@ public class AppRoleController {
public Result<List<String>> getRoleUsers(@Parameter(description = "角色ID") @PathVariable String roleId) {
QueryWrapper<AppRoleUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("roleid", roleId);
List<AppRoleUser> roleUsers = appRoleUserService.list(queryWrapper);
List<String> userIds = roleUsers.stream()
.map(AppRoleUser::getUserid)
.collect(Collectors.toList());
.map(AppRoleUser::getUserid)
.collect(Collectors.toList());
return Result.success(userIds);
}
}
}

View File

@ -1,10 +1,12 @@
package com.stdproject.controller;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.stdproject.common.OperationLog;
import com.stdproject.common.PageRequest;
import com.stdproject.common.QueryWrapperBuilder;
import com.stdproject.common.Result;
import com.stdproject.entity.AppUser;
import com.stdproject.service.IAppUserService;
@ -37,22 +39,25 @@ public class AppUserController {
private IAppUserService appUserService;
@Operation(summary = "分页查询用户列表")
@PostMapping("/page")
@PostMapping("/page/{orgId}/{appId}")
@OperationLog(type = "06", module = "用户管理", description = "分页查询用户列表")
public Result<IPage<AppUser>> page(@RequestBody @Valid PageRequest pageRequest) {
public Result<IPage<AppUser>> page(@Parameter(description = "用户ID") @PathVariable String orgId,
@Parameter(description = "用户ID") @PathVariable String appId,
@RequestBody @Valid PageRequest pageRequest) {
Page<AppUser> page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize());
QueryWrapper<AppUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq(StrUtil.isNotBlank(orgId),"orgid",orgId);
queryWrapper.eq(StrUtil.isNotBlank(appId),"app_id",appId);
// 关键字搜索
if (StringUtils.hasText(pageRequest.getKeyword())) {
queryWrapper.and(wrapper -> wrapper
.like("username", pageRequest.getKeyword())
.or().like("nickname", pageRequest.getKeyword())
.or().like("email", pageRequest.getKeyword())
.or().like("phone", pageRequest.getKeyword())
.like("username", pageRequest.getKeyword())
.or().like("nickname", pageRequest.getKeyword())
.or().like("email", pageRequest.getKeyword())
.or().like("phone", pageRequest.getKeyword())
);
}
// 排序
if (StringUtils.hasText(pageRequest.getOrderBy())) {
if ("asc".equalsIgnoreCase(pageRequest.getOrderDirection())) {
@ -63,7 +68,7 @@ public class AppUserController {
} else {
queryWrapper.orderByDesc("lastmodifydate");
}
IPage<AppUser> result = appUserService.page(page, queryWrapper);
return Result.success(result);
}
@ -101,7 +106,7 @@ public class AppUserController {
if (existUser != null) {
return Result.error("用户名已存在");
}
// 设置默认值
if (!StringUtils.hasText(appUser.getStatus())) {
appUser.setStatus("1"); // 默认有效
@ -112,12 +117,12 @@ public class AppUserController {
if (appUser.getFailednum() == null) {
appUser.setFailednum(0);
}
// 密码加密
if (StringUtils.hasText(appUser.getPassword())) {
appUser.setPassword(PasswordUtils.encodePassword(appUser.getPassword()));
}
appUser.setLastmodifydate(LocalDateTime.now());
boolean success = appUserService.save(appUser);
return success ? Result.success("新增成功") : Result.error("新增失败");
@ -132,7 +137,7 @@ public class AppUserController {
if (existUser == null) {
return Result.error("用户不存在");
}
// 如果修改了用户名检查新用户名是否已被其他用户使用
if (!existUser.getUsername().equals(appUser.getUsername())) {
AppUser userWithSameName = appUserService.findByUsername(appUser.getUsername());
@ -140,7 +145,7 @@ public class AppUserController {
return Result.error("用户名已被其他用户使用");
}
}
// 如果传入了新密码进行加密
if (StringUtils.hasText(appUser.getPassword())) {
appUser.setPassword(PasswordUtils.encodePassword(appUser.getPassword()));
@ -149,7 +154,7 @@ public class AppUserController {
// 如果没有传入密码保持原密码不变
appUser.setPassword(existUser.getPassword());
}
appUser.setLastmodifydate(LocalDateTime.now());
boolean success = appUserService.updateById(appUser);
return success ? Result.success("修改成功") : Result.error("修改失败");
@ -181,17 +186,17 @@ public class AppUserController {
if (user == null) {
return Result.error("用户不存在");
}
// 验证旧密码
if (!PasswordUtils.matches(request.getOldPassword(), user.getPassword())) {
return Result.error("原密码错误");
}
// 更新新密码
user.setPassword(PasswordUtils.encodePassword(request.getNewPassword()));
user.setPwdresettime(LocalDateTime.now());
user.setLastmodifydate(LocalDateTime.now());
boolean success = appUserService.updateById(user);
return success ? Result.success("密码修改成功") : Result.error("密码修改失败");
}
@ -206,12 +211,12 @@ public class AppUserController {
if (user == null) {
return Result.error("用户不存在");
}
// 重置密码
user.setPassword(PasswordUtils.encodePassword(request.getNewPassword()));
user.setPwdresettime(LocalDateTime.now());
user.setLastmodifydate(LocalDateTime.now());
boolean success = appUserService.updateById(user);
return success ? Result.success("密码重置成功") : Result.error("密码重置失败");
}
@ -226,10 +231,10 @@ public class AppUserController {
if (user == null) {
return Result.error("用户不存在");
}
user.setStatus(status);
user.setLastmodifydate(LocalDateTime.now());
boolean success = appUserService.updateById(user);
String message = "1".equals(status) ? "启用成功" : "禁用成功";
return success ? Result.success(message) : Result.error("操作失败");
@ -242,11 +247,11 @@ public class AppUserController {
QueryWrapper<AppUser> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("orgid", orgId);
queryWrapper.orderByDesc("lastmodifydate");
List<AppUser> users = appUserService.list(queryWrapper);
// 不返回密码信息
users.forEach(user -> user.setPassword(null));
return Result.success(users);
}
@ -254,24 +259,42 @@ public class AppUserController {
* 密码修改请求类
*/
public static class PasswordUpdateRequest {
private String oldPassword;
private String newPassword;
// getters and setters
public String getOldPassword() { return oldPassword; }
public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; }
public String getNewPassword() { return newPassword; }
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
/**
* 密码重置请求类
*/
public static class PasswordResetRequest {
private String newPassword;
// getters and setters
public String getNewPassword() { return newPassword; }
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
}
}

View File

@ -3,6 +3,7 @@ package com.stdproject.controller;
import com.stdproject.common.Constants;
import com.stdproject.common.OperationLog;
import com.stdproject.common.Result;
import com.stdproject.config.WebConfig;
import com.stdproject.entity.AppUser;
import com.stdproject.service.IAppUserService;
import com.stdproject.utils.CaptchaUtils;
@ -20,6 +21,7 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.*;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@ -48,12 +50,14 @@ public class AuthController {
@Autowired
private IAppUserService appUserService;
@Autowired
private WebConfig webConfig;
/**
* 登录请求类
*/
public static class LoginRequest {
@NotBlank(message = "用户名不能为空")
private String username;
@ -67,20 +71,44 @@ public class AuthController {
private String captchaKey;
// Getters and Setters
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getCaptcha() { return captcha; }
public void setCaptcha(String captcha) { this.captcha = captcha; }
public String getCaptchaKey() { return captchaKey; }
public void setCaptchaKey(String captchaKey) { this.captchaKey = captchaKey; }
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public String getCaptchaKey() {
return captchaKey;
}
public void setCaptchaKey(String captchaKey) {
this.captchaKey = captchaKey;
}
}
/**
* 修改密码请求类
*/
public static class ChangePasswordRequest {
@NotBlank(message = "原密码不能为空")
private String oldPassword;
@ -88,10 +116,21 @@ public class AuthController {
private String newPassword;
// Getters and Setters
public String getOldPassword() { return oldPassword; }
public void setOldPassword(String oldPassword) { this.oldPassword = oldPassword; }
public String getNewPassword() { return newPassword; }
public void setNewPassword(String newPassword) { this.newPassword = newPassword; }
public String getOldPassword() {
return oldPassword;
}
public void setOldPassword(String oldPassword) {
this.oldPassword = oldPassword;
}
public String getNewPassword() {
return newPassword;
}
public void setNewPassword(String newPassword) {
this.newPassword = newPassword;
}
}
/**
@ -128,7 +167,8 @@ public class AuthController {
@Operation(summary = "用户登录", description = "用户登录认证")
@PostMapping("/login")
@OperationLog(type = Constants.OPT_TYPE_LOGIN, module = "认证管理", description = "用户登录")
public Result<Map<String, Object>> login(@Valid @RequestBody LoginRequest loginRequest, HttpServletRequest request) {
public Result<Map<String, Object>> login(@Valid @RequestBody LoginRequest loginRequest,
HttpServletRequest request) {
try {
// 注意在实际应用中应该从会话或其他存储中获取验证码进行验证
// 此处简化处理假设验证码已通过实际应用中需要实现验证逻辑
@ -136,17 +176,17 @@ public class AuthController {
// 进行身份认证
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(),
loginRequest.getPassword()
)
);
AppUser user=appUserService.findByUsername(loginRequest.getUsername());
AppUser user = appUserService.findByUsername(loginRequest.getUsername());
// 设置认证信息到安全上下文
SecurityContextHolder.getContext().setAuthentication(authentication);
// 生成JWT Token
String token = jwtUtils.generateToken(user.getUsername(),user.getId());
String token = jwtUtils.generateToken(user.getUsername(), user.getId());
Map<String, Object> result = new HashMap<>();
result.put("token", token);
@ -172,10 +212,19 @@ public class AuthController {
String token = request.getHeader(Constants.JWT_HEADER);
if (token != null && token.startsWith(Constants.JWT_PREFIX)) {
token = token.substring(Constants.JWT_PREFIX.length());
// 实现token失效
Date expirationDateFromToken = jwtUtils.getExpirationDateFromToken(token);
// 获取当前时间的时间戳毫秒
long nowMillis = System.currentTimeMillis();
// 注意在实际应用中应该实现Token失效机制
// 可以考虑使用短期Token或其他方式实现Token失效
// 此处省略Token黑名单实现
// 获取 Token 过期时间的时间戳毫秒
long expirationMillis = expirationDateFromToken.getTime();
// 计算剩余有效时间毫秒
long remainingMillis = expirationMillis - nowMillis;
if (remainingMillis > 0) {
webConfig.timedCache().put(token, remainingMillis);
}
}
// 清除安全上下文
@ -216,14 +265,16 @@ public class AuthController {
@Operation(summary = "修改密码", description = "修改当前用户密码")
@PostMapping("/changePassword")
@OperationLog(type = Constants.OPT_TYPE_UPDATE, module = "认证管理", description = "修改密码")
public Result<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) {
public Result<Void> changePassword(@Valid @RequestBody ChangePasswordRequest request,
HttpServletRequest httpRequest) {
try {
String token = httpRequest.getHeader(Constants.JWT_HEADER);
if (token != null && token.startsWith(Constants.JWT_PREFIX)) {
token = token.substring(Constants.JWT_PREFIX.length());
String username = jwtUtils.getUsernameFromToken(token);
boolean success = appUserService.changePassword(username, request.getOldPassword(), request.getNewPassword());
boolean success = appUserService.changePassword(username, request.getOldPassword(),
request.getNewPassword());
if (success) {
return Result.success();
} else {
@ -239,6 +290,7 @@ public class AuthController {
}
@PostMapping("/refreshToken")
@Operation(summary = "刷新令牌", description = "刷新当前用户的令牌")
public Result<String> refreshToken(@RequestBody Map<String, String> params) {
String refreshToken = params.get("refreshToken");
@ -262,5 +314,9 @@ public class AuthController {
}
}
@GetMapping("/getExpirationDateFromToken")
public Result<Date> getExpirationDateFromToken(String token) {
Date expirationDateFromToken = jwtUtils.getExpirationDateFromToken(token);
return Result.success(expirationDateFromToken);
}
}

View File

@ -40,7 +40,6 @@ public class DynamicDataController {
return result;
}
@Operation(summary = "根据主键查询表数据")
@OperationLog(type = "06", module = "动态数据管理", description = "根据主键查询表数据")
@PostMapping("getTableDataByPk")
public Map<String, Object> getTableDataByPk(Long datasourceId,@RequestParam("whereJson") String whereJson) throws Exception {
Map<String, Object> result=dynamicDataService.getTableDataByPk(
@ -73,7 +72,6 @@ public class DynamicDataController {
}
@Operation(summary = "分页查询表数据")
@OperationLog(type = "06", module = "动态数据管理", description = "分页查询表数据")
@PostMapping("queryTableDataPaged")
public Page<Map<String, Object>> queryTableDataPaged(Long datasourceId, @RequestParam("queryJson") String queryJson) throws Exception {
Page<Map<String, Object>> result = dynamicDataService.queryTableDataPaged(

View File

@ -0,0 +1,23 @@
package com.stdproject.listener;
import com.stdproject.service.provider.CalciteProvider;
import jakarta.annotation.Resource;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Component
@Order(value = 2)
public class DataSourceInitStartListener implements ApplicationListener<ApplicationReadyEvent> {
@Resource
private CalciteProvider calciteProvider;
@Override
public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
try {
calciteProvider.initConnectionPool();
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -15,4 +15,4 @@ import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AppOptLogMapper extends BaseMapper<AppOptLog> {
}
}

View File

@ -2,6 +2,7 @@ package com.stdproject.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.stdproject.entity.AppOptLog;
import org.apache.ibatis.annotations.Delete;
/**
* <p>
@ -13,4 +14,4 @@ import com.stdproject.entity.AppOptLog;
*/
public interface IAppOptLogService extends IService<AppOptLog> {
}
}

View File

@ -4,6 +4,8 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.stdproject.entity.AppOptLog;
import com.stdproject.mapper.AppOptLogMapper;
import com.stdproject.service.IAppOptLogService;
import org.apache.ibatis.annotations.Delete;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
@ -17,4 +19,4 @@ import org.springframework.stereotype.Service;
@Service
public class AppOptLogServiceImpl extends ServiceImpl<AppOptLogMapper, AppOptLog> implements IAppOptLogService {
}
}

View File

@ -33,10 +33,10 @@ public class DynamicDataServiceImpl implements IDynamicDataService {
public boolean addTableData(Long datasourceId, String tableData) throws Exception {
// 根据数据源 id 查询数据源信息调用通用数据源执行器
CoreDatasource coreDatasource = coreDatasourceMapper.selectById(datasourceId);
coreDatasource.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
if (coreDatasource == null) {
BusinessException.throwException("数据源不存在");
}
coreDatasource.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
// 解析 tableData JSON 字符串 "{ \"tableName\": \"user\", \"data\": [ { \"fieldName\": \"id\", \"fieldType\": \"varchar\", \"IsPrimaryKey\": true, \"fieldValue\": \"0001\" }, { \"fieldName\": \"name\", \"fieldType\": \"varchar\", \"fieldValue\": \"张三\" } ] }";
Map<String, Object> dataMap = JsonUtil.parseObject(tableData, Map.class);
String tableName = (String) dataMap.get("tableName");
@ -99,6 +99,7 @@ public class DynamicDataServiceImpl implements IDynamicDataService {
if (coreDatasource == null) {
BusinessException.throwException("数据源不存在");
}
coreDatasource.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
// 解析 JSON 数据
//condtion={ \"tableName\": \"user\", key:[{ \"fieldName\": \"id\",\"fieldValue\": \"0001\"]
Map<String, Object> dataMap = JsonUtil.parseObject(condtion, Map.class);
@ -182,6 +183,7 @@ public class DynamicDataServiceImpl implements IDynamicDataService {
if (coreDatasource == null) {
BusinessException.throwException("数据源不存在");
}
coreDatasource.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
//String tableDataJson = "{ \"tableName\": \"user\", key:[{ \"fieldName\": \"id\",\"fieldValue\": \"0001\"], \"data\": [ { \"fieldName\": \"name\", \"fieldType\": \"varchar\", \"fieldValue\": \"李四\" } ] }";
// 解析 JSON 数据
Map<String, Object> dataMap = JsonUtil.parseObject(tableData, Map.class);
@ -242,7 +244,7 @@ public class DynamicDataServiceImpl implements IDynamicDataService {
CoreDatasource coreDatasource = coreDatasourceMapper.selectById(datasourceId);
if (coreDatasource == null) {
BusinessException.throwException("数据源不存在"); }
coreDatasource.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
// 解析 JSON 数据
//String tableDataJson = "{ \"tableName\": \"user\", key:[{ \"fieldName\": \"id\",\"fieldValue\": \"0001\"] }";
Map<String, Object> dataMap = JsonUtil.parseObject(condtion, Map.class);
@ -283,7 +285,7 @@ public class DynamicDataServiceImpl implements IDynamicDataService {
if (coreDatasource == null) {
BusinessException.throwException("数据源不存在");
}
coreDatasource.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
// 解析 condition JSON 数据
Map<String, Object> dataMap = JsonUtil.parseObject(condition, Map.class);
String tableName = (String) dataMap.get("tableName");

View File

@ -7,6 +7,7 @@ import com.stdproject.mapper.CoreDatasourceMapper;
import com.stdproject.mapper.CoreDeEngineMapper;
import com.stdproject.service.type.H2;
import com.stdproject.service.type.Mysql;
import com.stdproject.utils.EncryptUtils;
import io.gisbi.exception.DEException;
import io.gisbi.extensions.datasource.dto.DatasourceDTO;
import io.gisbi.extensions.datasource.dto.DatasourceRequest;
@ -101,44 +102,31 @@ public class EngineManage {
public void initSimpleEngine() throws Exception {
initLocalDataSource();
QueryWrapper<CoreDeEngine> queryWrapper = new QueryWrapper<>();
if (ModelUtils.isDesktop()) {
queryWrapper.eq("type", engineType.h2.name());
} else {
queryWrapper.eq("type", engineType.mysql.name());
}
queryWrapper.eq("type", engineType.mysql.name());
List<CoreDeEngine> deEngines = deEngineMapper.selectList(queryWrapper);
if (!CollectionUtils.isEmpty(deEngines)) {
return;
}
CoreDeEngine engine = new CoreDeEngine();
if (ModelUtils.isDesktop()) {
engine.setType(engineType.h2.name());
H2 h2 = new H2();
h2.setJdbc(engineUrl);
h2.setDataBase("PUBLIC");
h2.setUsername(env.getProperty("spring.datasource.username"));
h2.setPassword(env.getProperty("spring.datasource.password"));
engine.setConfiguration(JsonUtil.toJSONString(h2).toString());
} else {
engine.setType(engineType.mysql.name());
Mysql mysqlConfiguration = new Mysql();
Pattern WITH_SQL_FRAGMENT = Pattern.compile("jdbc:mysql://(.*):(\\d+)/(.*)");
Matcher matcher = WITH_SQL_FRAGMENT.matcher(env.getProperty("spring.datasource.url"));
if (!matcher.find()) {
return;
}
mysqlConfiguration.setHost(matcher.group(1));
mysqlConfiguration.setPort(Integer.valueOf(matcher.group(2)));
String[] databasePrams = matcher.group(3).split("\\?");
mysqlConfiguration.setDataBase(databasePrams[0]);
if (databasePrams.length == 2) {
mysqlConfiguration.setExtraParams(databasePrams[1]);
}
mysqlConfiguration.setUsername(env.getProperty("spring.datasource.username"));
mysqlConfiguration.setPassword(env.getProperty("spring.datasource.password"));
engine.setConfiguration(JsonUtil.toJSONString(mysqlConfiguration).toString());
engine.setType(engineType.mysql.name());
Mysql mysqlConfiguration = new Mysql();
Pattern WITH_SQL_FRAGMENT = Pattern.compile("jdbc:mysql://(.*):(\\d+)/(.*)");
Matcher matcher = WITH_SQL_FRAGMENT.matcher(env.getProperty("spring.datasource.url"));
if (!matcher.find()) {
return;
}
mysqlConfiguration.setHost(matcher.group(1));
mysqlConfiguration.setPort(Integer.valueOf(matcher.group(2)));
String[] databasePrams = matcher.group(3).split("\\?");
mysqlConfiguration.setDataBase(databasePrams[0]);
if (databasePrams.length == 2) {
mysqlConfiguration.setExtraParams(databasePrams[1]);
}
mysqlConfiguration.setUsername(env.getProperty("spring.datasource.username"));
mysqlConfiguration.setPassword(env.getProperty("spring.datasource.password"));
String config=JsonUtil.toJSONString(mysqlConfiguration).toString();
engine.setConfiguration((String) EncryptUtils.aesEncrypt(config));
engine.setName("默认引擎");
engine.setDescription("默认引擎");
deEngineMapper.insert(engine);
@ -163,7 +151,7 @@ public class EngineManage {
QueryWrapper<CoreDatasource> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("id", 985188400292302848L);
queryWrapper.ne("create_time", 1715053684176L);
if (!datasourceMapper.exists(queryWrapper) && !ModelUtils.isDesktop()) {
if (!datasourceMapper.exists(queryWrapper)) {
Pattern WITH_SQL_FRAGMENT = Pattern.compile("jdbc:mysql://(.*):(\\d+)/(.*)\\?(.*)");
Matcher matcher = WITH_SQL_FRAGMENT.matcher(env.getProperty("spring.datasource.url"));
if (!matcher.find()) {
@ -182,7 +170,8 @@ public class EngineManage {
initDatasource.setName("Demo");
initDatasource.setType("mysql");
initDatasource.setPid(0L);
initDatasource.setConfiguration(JsonUtil.toJSONString(configuration).toString());
String config=JsonUtil.toJSONString(configuration).toString();
initDatasource.setConfiguration((String) EncryptUtils.aesEncrypt(config));
initDatasource.setCreateTime(System.currentTimeMillis());
initDatasource.setUpdateTime(System.currentTimeMillis());
initDatasource.setCreateBy("1");

View File

@ -2,15 +2,16 @@ package com.stdproject.service.provider;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.jcraft.jsch.Session;
import com.stdproject.common.BusinessException;
import com.stdproject.controller.EngineRequest;
import com.stdproject.entity.CoreDatasource;
import com.stdproject.entity.CoreDriver;
import com.stdproject.mapper.CoreDatasourceMapper;
import com.stdproject.service.manage.EngineManage;
import com.stdproject.service.type.*;
import com.stdproject.utils.EncryptUtils;
import com.stdproject.utils.FieldUtils;
import io.gisbi.constant.SQLConstants;
import io.gisbi.exception.DEException;
import io.gisbi.extensions.datasource.dto.*;
import io.gisbi.extensions.datasource.provider.DriverShim;
import io.gisbi.extensions.datasource.provider.ExtendedJdbcClassLoader;
@ -97,7 +98,7 @@ public class CalciteProvider extends Provider {
schemas.add(resultSet.getString(1));
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return schemas;
}
@ -110,7 +111,7 @@ public class CalciteProvider extends Provider {
DatasourceConfiguration configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Pg.class);
List<String> schemas = getSchema(datasourceRequest);
if (CollectionUtils.isEmpty(schemas) || !schemas.contains(configuration.getSchema())) {
DEException.throwException("无效的 schema");
BusinessException.throwException("无效的 schema");
}
break;
default:
@ -147,13 +148,13 @@ public class CalciteProvider extends Provider {
}
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return tables;
}
@Override
public Map<String, Object> fetchResultField(DatasourceRequest datasourceRequest) throws DEException {
public Map<String, Object> fetchResultField(DatasourceRequest datasourceRequest) throws BusinessException {
// 不跨数据源
if (datasourceRequest.getDsList().size() == 1) {
return jdbcFetchResultField(datasourceRequest);
@ -189,7 +190,7 @@ public class CalciteProvider extends Provider {
} else {
msg = e.getMessage();
}
DEException.throwException(Translator.get("i18n_fetch_error") + msg);
BusinessException.throwException(Translator.get("i18n_fetch_error") + msg);
} finally {
try {
if (resultSet != null) resultSet.close();
@ -204,7 +205,7 @@ public class CalciteProvider extends Provider {
}
@Override
public String transSqlDialect(String sql, Map<Long, DatasourceSchemaDTO> dsMap) throws DEException {
public String transSqlDialect(String sql, Map<Long, DatasourceSchemaDTO> dsMap) throws BusinessException {
DatasourceSchemaDTO value = dsMap.entrySet().iterator().next().getValue();
try (Connection connection = getConnectionFromPool(value.getId());) {
// 获取数据库version
@ -215,7 +216,7 @@ public class CalciteProvider extends Provider {
SqlNode sqlNode = parser.parseStmt();
return sqlNode.toSqlString(getDialect(value)).toString();
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return null;
}
@ -241,7 +242,7 @@ public class CalciteProvider extends Provider {
}
@Override
public List<TableField> fetchTableField(DatasourceRequest datasourceRequest) throws DEException {
public List<TableField> fetchTableField(DatasourceRequest datasourceRequest) throws BusinessException {
List<TableField> datasetTableFields = new ArrayList<>();
DatasourceSchemaDTO datasourceSchemaDTO = datasourceRequest.getDsList().entrySet().iterator().next().getValue();
datasourceRequest.setDatasource(datasourceSchemaDTO);
@ -258,7 +259,7 @@ public class CalciteProvider extends Provider {
resultSet = statement.executeQuery(datasourceRequest.getQuery());
datasetTableFields.addAll(getField(resultSet, datasourceRequest));
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
} finally {
if (resultSet != null) {
try {
@ -295,7 +296,7 @@ public class CalciteProvider extends Provider {
}
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
} finally {
if (resultSet != null) {
try {
@ -385,7 +386,7 @@ public class CalciteProvider extends Provider {
conn = driverClass.connect(configuration.getJdbc(), props);
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
connectionObj.setConnection(conn);
return connectionObj;
@ -411,7 +412,7 @@ public class CalciteProvider extends Provider {
return drivers;
}
public Map<String, Object> jdbcFetchResultField(DatasourceRequest datasourceRequest) throws DEException {
public Map<String, Object> jdbcFetchResultField(DatasourceRequest datasourceRequest) throws BusinessException {
DatasourceSchemaDTO value = datasourceRequest.getDsList().entrySet().iterator().next().getValue();
datasourceRequest.setDatasource(value);
@ -441,9 +442,9 @@ public class CalciteProvider extends Provider {
fieldList = getField(resultSet, datasourceRequest);
dataList = getData(resultSet, datasourceRequest);
} catch (SQLException e) {
DEException.throwException("SQL ERROR: " + e.getMessage());
BusinessException.throwException("SQL ERROR: " + e.getMessage());
} catch (Exception e) {
DEException.throwException("Datasource connection exception: " + e.getMessage());
BusinessException.throwException("Datasource connection exception: " + e.getMessage());
} finally {
if (resultSet != null) {
try {
@ -460,7 +461,7 @@ public class CalciteProvider extends Provider {
}
@Override
public void exec(DatasourceRequest datasourceRequest) throws DEException {
public void exec(DatasourceRequest datasourceRequest) throws BusinessException {
DatasourceSchemaDTO value = datasourceRequest.getDsList().entrySet().iterator().next().getValue();
datasourceRequest.setDatasource(value);
DatasourceConfiguration datasourceConfiguration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), DatasourceConfiguration.class);
@ -482,9 +483,9 @@ public class CalciteProvider extends Provider {
}
} catch (SQLException e) {
DEException.throwException("SQL ERROR: " + e.getMessage());
BusinessException.throwException("SQL ERROR: " + e.getMessage());
} catch (Exception e) {
DEException.throwException("Datasource connection exception: " + e.getMessage());
BusinessException.throwException("Datasource connection exception: " + e.getMessage());
} finally {
if (resultSet != null) {
try {
@ -497,7 +498,7 @@ public class CalciteProvider extends Provider {
}
@Override
public int executeUpdate(DatasourceRequest datasourceRequest) throws DEException {
public int executeUpdate(DatasourceRequest datasourceRequest) throws BusinessException {
DatasourceSchemaDTO value = datasourceRequest.getDsList().entrySet().iterator().next().getValue();
datasourceRequest.setDatasource(value);
DatasourceConfiguration datasourceConfiguration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), DatasourceConfiguration.class);
@ -520,9 +521,9 @@ public class CalciteProvider extends Provider {
}
} catch (SQLException e) {
DEException.throwException("SQL ERROR: " + e.getMessage());
BusinessException.throwException("SQL ERROR: " + e.getMessage());
} catch (Exception e) {
DEException.throwException("Datasource connection exception: " + e.getMessage());
BusinessException.throwException("Datasource connection exception: " + e.getMessage());
} finally {
if (resultSet != null) {
try {
@ -734,6 +735,7 @@ public class CalciteProvider extends Provider {
private void registerDriver() {
for (String driverClass : getDriver()) {
try {
if(driverClass.equals("org.h2.Driver")) continue;//忽略h2
Driver driver = (Driver) extendedJdbcClassLoader.loadClass(driverClass).newInstance();
DriverManager.registerDriver(new DriverShim(driver));
} catch (Exception e) {
@ -745,23 +747,17 @@ public class CalciteProvider extends Provider {
private Connection getCalciteConnection() {
registerDriver();
Properties info = new Properties();
// info.setProperty(CalciteConnectionProperty.LEX.camelName(), "JAVA");
// info.setProperty(CalciteConnectionProperty.FUN.camelName(), "all");
// info.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(), "false");
// info.setProperty(CalciteConnectionProperty.PARSER_FACTORY.camelName(), "org.apache.calcite.sql.parser.impl.SqlParserImpl#FACTORY");
// info.setProperty(CalciteConnectionProperty.DEFAULT_NULL_COLLATION.camelName(), NullCollation.LAST.name());
info.setProperty("lex", "JAVA");
info.setProperty("fun", "all");
info.setProperty("caseSensitive", "false");
info.setProperty("parserFactory", "org.apache.calcite.sql.parser.impl.SqlParserImpl#FACTORY");
info.setProperty("defaultNullCollation", NullCollation.LAST.name());
info.setProperty("remarks", "true");
info.setProperty(CalciteConnectionProperty.LEX.camelName(), "JAVA");
info.setProperty(CalciteConnectionProperty.FUN.camelName(), "all");
info.setProperty(CalciteConnectionProperty.CASE_SENSITIVE.camelName(), "false");
info.setProperty(CalciteConnectionProperty.PARSER_FACTORY.camelName(), "org.apache.calcite.sql.parser.impl.SqlParserImpl#FACTORY");
info.setProperty(CalciteConnectionProperty.DEFAULT_NULL_COLLATION.camelName(), NullCollation.LAST.name());
Connection connection = null;
try {
Class.forName("org.apache.calcite.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:calcite:", info);
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return connection;
}
@ -1001,7 +997,7 @@ public class CalciteProvider extends Provider {
list.add(row);
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return list;
}
@ -1049,21 +1045,21 @@ public class CalciteProvider extends Provider {
case oracle:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Oracle.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
sql = String.format("SELECT a.COLUMN_NAME , a.DATA_TYPE , b.COMMENTS ,0 FROM all_tab_columns a LEFT JOIN all_col_comments b ON a.owner = b.owner AND a.table_name = b.table_name AND a.column_name = b.column_name WHERE a.owner = '%s' AND a.table_name = '%s' ORDER BY a.table_name, a.column_id", configuration.getSchema(), datasourceRequest.getTable());
break;
case db2:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Db2.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
sql = String.format("SELECT COLNAME , TYPENAME , REMARKS FROM SYSCAT.COLUMNS WHERE TABSCHEMA = '%s' AND TABNAME = '%s' ", configuration.getSchema(), datasourceRequest.getTable());
break;
case sqlServer:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Sqlserver.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
sql = String.format("SELECT \n" + " c.name ,t.name ,ep.value, 0 \n" + "FROM \n" + " sys.columns AS c\n" + "LEFT JOIN sys.extended_properties AS ep ON c.object_id = ep.major_id AND c.column_id = ep.minor_id\n" + "LEFT JOIN sys.types AS t ON c.user_type_id = t.user_type_id\n" + "LEFT JOIN sys.objects AS o ON c.object_id = o.object_id\n" + "WHERE o.name = '%s'", datasourceRequest.getTable());
@ -1071,7 +1067,7 @@ public class CalciteProvider extends Provider {
case pg:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Pg.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
sql = String.format("SELECT\n" +
" a.attname AS ColumnName,\n" +
@ -1126,7 +1122,7 @@ public class CalciteProvider extends Provider {
return sql;
}
private List<String> getTablesSql(DatasourceRequest datasourceRequest) throws DEException {
private List<String> getTablesSql(DatasourceRequest datasourceRequest) throws BusinessException {
List<String> tableSqls = new ArrayList<>();
DatasourceConfiguration.DatasourceType datasourceType = DatasourceConfiguration.DatasourceType.valueOf(datasourceRequest.getDatasource().getType());
DatasourceConfiguration configuration = null;
@ -1171,7 +1167,7 @@ public class CalciteProvider extends Provider {
case oracle:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Oracle.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
tableSqls.add("select table_name, comments, owner from all_tab_comments where owner='" + configuration.getSchema() + "' AND table_type = 'TABLE'");
tableSqls.add("select table_name, comments, owner from all_tab_comments where owner='" + configuration.getSchema() + "' AND table_type = 'VIEW'");
@ -1179,14 +1175,14 @@ public class CalciteProvider extends Provider {
case db2:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Db2.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
tableSqls.add("select TABNAME, REMARKS from syscat.tables WHERE TABSCHEMA ='DE_SCHEMA' AND \"TYPE\" = 'T'".replace("DE_SCHEMA", configuration.getSchema()));
break;
case sqlServer:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Sqlserver.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
tableSqls.add("SELECT \n" + " t.name AS TableName, \n" + " ep.value AS TableDescription \n" + "FROM \n" + " sys.tables t \n" + "LEFT OUTER JOIN sys.schemas sc ON sc.schema_id =t.schema_id \n" + "LEFT OUTER JOIN \n" + " sys.extended_properties ep ON t.object_id = ep.major_id \n" + " AND ep.minor_id = 0 \n" + " AND ep.class = 1 \n" + " AND ep.name = 'MS_Description'\n" + "where sc.name ='DS_SCHEMA'".replace("DS_SCHEMA", configuration.getSchema()));
tableSqls.add("SELECT \n" + " t.name AS TableName, \n" + " ep.value AS TableDescription \n" + "FROM \n" + " sys.views t \n" + "LEFT OUTER JOIN sys.schemas sc ON sc.schema_id =t.schema_id \n" + "LEFT OUTER JOIN \n" + " sys.extended_properties ep ON t.object_id = ep.major_id \n" + " AND ep.minor_id = 0 \n" + " AND ep.class = 1 \n" + " AND ep.name = 'MS_Description'\n" + "where sc.name ='DS_SCHEMA'".replace("DS_SCHEMA", configuration.getSchema()));
@ -1194,7 +1190,7 @@ public class CalciteProvider extends Provider {
case pg:
configuration = JsonUtil.parseObject(datasourceRequest.getDatasource().getConfiguration(), Pg.class);
if (StringUtils.isEmpty(configuration.getSchema())) {
DEException.throwException(Translator.get("i18n_schema_is_empty"));
BusinessException.throwException(Translator.get("i18n_schema_is_empty"));
}
tableSqls.add("SELECT \n" + " relname AS TableName, \n" + " obj_description(relfilenode::regclass, 'pg_class') AS TableDescription \n" + "FROM \n" + " pg_class \n" + "WHERE \n" + " relkind in ('r','p', 'f') \n" + " AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'SCHEMA') ".replace("SCHEMA", configuration.getSchema()));
break;
@ -1228,7 +1224,7 @@ public class CalciteProvider extends Provider {
}
private String getSchemaSql(DatasourceDTO datasource) throws DEException {
private String getSchemaSql(DatasourceDTO datasource) throws BusinessException {
DatasourceConfiguration.DatasourceType datasourceType = DatasourceConfiguration.DatasourceType.valueOf(datasource.getType());
switch (datasourceType) {
case oracle:
@ -1249,14 +1245,14 @@ public class CalciteProvider extends Provider {
public Statement getStatement(Connection connection, int queryTimeout) {
if (connection == null) {
DEException.throwException("Failed to get connection!");
BusinessException.throwException("Failed to get connection!");
}
Statement stat = null;
try {
stat = connection.createStatement();
stat.setQueryTimeout(queryTimeout);
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return stat;
}
@ -1271,7 +1267,7 @@ public class CalciteProvider extends Provider {
stat = connection.prepareStatement(sql);
stat.setQueryTimeout(queryTimeout);
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return stat;
} else {
@ -1285,7 +1281,7 @@ public class CalciteProvider extends Provider {
protected ExtendedJdbcClassLoader getCustomJdbcClassLoader(CoreDriver coreDriver) {
if (coreDriver == null) {
DEException.throwException("Can not found custom Driver");
BusinessException.throwException("Can not found custom Driver");
}
ExtendedJdbcClassLoader customJdbcClassLoader = customJdbcClassLoaders.get(coreDriver.getId());
if (customJdbcClassLoader == null) {
@ -1327,7 +1323,7 @@ public class CalciteProvider extends Provider {
customJdbcClassLoaders.put(coreDriver.getId(), customJdbcClassLoader);
return customJdbcClassLoader;
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
return null;
}
@ -1347,6 +1343,7 @@ public class CalciteProvider extends Provider {
Map<Long, DatasourceSchemaDTO> dsMap = new HashMap<>();
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, coreDatasource);
datasourceSchemaDTO.setConfiguration(String.valueOf(EncryptUtils.aesDecrypt(coreDatasource.getConfiguration())));
datasourceSchemaDTO.setSchemaAlias(String.format(SQLConstants.SCHEMA, datasourceSchemaDTO.getId()));
dsMap.put(datasourceSchemaDTO.getId(), datasourceSchemaDTO);
commonThreadPool.addTask(() -> {
@ -1360,7 +1357,7 @@ public class CalciteProvider extends Provider {
}
public void update(DatasourceDTO datasourceDTO) throws DEException {
public void update(DatasourceDTO datasourceDTO) throws BusinessException {
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, datasourceDTO);
datasourceSchemaDTO.setSchemaAlias(String.format(SQLConstants.SCHEMA, datasourceSchemaDTO.getId()));
@ -1370,11 +1367,11 @@ public class CalciteProvider extends Provider {
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = buildSchema(datasourceRequest, calciteConnection);
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
}
public void updateDsPoolAfterCheckStatus(DatasourceDTO datasourceDTO) throws DEException {
public void updateDsPoolAfterCheckStatus(DatasourceDTO datasourceDTO) throws BusinessException {
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, datasourceDTO);
datasourceSchemaDTO.setSchemaAlias(String.format(SQLConstants.SCHEMA, datasourceSchemaDTO.getId()));
@ -1394,11 +1391,11 @@ public class CalciteProvider extends Provider {
startSshSession(configuration, null, datasourceDTO.getId());
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
}
public void delete(CoreDatasource datasource) throws DEException {
public void delete(CoreDatasource datasource) throws BusinessException {
DatasourceSchemaDTO datasourceSchemaDTO = new DatasourceSchemaDTO();
BeanUtils.copyBean(datasourceSchemaDTO, datasource);
datasourceSchemaDTO.setSchemaAlias(String.format(SQLConstants.SCHEMA, datasourceSchemaDTO.getId()));
@ -1412,7 +1409,7 @@ public class CalciteProvider extends Provider {
rootSchema.removeSubSchema(datasourceSchemaDTO.getSchemaAlias());
}
} catch (Exception e) {
DEException.throwException(e.getMessage());
BusinessException.throwException(e.getMessage());
}
Provider.getLPorts().remove(datasource.getId());
if (Provider.getSessions().get(datasource.getId()) != null) {
@ -1438,13 +1435,13 @@ public class CalciteProvider extends Provider {
CalciteConnection calciteConnection = connection.unwrap(CalciteConnection.class);
SchemaPlus rootSchema = calciteConnection.getRootSchema();
if (rootSchema.getSubSchema(String.format(SQLConstants.SCHEMA, dsId)) == null) {
DEException.throwException(Translator.get("i18n_check_datasource_connection"));
BusinessException.throwException(Translator.get("i18n_check_datasource_connection"));
}
JdbcSchema jdbcSchema = rootSchema.getSubSchema(String.format(SQLConstants.SCHEMA, dsId)).unwrap(JdbcSchema.class);
BasicDataSource basicDataSource = (BasicDataSource) jdbcSchema.getDataSource();
return basicDataSource.getConnection();
} catch (Exception e) {
DEException.throwException(Translator.get("i18n_invalid_connection") + e.getMessage());
BusinessException.throwException(Translator.get("i18n_invalid_connection") + e.getMessage());
}
return null;
}

View File

@ -23,13 +23,13 @@ import java.util.Map;
public class JwtUtils {
@Value("${spring.security.jwt.secret}")
private String secret;
private String jwtSecret;
@Value("${spring.security.jwt.expiration-ms}")
private Long expirationMs;
private Long jwtExpirationMs;
@Value("${spring.security.jwt.refresh-expiration-ms}")
private Long refreshExpirationMs;
private Long jwtRefreshExpirationMs;
/**
* 生成JWT令牌
@ -54,7 +54,7 @@ public class JwtUtils {
*/
private String createToken(Map<String, Object> claims, String subject) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expirationMs);
Date expiryDate = new Date(now.getTime() + jwtExpirationMs);
return Jwts.builder()
.setClaims(claims)
@ -70,7 +70,7 @@ public class JwtUtils {
*/
private String createRefreshToken(Map<String, Object> claims, String subject) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + refreshExpirationMs);
Date expiryDate = new Date(now.getTime() + jwtRefreshExpirationMs);
return Jwts.builder()
.setClaims(claims)
@ -182,7 +182,7 @@ public class JwtUtils {
* @return 签名密钥
*/
private SecretKey getSigningKey() {
byte[] keyBytes = secret.getBytes();
byte[] keyBytes = jwtSecret.getBytes();
return Keys.hmacShaKeyFor(keyBytes);
}

View File

@ -82,4 +82,4 @@ public class PasswordUtils {
return hasUpper && hasLower && hasDigit && hasSpecial;
}
}
}

View File

@ -3,6 +3,7 @@ DB_URL=jdbc:mysql://your-host:3306/your-db
DB_USERNAME=your-username
DB_PASSWORD=your-password
# JWT配置
JWT_SECRET=your-super-secret-key
JWT_EXPIRATION=86400000

View File

@ -33,19 +33,6 @@ spring:
max-lifetime: 1800000
connection-timeout: 30000
connection-test-query: SELECT 1
# url: jdbc:sqlite:D:/Trae_space/StdProject/backend/db/project.db
# username: # SQLite 不需要用户名
# password: # SQLite 不需要密码
# driver-class-name: org.sqlite.JDBC
# hikari:
# pool-name: StdProjectHikariCP
# minimum-idle: 5
# maximum-pool-size: 20
# auto-commit: true
# idle-timeout: 30000
# max-lifetime: 1800000
# connection-timeout: 30000
# connection-test-query: SELECT 1
cache:
jcache:
@ -55,10 +42,10 @@ spring:
allowed-origins: ${CORS_ALLOWED_ORIGINS:http://localhost:3000,http://localhost:8080}
max-age: ${CORS_MAX_AGE:3600} # 预检请求的缓存时间(秒)
jwt:
enabled: ${JWT_ENABLED:true} # 控制是否启用JWT认证
enabled: ${JWT_ENABLED:false} # 控制是否启用JWT认证
secret: ${JWT_SECRET:YourJWTSecretKeyForStdProjectBackendApplicationWhichIsVeryLongAndSecure2024!@#$%^&*()}
expiration-ms: ${JWT_EXPIRATION:1800000} # Token 过期时间 (例如: 24小时)
refresh-expiration-ms: ${JWT_REFRESH_EXPIRATION:1800000} # 刷新Token过期时间 (例如: 30分钟)
expiration-ms: ${JWT_EXPIRATION:86400000} # Token 过期时间 (例如: 24小时)
refresh-expiration-ms: ${JWT_REFRESH_EXPIRATION:604800000} # 刷新Token过期时间 (例如: 7天)
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml # MyBatis Mapper XML文件位置

View File

@ -0,0 +1,2 @@
login.validator.name1=
login.validator.pwd1=

View File

@ -0,0 +1,205 @@
login.validator.name1=Username/Email/Phone number cannot be empty
login.validator.pwd1=Password cannot be empty
i18n_menu.home=Home
i18n_menu.workbranch=Workbench
i18n_menu.visualized=Visualization
i18n_menu.template=Template
i18n_menu.application=Application
i18n_menu.system=System Management
i18n_menu.template-market=Template Market
i18n_menu.template-setting=Template Management
i18n_menu.view=Data Display
i18n_menu.data=Data Preparation
i18n_menu.panel=Dashboard
i18n_menu.data-filling-manage=Data Reporting
i18n_menu.screen=Data Screen
i18n_menu.dataset=Dataset
i18n_menu.datasource=Data Source
i18n_menu.user=User Management
i18n_menu.org=Organization Management
i18n_menu.auth=Permission Configuration
i18n_menu.report=Scheduled Report
i18n_menu.sync=Synchronization Management
i18n_menu.association=Bloodline Analysis
i18n_menu.threshold=Alert Management
i18n_menu.webhook=Webhook Management
i18n_menu.summary=Overview
i18n_menu.ds=Data Connection Management
i18n_menu.task=Task Management
i18n_menu.embedded=Embedded Management
i18n_menu.plugin=Plugin Management
i18n_menu.platform=Platform Integration
i18n_menu.appearance=Appearance Configuration
i18n_menu.sysVariable=System Variables
i18n_menu.sysTypeface=Font Management
i18n_menu.font=Font Management
i18n_menu.msg-fill=Reporting Task
i18n_field_name_repeat=Duplicate field name:
i18n_pid_not_eq_id=The target for moving cannot be itself or its subdirectory
i18n_ds_name_exists=Name is duplicated under this group
i18n_table_id_can_not_empty=Query node cannot be empty
i18n_no_fields=Fields cannot be empty
i18n_no_field=The field does not exist
i18n_union_ds_no_checked=No fields are selected
i18n_field_name_duplicated=Field name is duplicated
i18n_can_not_cross_ds=Cross dataset operations are not supported
i18n_dataset_ds_error=The data source used by the current dataset has been deleted
i18n_union_field_can_not_empty=Association field cannot be empty
i18n_table_duplicate=The same node needs to be dragged in again to continue creating a new dataset
i18n_no_column_permission=No column permissions
i18n_fetch_error=SQL execution failed, please check if the table, fields, association relationships, etc., are correct and edit again.
i18n_no_datasource_permission=No data source access permissions
i18n_no_dataset_permission=No dataset access permissions
i18n_not_full=The current data source does not support full join
i18n_field_circular_ref=Fields have circular references
i18n_chart_not_handler=Unable to handle this chart type
i18n_chart_delete=Chart does not exist
i18n_no_ds=Dataset does not exist or no permissions
i18n_datasource_delete=Data source does not exist
i18n_gauge_field_change=The field used has changed, please edit again
i18n_gauge_field_delete=The field used has been deleted, please edit again
i18n_no_id=ID cannot be empty
i18n_name_limit_100=Name cannot exceed 100 characters
i18n_field_circular_error=Field parsing error, possible reasons: field has been deleted, calculation field reference level is too deep, circular references exist, etc., please check the table nodes and fields and edit again.
i18n_invalid_ds=Data source is invalid
i18n_user_disable=User has been disabled and cannot log in
i18n_login_name_pwd_err=Username or password is incorrect
i18n_error_login_type=Login type error
i18n_schema_is_empty=Schema is empty!
i18n_table_name_repeat=Name is duplicated:
i18n_sql_not_empty=SQL cannot be empty
i18n_menu.parameter=System Parameters
i18n_user_old_pwd_error=Original password is incorrect
i18n_menu.toolbox-log=Operation Logs
i18n_year=Year
i18n_month=Month
i18n_day=Day
i18n_hour=Hour
i18n_minute=Minute
i18n_second=Second
i18n_no_datasource_permission_to_create_column=No data source access permissions, unable to create table fields
i18n_df_folder_cannot_to_search=Folders cannot query data
i18n_df_no_primary_key=No primary key
i18n_df_cannot_operate_folder=Cannot operate on folders
i18n_df_cannot_be_none=[%s] cannot be empty
i18n_df_value_cannot_be_none=[%s] value: %s cannot be empty
i18n_df_value_exists_in_database=[%s] value: %s already exists in the database, cannot be duplicated
i18n_df_data=Data
i18n_df_start=Start
i18n_df_end=End
i18n_df_datasource_not_found=No data source found
i18n_df_datasource_does_not_enable_data_filling=This data source has not enabled data reporting configuration
i18n_df_builtin_datasource=Built-in database
i18n_df_folder_required=Folder is required
i18n_df_form_not_exists=Form does not exist
i18n_df_name_can_not_empty=Name cannot be empty
i18n_df_template=Template
i18n_df_task_status_is_null_or_finished=Task status is null or completed
i18n_df_task_need_task_id=Task ID must be specified
i18n_df_not_current_task_user=Not the target user for the current task
i18n_df_miss_parameter=Missing parameter
i18n_df_no_running_instance=No running instances for the current task
i18n_df_value=Value
i18n_df_format_error=Format parsing error
i18n_df_cannot_earlier_than=Cannot be earlier than
i18n_df_cannot_be_all_null=Cannot have only one empty
i18n_df_value_not_in_range=Value is not within the range
i18n_df_value_value_not_in_range=Value: %s is not within the range
i18n_df_required=Required
i18n_df_must_unique=Duplicate values are not allowed
i18n_df_excel_parsing_error=Excel parsing error
i18n_df_excel_is_empty=This Excel has no data
i18n_df_excel_template_column_not_fit=Template field count does not match
i18n_df_selection=Option value is
i18n_df_date_format=Date format
i18n_df_integer=Integer
i18n_df_decimal=Decimal
i18n_df_multiple_value_split=Multiple values are separated by semicolon "; "
i18n_df_email_type=Email format
i18n_df_phone_type=Phone number format
i18n_df_lt_check=Value needs to be less than %s: %s
i18n_df_gt_check=Value needs to be greater than %s: %s
i18n_df_le_check=Value needs to be less than or equal to %s: %s
i18n_df_ge_check=Value needs to be greater than or equal to %s: %s
i18n_df_column_exists=The column: %s exists
i18n_wrong_email=Email format is incorrect
i18n_wrong_tel=Phone number format is incorrect
i18n_copilot_cross_ds_error=Cross-source datasets do not support this feature
i18n_template_recommend=Recommended
i18n_template_recent=Recently Used
i18n_default_org=Default Organization
i18n_org_admin=Organization Admin
i18n_ordinary_role=Ordinary User
i18n_sys_admin=System Admin
i18n_threshold_logic_eq=Equal to
i18n_threshold_logic_not_eq=Not equal to
i18n_threshold_logic_lt=Less than
i18n_threshold_logic_le=Less than or equal to
i18n_threshold_logic_gt=Greater than
i18n_threshold_logic_ge=Greater than or equal to
i18n_threshold_logic_in=Belong to
i18n_threshold_logic_not_in=Do not belong to
i18n_threshold_logic_like=Contains
i18n_threshold_logic_not_like=Does not contain
i18n_threshold_logic_null=Empty
i18n_threshold_logic_not_null=Not empty
i18n_threshold_logic_empty=Empty string
i18n_threshold_logic_not_empty=Non-empty string
i18n_threshold_logic_between=Range is
i18n_threshold_logic_and=And
i18n_threshold_logic_or=Or
i18n_threshold_max=Maximum value
i18n_threshold_min=Minimum value
i18n_threshold_average=Average value
i18n_time_year=Year
i18n_time_month=Month
i18n_time_date=Day
i18n_time_hour=Hour
i18n_time_minute=Minute
i18n_time_second=Second
i18n_time_ago=Ago
i18n_time_later=Later
i18n_time_year_current=Current year
i18n_time_year_last=Last year
i18n_time_year_next=Next year
i18n_time_month_current=Current month
i18n_time_month_last=Last month
i18n_time_month_next=Next month
i18n_time_month_start=Beginning of the year
i18n_time_month_end=End of the year
i18n_time_date_current=Today
i18n_time_date_last=Yesterday
i18n_time_date_next=Tomorrow
i18n_time_date_start=Beginning of the month
i18n_time_date_end=End of the month
i18n_dataset_create_error=Create error,please try again
i18n_dataset_ds_delete=Datasource deleted,this dataset can not be show
i18n_dataset_plugin_error=Datasource plugin is not exist
i18n_dataset_cross_error=Dataset with more than two data sources is not supported
i18n_board=Board
i18n_invalid_connection=Invalid connection.
i18n_check_datasource_connection=Please check the validity of the datasource.
i18n_datasource_not_exists=Datasource not exists!
i18n_geo_exists=An area with the same name already exists\uFF01
i18n_geo_sub_exists=A sub-area with the same name already exists\uFF01
i18n_user_new_pwd_error=Password format: 8-20 characters and must include at least one uppercase letter, one lowercase letter, one number, and one special character.
i18n_user_pwd_same_error=Old and new passwords cannot be the same
i18n_copilot_ds=Only supports MySQL datasource

View File

@ -0,0 +1,204 @@
login.validator.name1=\u8D26\u53F7/\u90AE\u7BB1/\u624B\u673A\u53F7\u4E0D\u80FD\u4E3A\u7A7A
login.validator.pwd1=\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A
i18n_menu.home=\u9996\u9875
i18n_menu.workbranch=\u5DE5\u4F5C\u53F0
i18n_menu.visualized=\u53EF\u89C6\u5316
i18n_menu.template=\u6A21\u7248
i18n_menu.application=\u5E94\u7528
i18n_menu.system=\u7CFB\u7EDF\u7BA1\u7406
i18n_menu.template-market=\u6A21\u677F\u5E02\u573A
i18n_menu.template-setting=\u6A21\u677F\u7BA1\u7406
i18n_menu.view=\u6570\u636E\u5C55\u793A
i18n_menu.data=\u6570\u636E\u51C6\u5907
i18n_menu.panel=\u4EEA\u8868\u677F
i18n_menu.data-filling-manage=\u6570\u636E\u586B\u62A5
i18n_menu.screen=\u6570\u636E\u5927\u5C4F
i18n_menu.dataset=\u6570\u636E\u96C6
i18n_menu.datasource=\u6570\u636E\u6E90
i18n_menu.user=\u7528\u6237\u7BA1\u7406
i18n_menu.org=\u7EC4\u7EC7\u7BA1\u7406
i18n_menu.auth=\u6743\u9650\u914D\u7F6E
i18n_menu.report=\u5B9A\u65F6\u62A5\u544A
i18n_menu.sync=\u540C\u6B65\u7BA1\u7406
i18n_menu.association=\u8840\u7F18\u5206\u6790
i18n_menu.threshold=\u544A\u8B66\u7BA1\u7406
i18n_menu.webhook=Webhook \u7BA1\u7406
i18n_menu.summary=\u6982\u89C8
i18n_menu.ds=\u6570\u636E\u8FDE\u63A5\u7BA1\u7406
i18n_menu.task=\u4EFB\u52A1\u7BA1\u7406
i18n_menu.embedded=\u5D4C\u5165\u5F0F\u7BA1\u7406
i18n_menu.plugin=\u63D2\u4EF6\u7BA1\u7406
i18n_menu.platform=\u5E73\u53F0\u5BF9\u63A5
i18n_menu.appearance=\u5916\u89C2\u914D\u7F6E
i18n_menu.sysVariable=\u7CFB\u7EDF\u53D8\u91CF
i18n_menu.sysTypeface=\u5B57\u4F53\u7BA1\u7406
i18n_menu.font=\u5B57\u4F53\u7BA1\u7406
i18n_menu.msg-fill=\u586B\u62A5\u4EFB\u52A1
i18n_field_name_repeat=\u6709\u91CD\u590D\u5B57\u6BB5\u540D\uFF1A
i18n_pid_not_eq_id=\u79FB\u52A8\u76EE\u6807\u4E0D\u80FD\u662F\u81EA\u5DF1\u6216\u5B50\u76EE\u5F55
i18n_ds_name_exists=\u8BE5\u5206\u7EC4\u4E0B\u540D\u79F0\u91CD\u590D
i18n_table_id_can_not_empty=\u67E5\u8BE2\u8282\u70B9\u4E0D\u80FD\u4E3A\u7A7A
i18n_no_fields=\u5B57\u6BB5\u4E0D\u80FD\u4E3A\u7A7A
i18n_no_field=\u8BE5\u5B57\u6BB5\u4E0D\u5B58\u5728
i18n_union_ds_no_checked=\u6CA1\u6709\u5B57\u6BB5\u9009\u4E2D
i18n_field_name_duplicated=\u5B57\u6BB5\u540D\u91CD\u590D
i18n_can_not_cross_ds=\u4E0D\u652F\u6301\u8DE8\u6570\u636E\u96C6\u64CD\u4F5C
i18n_dataset_ds_error=\u5F53\u524D\u6570\u636E\u96C6\u7528\u5230\u7684\u6570\u636E\u6E90\u5DF2\u88AB\u5220\u9664
i18n_union_field_can_not_empty=\u5173\u8054\u5B57\u6BB5\u4E0D\u80FD\u4E3A\u7A7A
i18n_table_duplicate=\u76F8\u540C\u8282\u70B9\u9700\u91CD\u65B0\u62D6\u5165\u624D\u80FD\u7EE7\u7EED\u65B0\u5EFA\u6570\u636E\u96C6
i18n_no_column_permission=\u6CA1\u6709\u5217\u6743\u9650
i18n_fetch_error=SQL\u6267\u884C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8868\u3001\u5B57\u6BB5\u3001\u5173\u8054\u5173\u7CFB\u7B49\u4FE1\u606F\u662F\u5426\u6B63\u786E\u5E76\u91CD\u65B0\u7F16\u8F91\u3002
i18n_no_datasource_permission=\u65E0\u6570\u636E\u6E90\u8BBF\u95EE\u6743\u9650
i18n_no_dataset_permission=\u65E0\u6570\u636E\u96C6\u8BBF\u95EE\u6743\u9650
i18n_not_full=\u5F53\u524D\u6570\u636E\u6E90\u4E0D\u652F\u6301\u5168\u8FDE\u63A5
i18n_field_circular_ref=\u5B57\u6BB5\u5B58\u5728\u5FAA\u73AF\u5F15\u7528
i18n_chart_not_handler=\u65E0\u6CD5\u5904\u7406\u8BE5\u56FE\u8868\u7C7B\u578B
i18n_chart_delete=\u56FE\u8868\u4E0D\u5B58\u5728
i18n_no_ds=\u6570\u636E\u96C6\u4E0D\u5B58\u5728\u6216\u6CA1\u6709\u6743\u9650
i18n_datasource_delete=\u6570\u636E\u6E90\u4E0D\u5B58\u5728
i18n_gauge_field_change=\u6240\u7528\u5B57\u6BB5\u53D1\u751F\u53D8\u66F4\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8F91
i18n_gauge_field_delete=\u6240\u7528\u5B57\u6BB5\u5DF2\u5220\u9664\uFF0C\u8BF7\u91CD\u65B0\u7F16\u8F91
i18n_no_id=id\u4E0D\u80FD\u4E3A\u7A7A
i18n_name_limit_100=\u540D\u79F0\u4E0D\u80FD\u8D85\u8FC7100\u5B57\u7B26
i18n_field_circular_error=\u5B57\u6BB5\u89E3\u6790\u9519\u8BEF\uFF0C\u53EF\u80FD\u539F\u56E0\uFF1A\u5B57\u6BB5\u5DF2\u5220\u9664\u3001\u8BA1\u7B97\u5B57\u6BB5\u5F15\u7528\u5C42\u7EA7\u8FC7\u6DF1\u3001\u5B58\u5728\u5FAA\u73AF\u5F15\u7528\u7B49\uFF0C\u8BF7\u68C0\u67E5\u8868\u8282\u70B9\u548C\u5B57\u6BB5\u5E76\u91CD\u65B0\u7F16\u8F91\u3002
i18n_invalid_ds=\u6570\u636E\u6E90\u65E0\u6548
i18n_user_disable=\u7528\u6237\u5DF2\u88AB\u7981\u7528\uFF0C\u65E0\u6CD5\u767B\u5F55
i18n_login_name_pwd_err=\u7528\u6237\u540D\u6216\u5BC6\u7801\u9519\u8BEF
i18n_error_login_type=\u767B\u5F55\u7C7B\u578B\u9519\u8BEF
i18n_schema_is_empty=schema \u4E3A\u7A7A\uFF01
i18n_table_name_repeat=\u540D\u79F0\u91CD\u590D:
i18n_sql_not_empty=sql \u4E0D\u80FD\u4E3A\u7A7A
i18n_menu.parameter=\u7CFB\u7EDF\u53C2\u6570
i18n_user_old_pwd_error=\u539F\u59CB\u5BC6\u7801\u9519\u8BEF
i18n_menu.toolbox-log=\u64CD\u4F5C\u65E5\u5FD7
i18n_year=\u5E74
i18n_month=\u6708
i18n_day=\u5929
i18n_hour=\u5C0F\u65F6
i18n_minute=\u5206\u949F
i18n_second=\u79D2
i18n_no_datasource_permission_to_create_column=\u65E0\u6570\u636E\u6E90\u8BBF\u95EE\u6743\u9650\uFF0C\u65E0\u6CD5\u521B\u5EFA\u8868\u5B57\u6BB5
i18n_df_folder_cannot_to_search=\u6587\u4EF6\u5939\u4E0D\u80FD\u67E5\u8BE2\u6570\u636E
i18n_df_no_primary_key=\u6CA1\u6709\u4E3B\u952E
i18n_df_cannot_operate_folder=\u4E0D\u80FD\u64CD\u4F5C\u6587\u4EF6\u5939
i18n_df_cannot_be_none=[%s] \u4E0D\u80FD\u4E3A\u7A7A
i18n_df_value_cannot_be_none=[%s] \u503C: %s \u4E0D\u80FD\u4E3A\u7A7A
i18n_df_value_exists_in_database=[%s] \u503C: %s \u5728\u6570\u636E\u5E93\u4E2D\u5DF2\u5B58\u5728, \u4E0D\u80FD\u91CD\u590D
i18n_df_data=\u6570\u636E
i18n_df_start=\u5F00\u59CB
i18n_df_end=\u7ED3\u675F
i18n_df_datasource_not_found=\u6CA1\u6709\u627E\u5230\u6570\u636E\u6E90
i18n_df_datasource_does_not_enable_data_filling=\u8BE5\u6570\u636E\u6E90\u6CA1\u6709\u542F\u7528\u6570\u636E\u586B\u62A5\u914D\u7F6E
i18n_df_builtin_datasource=\u5185\u5EFA\u6570\u636E\u5E93
i18n_df_folder_required=\u76EE\u5F55\u5FC5\u9009
i18n_df_form_not_exists=\u8868\u5355\u4E0D\u5B58\u5728
i18n_df_name_can_not_empty=\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
i18n_df_template=\u6A21\u677F
i18n_df_task_status_is_null_or_finished=\u4EFB\u52A1\u72B6\u6001\u4E3A\u7A7A\u6216\u5DF2\u5B8C\u6210
i18n_df_task_need_task_id=\u9700\u6307\u5B9A\u4EFB\u52A1ID
i18n_df_not_current_task_user=\u4E0D\u662F\u5F53\u524D\u4EFB\u52A1\u7684\u76EE\u6807\u7528\u6237
i18n_df_miss_parameter=\u7F3A\u5931\u53C2\u6570
i18n_df_no_running_instance=\u5F53\u524D\u4EFB\u52A1\u6682\u65F6\u65E0\u8FD0\u884C\u5B9E\u4F8B
i18n_df_value=\u503C
i18n_df_format_error=\u683C\u5F0F\u89E3\u6790\u9519\u8BEF
i18n_df_cannot_earlier_than=\u4E0D\u80FD\u65E9\u4E8E
i18n_df_cannot_be_all_null=\u4E0D\u80FD\u53EA\u6709\u4E00\u4E2A\u4E3A\u7A7A
i18n_df_value_not_in_range=\u503C\u4E0D\u5728\u8303\u56F4\u5185
i18n_df_value_value_not_in_range=\u503C: %s \u4E0D\u5728\u8303\u56F4\u5185
i18n_df_required=\u5FC5\u586B
i18n_df_must_unique=\u4E0D\u5141\u8BB8\u91CD\u590D\u503C
i18n_df_excel_parsing_error=Excel\u89E3\u6790\u9519\u8BEF
i18n_df_excel_is_empty=\u8BE5Excel\u6CA1\u6709\u6570\u636E
i18n_df_excel_template_column_not_fit=\u6A21\u677F\u5B57\u6BB5\u4E2A\u6570\u4E0D\u5339\u914D
i18n_df_selection=\u9009\u9879\u503C\u4E3A
i18n_df_date_format=\u65E5\u671F\u683C\u5F0F
i18n_df_integer=\u6574\u5F62\u6570\u5B57
i18n_df_decimal=\u5C0F\u6570\u6570\u5B57
i18n_df_multiple_value_split=\u591A\u4E2A\u503C\u4F7F\u7528\u5206\u53F7";"\u5206\u5272
i18n_df_email_type=\u90AE\u7BB1\u683C\u5F0F
i18n_df_phone_type=\u624B\u673A\u53F7\u683C\u5F0F
i18n_df_lt_check=\u503C\u9700\u8981\u5C0F\u4E8E %s: %s
i18n_df_gt_check=\u503C\u9700\u8981\u5927\u4E8E %s: %s
i18n_df_le_check=\u503C\u9700\u8981\u5C0F\u4E8E\u7B49\u4E8E %s: %s
i18n_df_ge_check=\u503C\u9700\u8981\u5927\u4E8E\u7B49\u4E8E %s: %s
i18n_df_column_exists=\u5B57\u6BB5: %s \u5DF2\u5B58\u5728
i18n_wrong_email=\u90AE\u7BB1\u683C\u5F0F\u9519\u8BEF
i18n_wrong_tel=\u624B\u673A\u53F7\u7801\u683C\u5F0F\u9519\u8BEF
i18n_copilot_cross_ds_error=\u8DE8\u6E90\u6570\u636E\u96C6\u4E0D\u652F\u6301\u8BE5\u529F\u80FD
i18n_template_recommend=\u63A8\u8350
i18n_template_recent=\u6700\u8FD1\u4F7F\u7528
i18n_default_org=\u9ED8\u8BA4\u7EC4\u7EC7
i18n_org_admin=\u7EC4\u7EC7\u7BA1\u7406\u5458
i18n_ordinary_role=\u666E\u901A\u7528\u6237
i18n_sys_admin=\u7CFB\u7EDF\u7BA1\u7406\u5458
i18n_threshold_logic_eq=\u7B49\u4E8E
i18n_threshold_logic_not_eq=\u4E0D\u7B49\u4E8E
i18n_threshold_logic_lt=\u5C0F\u4E8E
i18n_threshold_logic_le=\u5C0F\u4E8E\u7B49\u4E8E
i18n_threshold_logic_gt=\u5927\u4E8E
i18n_threshold_logic_ge=\u5927\u4E8E\u7B49\u4E8E
i18n_threshold_logic_in=\u5C5E\u4E8E
i18n_threshold_logic_not_in=\u4E0D\u5C5E\u4E8E
i18n_threshold_logic_like=\u5305\u542B
i18n_threshold_logic_not_like=\u4E0D\u5305\u542B
i18n_threshold_logic_null=\u7A7A
i18n_threshold_logic_not_null=\u4E0D\u7A7A
i18n_threshold_logic_empty=\u7A7A\u5B57\u7B26\u4E32
i18n_threshold_logic_not_empty=\u975E\u7A7A\u5B57\u7B26\u4E32
i18n_threshold_logic_between=\u8303\u56F4\u662F
i18n_threshold_logic_and=\u4E14
i18n_threshold_logic_or=\u6216
i18n_threshold_max=\u6700\u5927\u503C
i18n_threshold_min=\u6700\u5C0F\u503C
i18n_threshold_average=\u5E73\u5747\u503C
i18n_time_year=\u5E74
i18n_time_month=\u6708
i18n_time_date=\u65E5
i18n_time_hour=\u65F6
i18n_time_minute=\u5206
i18n_time_second=\u79D2
i18n_time_ago=\u524D
i18n_time_later=\u540E
i18n_time_year_current=\u5F53\u5E74
i18n_time_year_last=\u53BB\u5E74
i18n_time_year_next=\u660E\u5E74
i18n_time_month_current=\u5F53\u6708
i18n_time_month_last=\u4E0A\u4E2A\u6708
i18n_time_month_next=\u4E0B\u4E2A\u6708
i18n_time_month_start=\u5E74\u521D
i18n_time_month_end=\u5E74\u672B
i18n_time_date_current=\u4ECA\u5929
i18n_time_date_last=\u6628\u5929
i18n_time_date_next=\u660E\u5929
i18n_time_date_start=\u6708\u521D
i18n_time_date_end=\u6708\u672B
i18n_dataset_create_error=\u6570\u636E\u96C6\u56E0\u5F02\u5E38\u5BFC\u81F4\u65E0\u6CD5\u4F7F\u7528\uFF0C\u8BF7\u91CD\u65B0\u521B\u5EFA
i18n_dataset_ds_delete=\u7531\u4E8E\u6570\u636E\u96C6\u6240\u7528\u7684\u6570\u636E\u6E90\u5DF2\u88AB\u5220\u9664,\u65E0\u6CD5\u663E\u793A\u6570\u636E\u96C6
i18n_dataset_plugin_error=\u5F53\u524D\u6570\u636E\u6E90\u63D2\u4EF6\u4E0D\u5B58\u5728
i18n_dataset_cross_error=\u8DE8\u6E90\u6570\u636E\u96C6\u4E0D\u652F\u6301\u8BE5\u529F\u80FD
i18n_board=\u8FB9\u6846
i18n_invalid_connection=\u8FDE\u63A5\u65E0\u6548,
i18n_check_datasource_connection=\u8BF7\u68C0\u67E5\u6570\u636E\u6E90\u7684\u6709\u6548\u6027
i18n_datasource_not_exists=\u6570\u636E\u6E90\u4E0D\u5B58\u5728\uFF01
i18n_geo_exists=\u5DF2\u5B58\u5728\u540C\u540D\u533A\u57DF\uFF01
i18n_geo_sub_exists=\u5DF2\u5B58\u5728\u540C\u540D\u5B50\u533A\u57DF\uFF01
i18n_user_new_pwd_error=\u5BC6\u7801\u683C\u5F0F\uFF1A8-20\u4F4D\u4E14\u81F3\u5C11\u4E00\u4F4D\u5927\u5199\u5B57\u6BCD\u3001\u5C0F\u5199\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u7279\u6B8A\u5B57\u7B26
i18n_user_pwd_same_error=\u65B0\u65E7\u5BC6\u7801\u4E0D\u80FD\u76F8\u540C
i18n_copilot_ds=\u5F53\u524D\u4EC5\u652F\u6301MySQL\u6570\u636E\u6E90

View File

@ -0,0 +1,5 @@
# 默认国际化资源文件
i18n_fetch_error=数据获取错误:
i18n_schema_is_empty=数据库架构为空
i18n_check_datasource_connection=数据源连接检查失败
i18n_invalid_connection=无效的数据库连接: