diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..111a596 --- /dev/null +++ b/backend/.env.example @@ -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 \ No newline at end of file diff --git a/backend/libs/sdk-bundle-2.0.jar b/backend/libs/sdk-bundle-2.0.jar new file mode 100644 index 0000000..3384ade Binary files /dev/null and b/backend/libs/sdk-bundle-2.0.jar differ diff --git a/backend/pom.xml b/backend/pom.xml index ba55f4b..556a08d 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -21,8 +21,9 @@ 3.0.3 3.5.6 0.11.5 - 2.0.2 - 1.35.18 + 2.3.0 + 1.32.0 + 33.0.0-jre @@ -57,6 +58,13 @@ 2.0.0 system ${project.basedir}/libs/sdk-bundle-2.0.jar + + + + + org.apache.calcite + calcite-core + ${calcite-core.version} @@ -64,6 +72,7 @@ mybatis-spring ${mybatis-spring.version} + com.baomidou mybatis-plus-boot-starter @@ -76,6 +85,7 @@ 8.0.30 runtime + org.xerial sqlite-jdbc @@ -107,27 +117,31 @@ ${springdoc.version} - - org.projectlombok - lombok - true - org.springframework.boot spring-boot-starter-test test + org.springframework.security spring-security-test test + org.projectlombok lombok 1.18.34 provided + + + + cn.hutool + hutool-all + 5.8.8 + @@ -147,4 +161,4 @@ - \ No newline at end of file + diff --git a/backend/src/main/java/com/stdproject/common/FilterCondition.java b/backend/src/main/java/com/stdproject/common/FilterCondition.java new file mode 100644 index 0000000..ca9b61f --- /dev/null +++ b/backend/src/main/java/com/stdproject/common/FilterCondition.java @@ -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; + +} diff --git a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java index ee8485e..82547b3 100644 --- a/backend/src/main/java/com/stdproject/common/OperationLogAspect.java +++ b/backend/src/main/java/com/stdproject/common/OperationLogAspect.java @@ -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; } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/common/PageRequest.java b/backend/src/main/java/com/stdproject/common/PageRequest.java index 8495a86..136817c 100644 --- a/backend/src/main/java/com/stdproject/common/PageRequest.java +++ b/backend/src/main/java/com/stdproject/common/PageRequest.java @@ -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 filters; + /** * 获取偏移量 * @@ -69,4 +74,4 @@ public class PageRequest { public boolean isDesc() { return "desc".equalsIgnoreCase(orderDirection); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/common/QueryWrapperBuilder.java b/backend/src/main/java/com/stdproject/common/QueryWrapperBuilder.java new file mode 100644 index 0000000..e230d79 --- /dev/null +++ b/backend/src/main/java/com/stdproject/common/QueryWrapperBuilder.java @@ -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 QueryWrapper buildQueryWrapper(PageRequest pageRequest) { + QueryWrapper 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 ALLOWED_ORDER_FIELDS = Set.of( + "username", "nickname", "email", "phone", "lastmodifydate", "age" + ); + + private static boolean isAllowedOrderField(String field) { + return ALLOWED_ORDER_FIELDS.contains(field); + } + +} diff --git a/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java b/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java index 5e277fb..5d3aaca 100644 --- a/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/stdproject/config/JwtAuthenticationFilter.java @@ -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); } diff --git a/backend/src/main/java/com/stdproject/config/MessageSourceConfig.java b/backend/src/main/java/com/stdproject/config/MessageSourceConfig.java new file mode 100644 index 0000000..3f7e22d --- /dev/null +++ b/backend/src/main/java/com/stdproject/config/MessageSourceConfig.java @@ -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; + } + + } \ No newline at end of file diff --git a/backend/src/main/java/com/stdproject/config/SecurityConfig.java b/backend/src/main/java/com/stdproject/config/SecurityConfig.java index 48a4688..ee0ffd3 100644 --- a/backend/src/main/java/com/stdproject/config/SecurityConfig.java +++ b/backend/src/main/java/com/stdproject/config/SecurityConfig.java @@ -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) { diff --git a/backend/src/main/java/com/stdproject/config/WebConfig.java b/backend/src/main/java/com/stdproject/config/WebConfig.java index e207fd0..7c80abc 100644 --- a/backend/src/main/java/com/stdproject/config/WebConfig.java +++ b/backend/src/main/java/com/stdproject/config/WebConfig.java @@ -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 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/"); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java b/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java index bbce2a5..8f55c14 100644 --- a/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java +++ b/backend/src/main/java/com/stdproject/controller/AppDictionaryController.java @@ -18,6 +18,7 @@ import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.List; +import java.util.Map; /** *

@@ -41,7 +42,7 @@ public class AppDictionaryController { public Result> page(@RequestBody @Valid PageRequest pageRequest) { Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize()); QueryWrapper 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 result = appDictionaryService.page(page, queryWrapper); return Result.success(result); } @@ -72,7 +73,7 @@ public class AppDictionaryController { public Result> list() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByAsc("dictcode", "orderno"); - + List dictionaries = appDictionaryService.list(queryWrapper); return Result.success(dictionaries); } @@ -92,7 +93,7 @@ public class AppDictionaryController { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("dictcode", dictCode); queryWrapper.orderByAsc("orderno"); - + List dictionaries = appDictionaryService.list(queryWrapper); return Result.success(dictionaries); } @@ -106,7 +107,7 @@ public class AppDictionaryController { QueryWrapper 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 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 deleteByDictCode(@Parameter(description = "字典编码") @PathVariable String dictCode) { QueryWrapper 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 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 queryWrapper = new QueryWrapper<>(); queryWrapper.select("DISTINCT dictcode"); queryWrapper.orderByAsc("dictcode"); - + List dictionaries = appDictionaryService.list(queryWrapper); List 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>> getGroupedDictionaries() { + public Result>> getGroupedDictionaries() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByAsc("dictcode", "orderno"); - + List allDictionaries = appDictionaryService.list(queryWrapper); java.util.Map> 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; } } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AppMenuController.java b/backend/src/main/java/com/stdproject/controller/AppMenuController.java index d8093e9..417dd6c 100644 --- a/backend/src/main/java/com/stdproject/controller/AppMenuController.java +++ b/backend/src/main/java/com/stdproject/controller/AppMenuController.java @@ -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> page(@RequestBody @Valid PageRequest pageRequest) { Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize()); QueryWrapper 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 result = appMenuService.page(page, queryWrapper); return Result.success(result); } @@ -74,7 +80,7 @@ public class AppMenuController { public Result> list() { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.orderByAsc("parentid", "orderno"); - + List 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> getMenuTree() { + public Result> getMenuTree(@RequestParam String appId,@RequestParam String name) { QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq(StrUtil.isNotBlank(appId),"app_id", appId); + queryWrapper.like(StrUtil.isNotBlank(name),"name", name); queryWrapper.orderByAsc("parentid", "orderno"); - + List allMenus = appMenuService.list(queryWrapper); List tree = buildMenuTree(allMenus, "0"); return Result.success(tree); @@ -106,7 +114,7 @@ public class AppMenuController { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("parentid", parentId); queryWrapper.orderByAsc("orderno"); - + List 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 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 setIsDisplay( + @Parameter(description = "菜单ID") @PathVariable String id, + @RequestParam String isDisplay) { + LambdaUpdateWrapper 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 setModuleId( + @Parameter(description = "菜单ID") @PathVariable String id, + @RequestParam String moduleId) { + LambdaUpdateWrapper 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 queryWrapper = new QueryWrapper<>(); queryWrapper.eq("type", type); queryWrapper.orderByAsc("parentid", "orderno"); - + List menus = appMenuService.list(queryWrapper); return Result.success(menus); } @@ -253,7 +290,7 @@ public class AppMenuController { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("isdisplay", "1"); queryWrapper.orderByAsc("parentid", "orderno"); - + List displayMenus = appMenuService.list(queryWrapper); List tree = buildMenuTree(displayMenus, "0"); return Result.success(tree); @@ -279,24 +316,24 @@ public class AppMenuController { */ private List buildMenuTree(List 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 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 getChildren() { return children; } - public void setChildren(List 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 getChildren() { + return children; + } + + public void setChildren(List 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; + } } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AppOptLogController.java b/backend/src/main/java/com/stdproject/controller/AppOptLogController.java index dd5a61c..5130ea8 100644 --- a/backend/src/main/java/com/stdproject/controller/AppOptLogController.java +++ b/backend/src/main/java/com/stdproject/controller/AppOptLogController.java @@ -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> page(@RequestBody @Valid PageRequest pageRequest) { Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize()); QueryWrapper 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 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 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 logs = appOptLogService.list(queryWrapper); return Result.success(logs); } @@ -113,9 +117,9 @@ public class AppOptLogController { @Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("module", module); - queryWrapper.orderByDesc("opttime"); + queryWrapper.orderByDesc("logtime"); queryWrapper.last("LIMIT " + limit); - + List 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 queryWrapper = new QueryWrapper<>(); - queryWrapper.between("opttime", startTime, endTime); - queryWrapper.orderByDesc("opttime"); + queryWrapper.between("logtime", startTime, endTime); + queryWrapper.orderByDesc("logtime"); queryWrapper.last("LIMIT " + limit); - + List logs = appOptLogService.list(queryWrapper); return Result.success(logs); } @@ -144,14 +148,13 @@ public class AppOptLogController { @Parameter(description = "限制条数") @RequestParam(defaultValue = "100") Integer limit) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("ip", ip); - queryWrapper.orderByDesc("opttime"); + queryWrapper.orderByDesc("logtime"); queryWrapper.last("LIMIT " + limit); - + List 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 cleanOldLogs(@Parameter(description = "保留天数") @PathVariable Integer days) { - LocalDateTime cutoffTime = LocalDateTime.now().minusDays(days); - QueryWrapper 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 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 getStatistics() { // 总日志数 long totalCount = appOptLogService.count(); - + // 今日日志数 LocalDateTime todayStart = LocalDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0); QueryWrapper 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 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 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 queryWrapper = new QueryWrapper<>(); queryWrapper.orderByDesc("logtime"); queryWrapper.last("LIMIT " + limit); - + List 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; + } } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java b/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java index 1feb644..9e16cf7 100644 --- a/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java +++ b/backend/src/main/java/com/stdproject/controller/AppOrganizationController.java @@ -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; /** *

@@ -39,21 +45,24 @@ public class AppOrganizationController { @Operation(summary = "查询公司结构树") @GetMapping("/getCompanyTree") @OperationLog(type = "06", module = "组织管理", description = "查询公司结构树") - public Result> getCompanyTree() { + public Result> getCompanyTree(@RequestParam String appId) { QueryWrapper 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 children = appOrganizationService.list(queryWrapper); - return Result.success(children); + List organizationTreeNodes = buildOrganizationTree(children, "0"); + return Result.success(organizationTreeNodes); } - @Operation(summary = "查询部门列表") + @Operation(summary = "查询公司/部门列表") @GetMapping("/getDepartmentList") - @OperationLog(type = "06", module = "组织管理", description = "查询部门列表") - public Result> getDepartmentList(@RequestParam String parentid,@RequestParam String keystr) { + @OperationLog(type = "06", module = "组织管理", description = "查询公司/部门列表") + public Result> getDepartmentList(@RequestParam String appId, @RequestParam String orgtype, + @RequestParam String parentid, @RequestParam String keystr) { QueryWrapper 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 setIsValid( + @Parameter(description = "组织ID") @PathVariable String id, + @RequestParam String isValid) { + LambdaUpdateWrapper 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 queryWrapper = new LambdaQueryWrapper() .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 buildOrganizationTree(List 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()); + } -} \ No newline at end of file + /** + * 组织树节点 + */ + @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 children = new ArrayList<>(); + } + +} diff --git a/backend/src/main/java/com/stdproject/controller/AppRoleController.java b/backend/src/main/java/com/stdproject/controller/AppRoleController.java index 6f660a9..710c43d 100644 --- a/backend/src/main/java/com/stdproject/controller/AppRoleController.java +++ b/backend/src/main/java/com/stdproject/controller/AppRoleController.java @@ -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> page(@RequestBody @Valid PageRequest pageRequest) { + public Result> page(@Parameter(description = "用户ID") @PathVariable String appId, + @RequestBody @Valid PageRequest pageRequest) { Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize()); QueryWrapper 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 result = appRoleService.page(page, queryWrapper); return Result.success(result); } @@ -89,7 +91,7 @@ public class AppRoleController { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("isvaild", "1"); queryWrapper.orderByAsc("rolecode"); - + List 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 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 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 queryWrapper = new QueryWrapper<>(); queryWrapper.eq("type", type); queryWrapper.eq("isvaild", "1"); - queryWrapper.orderByAsc("rolecode"); + queryWrapper.orderByAsc("rolecode"); List roles = appRoleService.list(queryWrapper); return Result.success(roles); } @@ -224,18 +226,18 @@ public class AppRoleController { public Result assignMenus( @Parameter(description = "角色ID") @PathVariable String roleId, @RequestBody List menuIds) { - + // 检查角色是否存在 AppRole role = appRoleService.getById(roleId); if (role == null || !"1".equals(role.getIsvaild())) { return Result.error("角色不存在或已禁用"); } - + // 先删除原有的菜单权限 QueryWrapper deleteQuery = new QueryWrapper<>(); deleteQuery.eq("roleid", roleId); appRoleMenuService.remove(deleteQuery); - + // 添加新的菜单权限 if (menuIds != null && !menuIds.isEmpty()) { List 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> getRoleMenus(@Parameter(description = "角色ID") @PathVariable String roleId) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("roleid", roleId); - + List roleMenus = appRoleMenuService.list(queryWrapper); List 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 assignUsers( @Parameter(description = "角色ID") @PathVariable String roleId, @RequestBody List userIds) { - + // 检查角色是否存在 AppRole role = appRoleService.getById(roleId); if (role == null || !"1".equals(role.getIsvaild())) { return Result.error("角色不存在或已禁用"); } - + // 先删除原有的用户角色关联 QueryWrapper deleteQuery = new QueryWrapper<>(); deleteQuery.eq("roleid", roleId); appRoleUserService.remove(deleteQuery); - + // 添加新的用户角色关联 if (userIds != null && !userIds.isEmpty()) { List 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> getRoleUsers(@Parameter(description = "角色ID") @PathVariable String roleId) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("roleid", roleId); - + List roleUsers = appRoleUserService.list(queryWrapper); List userIds = roleUsers.stream() - .map(AppRoleUser::getUserid) - .collect(Collectors.toList()); - + .map(AppRoleUser::getUserid) + .collect(Collectors.toList()); + return Result.success(userIds); } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AppUserController.java b/backend/src/main/java/com/stdproject/controller/AppUserController.java index 15ceb55..cc82f8f 100644 --- a/backend/src/main/java/com/stdproject/controller/AppUserController.java +++ b/backend/src/main/java/com/stdproject/controller/AppUserController.java @@ -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> page(@RequestBody @Valid PageRequest pageRequest) { + public Result> page(@Parameter(description = "用户ID") @PathVariable String orgId, + @Parameter(description = "用户ID") @PathVariable String appId, + @RequestBody @Valid PageRequest pageRequest) { Page page = new Page<>(pageRequest.getCurrent(), pageRequest.getSize()); QueryWrapper 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 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 queryWrapper = new QueryWrapper<>(); queryWrapper.eq("orgid", orgId); queryWrapper.orderByDesc("lastmodifydate"); - + List 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; + } } -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/controller/AuthController.java b/backend/src/main/java/com/stdproject/controller/AuthController.java index dff7e05..b2346de 100644 --- a/backend/src/main/java/com/stdproject/controller/AuthController.java +++ b/backend/src/main/java/com/stdproject/controller/AuthController.java @@ -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> login(@Valid @RequestBody LoginRequest loginRequest, HttpServletRequest request) { + public Result> 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 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 changePassword(@Valid @RequestBody ChangePasswordRequest request, HttpServletRequest httpRequest) { + public Result 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 refreshToken(@RequestBody Map params) { String refreshToken = params.get("refreshToken"); @@ -262,5 +314,9 @@ public class AuthController { } } - + @GetMapping("/getExpirationDateFromToken") + public Result getExpirationDateFromToken(String token) { + Date expirationDateFromToken = jwtUtils.getExpirationDateFromToken(token); + return Result.success(expirationDateFromToken); + } } diff --git a/backend/src/main/java/com/stdproject/controller/DynamicDataController.java b/backend/src/main/java/com/stdproject/controller/DynamicDataController.java index d21f0d0..0e078f4 100644 --- a/backend/src/main/java/com/stdproject/controller/DynamicDataController.java +++ b/backend/src/main/java/com/stdproject/controller/DynamicDataController.java @@ -40,7 +40,6 @@ public class DynamicDataController { return result; } @Operation(summary = "根据主键查询表数据") - @OperationLog(type = "06", module = "动态数据管理", description = "根据主键查询表数据") @PostMapping("getTableDataByPk") public Map getTableDataByPk(Long datasourceId,@RequestParam("whereJson") String whereJson) throws Exception { Map result=dynamicDataService.getTableDataByPk( @@ -73,7 +72,6 @@ public class DynamicDataController { } @Operation(summary = "分页查询表数据") - @OperationLog(type = "06", module = "动态数据管理", description = "分页查询表数据") @PostMapping("queryTableDataPaged") public Page> queryTableDataPaged(Long datasourceId, @RequestParam("queryJson") String queryJson) throws Exception { Page> result = dynamicDataService.queryTableDataPaged( diff --git a/backend/src/main/java/com/stdproject/listener/DataSourceInitStartListener.java b/backend/src/main/java/com/stdproject/listener/DataSourceInitStartListener.java new file mode 100644 index 0000000..20479a7 --- /dev/null +++ b/backend/src/main/java/com/stdproject/listener/DataSourceInitStartListener.java @@ -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 { + @Resource + private CalciteProvider calciteProvider; + @Override + public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { + try { + calciteProvider.initConnectionPool(); + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/backend/src/main/java/com/stdproject/mapper/AppOptLogMapper.java b/backend/src/main/java/com/stdproject/mapper/AppOptLogMapper.java index 5308b74..002134e 100644 --- a/backend/src/main/java/com/stdproject/mapper/AppOptLogMapper.java +++ b/backend/src/main/java/com/stdproject/mapper/AppOptLogMapper.java @@ -15,4 +15,4 @@ import org.apache.ibatis.annotations.Mapper; @Mapper public interface AppOptLogMapper extends BaseMapper { -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/service/IAppOptLogService.java b/backend/src/main/java/com/stdproject/service/IAppOptLogService.java index 7382c2b..9e570ec 100644 --- a/backend/src/main/java/com/stdproject/service/IAppOptLogService.java +++ b/backend/src/main/java/com/stdproject/service/IAppOptLogService.java @@ -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; /** *

@@ -13,4 +14,4 @@ import com.stdproject.entity.AppOptLog; */ public interface IAppOptLogService extends IService { -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/service/impl/AppOptLogServiceImpl.java b/backend/src/main/java/com/stdproject/service/impl/AppOptLogServiceImpl.java index 07d32a6..e8441d8 100644 --- a/backend/src/main/java/com/stdproject/service/impl/AppOptLogServiceImpl.java +++ b/backend/src/main/java/com/stdproject/service/impl/AppOptLogServiceImpl.java @@ -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 implements IAppOptLogService { -} \ No newline at end of file +} diff --git a/backend/src/main/java/com/stdproject/service/impl/DynamicDataServiceImpl.java b/backend/src/main/java/com/stdproject/service/impl/DynamicDataServiceImpl.java index f845ded..9a4fde0 100644 --- a/backend/src/main/java/com/stdproject/service/impl/DynamicDataServiceImpl.java +++ b/backend/src/main/java/com/stdproject/service/impl/DynamicDataServiceImpl.java @@ -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 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 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 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 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 dataMap = JsonUtil.parseObject(condition, Map.class); String tableName = (String) dataMap.get("tableName"); diff --git a/backend/src/main/java/com/stdproject/service/manage/EngineManage.java b/backend/src/main/java/com/stdproject/service/manage/EngineManage.java index f05885b..829f6ab 100644 --- a/backend/src/main/java/com/stdproject/service/manage/EngineManage.java +++ b/backend/src/main/java/com/stdproject/service/manage/EngineManage.java @@ -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 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 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 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"); diff --git a/backend/src/main/java/com/stdproject/service/provider/CalciteProvider.java b/backend/src/main/java/com/stdproject/service/provider/CalciteProvider.java index f7d61bb..79ff034 100644 --- a/backend/src/main/java/com/stdproject/service/provider/CalciteProvider.java +++ b/backend/src/main/java/com/stdproject/service/provider/CalciteProvider.java @@ -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 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 fetchResultField(DatasourceRequest datasourceRequest) throws DEException { + public Map 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 dsMap) throws DEException { + public String transSqlDialect(String sql, Map 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 fetchTableField(DatasourceRequest datasourceRequest) throws DEException { + public List fetchTableField(DatasourceRequest datasourceRequest) throws BusinessException { List 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 jdbcFetchResultField(DatasourceRequest datasourceRequest) throws DEException { + public Map 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 getTablesSql(DatasourceRequest datasourceRequest) throws DEException { + private List getTablesSql(DatasourceRequest datasourceRequest) throws BusinessException { List 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 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; } diff --git a/backend/src/main/java/com/stdproject/utils/JwtUtils.java b/backend/src/main/java/com/stdproject/utils/JwtUtils.java index 4f6382d..6d9c921 100644 --- a/backend/src/main/java/com/stdproject/utils/JwtUtils.java +++ b/backend/src/main/java/com/stdproject/utils/JwtUtils.java @@ -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 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 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); } diff --git a/backend/src/main/java/com/stdproject/utils/PasswordUtils.java b/backend/src/main/java/com/stdproject/utils/PasswordUtils.java index 401ef9a..86b324d 100644 --- a/backend/src/main/java/com/stdproject/utils/PasswordUtils.java +++ b/backend/src/main/java/com/stdproject/utils/PasswordUtils.java @@ -82,4 +82,4 @@ public class PasswordUtils { return hasUpper && hasLower && hasDigit && hasSpecial; } -} \ No newline at end of file +} diff --git a/backend/src/main/resources/.env b/backend/src/main/resources/.env index 4fe5114..ee60068 100644 --- a/backend/src/main/resources/.env +++ b/backend/src/main/resources/.env @@ -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 diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml index 390b5ba..9f9acdd 100644 --- a/backend/src/main/resources/application.yml +++ b/backend/src/main/resources/application.yml @@ -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文件位置 diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties new file mode 100644 index 0000000..c7d3485 --- /dev/null +++ b/backend/src/main/resources/i18n/messages.properties @@ -0,0 +1,2 @@ +login.validator.name1= +login.validator.pwd1= \ No newline at end of file diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties new file mode 100644 index 0000000..13c7b02 --- /dev/null +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -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 diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties new file mode 100644 index 0000000..e7122b8 --- /dev/null +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -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 diff --git a/backend/src/main/resources/messages.properties b/backend/src/main/resources/messages.properties new file mode 100644 index 0000000..f6cdbec --- /dev/null +++ b/backend/src/main/resources/messages.properties @@ -0,0 +1,5 @@ +# 默认国际化资源文件 +i18n_fetch_error=数据获取错误: +i18n_schema_is_empty=数据库架构为空 +i18n_check_datasource_connection=数据源连接检查失败 +i18n_invalid_connection=无效的数据库连接: \ No newline at end of file