Squashed commit of the following:

升级seata到1.7.0;升级dynamic-datasource-spring-boot-starter到3.5.2
    支持多字段默认排序defSort数组、解决多列排序无效 #8659
    支持多字段默认排序defSort数组
    提供JeecgBoot 运行环境python检查脚本
    提供jeecgboot-oracle11g.dmp
springboot3
JEECG 2025-08-12 09:25:01 +08:00
parent ac446691c4
commit cd809a6573
13 changed files with 400 additions and 41 deletions

View File

@ -69,6 +69,7 @@ Jeecg-Boot AI low code platform can be applied in the development of any J2EE pr
Starts the project Starts the project
----------------------------------- -----------------------------------
- [Development Environment setup](https://help.jeecg.com/java/setup/tools)
- [IDEA Quick start](https://help.jeecg.com/java/setup/idea/startup) - [IDEA Quick start](https://help.jeecg.com/java/setup/idea/startup)
- [Docker Quick start](https://help.jeecg.com/java/docker/quick) - [Docker Quick start](https://help.jeecg.com/java/docker/quick)

View File

@ -66,6 +66,7 @@ JeecgBoot低代码平台可以应用在任何J2EE项目的开发中支持
启动项目 启动项目
----------------------------------- -----------------------------------
- [开发环境搭建](https://help.jeecg.com/java/setup/tools)
- [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup) - [IDEA启动前后端项目](https://help.jeecg.com/java/setup/idea/startup)
- [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick) - [Docker一键启动前后端](https://help.jeecg.com/java/docker/quick)

216
check_jeecgenv.py Normal file
View File

@ -0,0 +1,216 @@
import os
import subprocess
import re
import sys
from typing import Tuple, Optional
def run_command(cmd: str) -> Tuple[int, str]:
"""执行命令并返回退出码和输出"""
try:
result = subprocess.run(cmd, shell=True, check=False,
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
text=True)
return result.returncode, result.stdout.strip()
except Exception as e:
return -1, str(e)
def check_java() -> bool:
"""检查JDK 17+是否安装"""
print("\n检查JDK 17+...")
rc, output = run_command("java -version 2>&1")
if rc != 0:
print("❌ 未检测到Java请安装JDK 17+")
return False
version_pattern = r'"(\d+)(?:\.\d+)*(?:_\d+)?'
match = re.search(version_pattern, output)
if not match:
print("❌ 无法解析Java版本")
return False
version = int(match.group(1))
if version >= 17:
print(f"✅ JDK版本 {version} (满足17+要求)")
return True
else:
print(f"❌ JDK版本 {version} (需要17+)")
return False
def check_maven() -> bool:
"""检查Maven是否安装"""
print("\n检查Maven...")
rc, output = run_command("mvn -v")
if rc == 0:
print("✅ Maven已安装")
return True
else:
print("❌ Maven未安装")
return False
def check_node() -> bool:
"""检查Node.js 20+是否安装"""
print("\n检查Node.js 20+...")
rc, output = run_command("node -v")
if rc != 0:
print("❌ Node.js未安装")
return False
version_pattern = r'v(\d+)\.\d+\.\d+'
match = re.search(version_pattern, output)
if not match:
print("❌ 无法解析Node.js版本")
return False
version = int(match.group(1))
if version >= 20:
print(f"✅ Node.js版本 {version} (满足20+要求)")
return True
else:
print(f"❌ Node.js版本 {version} (需要20+)")
return False
def check_pnpm() -> bool:
"""检查PNPM 9+是否安装"""
print("\n检查PNPM 9+...")
rc, output = run_command("pnpm -v")
if rc != 0:
print("❌ PNPM未安装")
return False
try:
# 处理可能的版本号格式v9.0.0 或 9.0.0 或 9
version_str = output.strip().lstrip('v').split('.')[0]
version = int(version_str)
if version >= 9:
print(f"✅ PNPM版本 {output.strip()} (满足9+要求)")
return True
else:
print(f"❌ PNPM版本 {output.strip()} (需要9+)")
return False
except (ValueError, IndexError):
print(f"❌ 无法解析PNPM版本: {output.strip()}")
return False
def check_redis_connection() -> bool:
"""检查Redis连接"""
print("\n检查Redis连接...")
print("⚠️ 请确保已配置Redis连接信息并在jeecg-boot项目中正确配置")
print("⚠️ 此检查需要根据实际项目配置进行验证")
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
return True
def check_mysql_connection() -> bool:
"""检查MySQL连接"""
print("\n检查MySQL连接...")
print("⚠️ 请确保已配置MySQL连接信息并在jeecg-boot项目中正确配置")
print("⚠️ 此检查需要根据实际项目配置进行验证")
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
return True
def print_mysql_config():
"""打印MySQL配置并提示需要修改的位置"""
print("\nMySQL配置参考 (请检查以下配置是否正确):")
print("""
spring.datasource.dynamic.datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/jeecg-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
username: root # ← 可能需要修改
password: root # ← 可能需要修改
driver-class-name: com.mysql.cj.jdbc.Driver
""")
def check_ai_vector_db() -> bool:
"""检查AI向量库(pgvector)配置"""
print("\n检查AI知识库向量库配置...")
print("⚠️ 如果需要使用AI知识库功能请配置pgvector向量库")
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
print("\n配置参考:")
print("""
jeecg.ai-rag:
embed-store:
host: 127.0.0.1 # ← 可能需要修改
port: 5432 # ← 可能需要修改
database: postgres # ← 可能需要修改
user: postgres # ← 可能需要修改
password: postgres # ← 可能需要修改
table: embeddings # ← 可能需要修改
""")
print("⚠️ 注意: 请确保已安装PostgreSQL并添加pgvector扩展docker安装参考https://help.jeecg.com/aigc/config")
return True
def check_ai_config() -> bool:
"""检查AI账号配置"""
print("\n检查AI功能配置...")
print("⚠️ 如果需要使用AI聊天功能请配置AI账号信息")
print("⚠️ 配置文件位置: jeecg-boot/jeecg-module-system/jeecg-system-start/src/main/resources/application-dev.yml")
print("\n配置参考:")
print("""
jeecg:
# AI集成
ai-chat:
enabled: true # ← 启用AI功能
model: deepseek-chat # ← 模型名称
apiKey: ?? # ← 必须修改为您的API Key
apiHost: https://api.deepseek.com/v1 # ← API地址
timeout: 60 # ← 超时时间(秒)
""")
print("⚠️ 注意: 请确保已获取有效的API Key并正确配置AI账号注册获取参考 https://help.jeecg.com/java/deepSeekSupport")
return True
def print_redis_config():
"""打印Redis配置并提示需要修改的位置"""
print("\nRedis配置参考 (请检查以下配置是否正确):")
print("""
spring.redis:
database: 0
host: 127.0.0.1 # ← 可能需要修改
port: 6379 # ← 可能需要修改
password: '' # ← 如果需要密码请修改
""")
def main():
print("="*50)
print("JeecgBoot 运行环境检查脚本")
print("="*50)
all_checks_passed = True
# 检查各项依赖
if not check_java():
all_checks_passed = False
if not check_maven():
all_checks_passed = False
if not check_node():
all_checks_passed = False
if not check_pnpm():
all_checks_passed = False
# 数据库提示
print("="*50)
check_redis_connection()
print_redis_config()
print("="*50)
check_mysql_connection()
print_mysql_config()
print("="*50)
check_ai_config()
print("="*50)
check_ai_vector_db()
print("\n" + "="*50)
if all_checks_passed:
print("✅ 所有基础环境检查通过")
print("⚠️ 注意: 请确保Redis和MySQL、AI账号、向量库pgvector 已正确配置并连接成功")
else:
print("❌ 部分环境检查未通过,请根据上述提示解决问题")
print("="*50)
if __name__ == "__main__":
main()
input("\n按回车键退出...") # 等待用户输入

View File

@ -0,0 +1,5 @@
oracle导出编码 export NLS_LANG=AMERICAN_AMERICA.ZHS16GBK
导出用户: jeecgbootos
导入命令: imp scott/tiger@orcl file=jeecgboot-oracle11g.dmp

View File

@ -11,6 +11,7 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.beanutils.PropertyUtils;
import org.jeecg.common.constant.CommonConstant; import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant; import org.jeecg.common.constant.DataBaseConstant;
@ -257,8 +258,69 @@ public class QueryGenerator {
if(parameterMap!=null&& parameterMap.containsKey(ORDER_TYPE)) { if(parameterMap!=null&& parameterMap.containsKey(ORDER_TYPE)) {
order = parameterMap.get(ORDER_TYPE)[0]; order = parameterMap.get(ORDER_TYPE)[0];
} }
log.debug("排序规则>>列:" + column + ",排序方式:" + order);
if(oConvertUtils.isNotEmpty(column)){
log.info("单字段排序规则>> column:" + column + ",排序方式:" + order);
}
// 1. 列表多字段排序优先
if(parameterMap!=null&& parameterMap.containsKey("sortInfoString")) {
// 多字段排序
String sortInfoString = parameterMap.get("sortInfoString")[0];
log.info("多字段排序规则>> sortInfoString:" + sortInfoString);
List<OrderItem> orderItemList = SqlConcatUtil.getQueryConditionOrders(column, order, sortInfoString);
log.info(orderItemList.toString());
if (orderItemList != null && !orderItemList.isEmpty()) {
for (OrderItem item : orderItemList) {
// 一、获取排序数据库字段
String columnName = item.getColumn();
// 1.字典字段,去掉字典翻译文本后缀
if(columnName.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
columnName = columnName.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
}
// 2.实体驼峰字段转为数据库字段
columnName = SqlInjectionUtil.getSqlInjectSortField(columnName);
// 二、设置字段排序规则
if (item.isAsc()) {
queryWrapper.orderByAsc(columnName);
} else {
queryWrapper.orderByDesc(columnName);
}
}
}
return;
}
// 2. 列表单字段默认排序
if(oConvertUtils.isEmpty(column) && parameterMap!=null&& parameterMap.containsKey("defSortString")) {
// 多字段排序
String sortInfoString = parameterMap.get("defSortString")[0];
log.info("默认多字段排序规则>> defSortString:" + sortInfoString);
List<OrderItem> orderItemList = SqlConcatUtil.getQueryConditionOrders(column, order, sortInfoString);
log.info(orderItemList.toString());
if (orderItemList != null && !orderItemList.isEmpty()) {
for (OrderItem item : orderItemList) {
// 一、获取排序数据库字段
String columnName = item.getColumn();
// 1.字典字段,去掉字典翻译文本后缀
if(columnName.endsWith(CommonConstant.DICT_TEXT_SUFFIX)) {
columnName = columnName.substring(0, column.lastIndexOf(CommonConstant.DICT_TEXT_SUFFIX));
}
// 2.实体驼峰字段转为数据库字段
columnName = SqlInjectionUtil.getSqlInjectSortField(columnName);
// 二、设置字段排序规则
if (item.isAsc()) {
queryWrapper.orderByAsc(columnName);
} else {
queryWrapper.orderByDesc(columnName);
}
}
}
return;
}
//update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错 //update-begin-author:scott date:2022-11-07 for:避免用户自定义表无默认字段{创建时间},导致排序报错
//TODO 避免用户自定义表无默认字段创建时间,导致排序报错 //TODO 避免用户自定义表无默认字段创建时间,导致排序报错
if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){ if(DataBaseConstant.CREATE_TIME.equals(column) && !fieldColumnMap.containsKey(DataBaseConstant.CREATE_TIME)){

View File

@ -1,13 +1,22 @@
package org.jeecg.common.system.util; package org.jeecg.common.system.util;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.DataBaseConstant; import org.jeecg.common.constant.DataBaseConstant;
import org.jeecg.common.constant.SymbolConstant; import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.system.query.QueryGenerator; import org.jeecg.common.system.query.QueryGenerator;
import org.jeecg.common.system.query.QueryRuleEnum; import org.jeecg.common.system.query.QueryRuleEnum;
import org.jeecg.common.util.CommonUtils; import org.jeecg.common.util.CommonUtils;
import org.jeecg.common.util.oConvertUtils; import org.jeecg.common.util.oConvertUtils;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; import java.util.List;
/** /**
@ -239,5 +248,47 @@ public class SqlConcatUtil {
private static String getDbType() { private static String getDbType() {
return CommonUtils.getDatabaseType(); return CommonUtils.getDatabaseType();
} }
/**
* "多字段排序信息: sortInfoString"
* @return
*/
public static List<OrderItem> getQueryConditionOrders(String column, String order, String queryInfoString){
List<OrderItem> list = new ArrayList<>();
if(oConvertUtils.isEmpty(queryInfoString)){
//默认以创建时间倒序查询
if(CommonConstant.ORDER_TYPE_DESC.equalsIgnoreCase(order)){
list.add(OrderItem.desc(column));
}else{
list.add(OrderItem.asc(column));
}
}else{
// 【TV360X-967】URL解码微服务下需要
if (queryInfoString.contains("%22column%22")) {
log.info("queryInfoString 原生 = {}", queryInfoString);
try {
queryInfoString = URLDecoder.decode(queryInfoString, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new JeecgBootException(e);
}
log.info("queryInfoString 解码 = {}", queryInfoString);
}
JSONArray array = JSONArray.parseArray(queryInfoString);
Iterator it = array.iterator();
while(it.hasNext()){
JSONObject json = (JSONObject)it.next();
String tempColumn = json.getString("column");
if(oConvertUtils.isNotEmpty(tempColumn)){
String tempOrder = json.getString("order");
if(CommonConstant.ORDER_TYPE_DESC.equalsIgnoreCase(tempOrder)){
list.add(OrderItem.desc(tempColumn));
}else{
list.add(OrderItem.asc(tempColumn));
}
}
}
}
return list;
}
} }

View File

@ -12,6 +12,11 @@ spring:
enabled: false enabled: false
application: application:
name: seata-account name: seata-account
cloud:
nacos:
config:
import-check:
enabled: false
main: main:
allow-bean-definition-overriding: true allow-bean-definition-overriding: true
autoconfigure: autoconfigure:
@ -21,18 +26,19 @@ spring:
username: root username: root
password: root password: root
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
schema: classpath:sql/schema-account.sql sql:
cloud: init:
nacos: schema-locations: classpath:sql/schema-account.sql
config:
import-check:
enabled: false
seata: seata:
# enable-auto-data-source-proxy: true enable-auto-data-source-proxy: false
service: service:
grouplist: grouplist:
default: 127.0.0.1:8091 default: 127.0.0.1:8091
vgroup-mapping: vgroup-mapping:
springboot-seata-group: default springboot-seata-group: default
# seata 事务组编号 用于TC集群名 # seata 事务组编号 用于TC集群名
tx-service-group: springboot-seata-group tx-service-group: springboot-seata-group
# 无用配置,为了避免扫码全代码导致启动慢
minidao:
base-package: org.jeecg.modules.jmreport.*

View File

@ -12,6 +12,11 @@ spring:
enabled: false enabled: false
application: application:
name: seata-order name: seata-order
cloud:
nacos:
config:
import-check:
enabled: false
main: main:
allow-bean-definition-overriding: true allow-bean-definition-overriding: true
autoconfigure: autoconfigure:
@ -21,18 +26,19 @@ spring:
url: jdbc:mysql://127.0.0.1:3306/jeecg_order?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false url: jdbc:mysql://127.0.0.1:3306/jeecg_order?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
username: root username: root
password: root password: root
schema: classpath:sql/schema-order.sql sql:
cloud: init:
nacos: schema-locations: classpath:sql/schema-order.sql
config:
import-check:
enabled: false
seata: seata:
# enable-auto-data-source-proxy: false enable-auto-data-source-proxy: false
service: service:
grouplist: grouplist:
default: 127.0.0.1:8091 default: 127.0.0.1:8091
vgroup-mapping: vgroup-mapping:
springboot-seata-group: default springboot-seata-group: default
# seata 事务组编号 用于TC集群名 # seata 事务组编号 用于TC集群名
tx-service-group: springboot-seata-group tx-service-group: springboot-seata-group
# 无用配置,为了避免扫码全代码导致启动慢
minidao:
base-package: org.jeecg.modules.jmreport.*

View File

@ -1,17 +1,13 @@
server: server:
port: 5003 port: 5003
spring: spring:
data:
redis:
##redis 单机环境配置
host: localhost
port: 6379
database: 0
password:
ssl:
enabled: false
application: application:
name: seata-product name: seata-product
cloud:
nacos:
config:
import-check:
enabled: false
main: main:
allow-bean-definition-overriding: true allow-bean-definition-overriding: true
autoconfigure: autoconfigure:
@ -21,18 +17,19 @@ spring:
url: jdbc:mysql://127.0.0.1:3306/jeecg_product?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false url: jdbc:mysql://127.0.0.1:3306/jeecg_product?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useSSL=false
username: root username: root
password: root password: root
schema: classpath:sql/schema-product.sql sql:
cloud: init:
nacos: schema-locations: classpath:sql/schema-product.sql
config:
import-check:
enabled: false
seata: seata:
# enable-auto-data-source-proxy: false enable-auto-data-source-proxy: false
service: service:
grouplist: grouplist:
default: 127.0.0.1:8091 default: 127.0.0.1:8091
vgroup-mapping: vgroup-mapping:
springboot-seata-group: default springboot-seata-group: default
# seata 事务组编号 用于TC集群名 # seata 事务组编号 用于TC集群名
tx-service-group: springboot-seata-group tx-service-group: springboot-seata-group
# 无用配置,为了避免扫码全代码导致启动慢
minidao:
base-package: org.jeecg.modules.jmreport.*

View File

@ -238,12 +238,20 @@ export function useDataSource(
const { sortInfo = {}, filterInfo } = searchState; const { sortInfo = {}, filterInfo } = searchState;
//
let defSortInfo: Recordable<any> | undefined = {};
if (defSort && Array.isArray(defSort) && defSort.length > 0) {
defSortInfo['defSortString'] = JSON.stringify(defSort);
} else {
defSortInfo = defSort;
}
let params: Recordable = { let params: Recordable = {
...pageParams, ...pageParams,
// getFieldsValue validate // getFieldsValue validate
...(useSearchForm ? await validate() : {}), ...(useSearchForm ? await validate() : {}),
...searchInfo, ...searchInfo,
...defSort, ...defSortInfo,
...(opt?.searchInfo ?? {}), ...(opt?.searchInfo ?? {}),
...sortInfo, ...sortInfo,
...filterInfo, ...filterInfo,

View File

@ -190,7 +190,7 @@ export interface BasicTableProps<T = any> {
// //
searchInfo?: Recordable; searchInfo?: Recordable;
// //
defSort?: Recordable; defSort?: Recordable | Recordable[];
// 使 // 使
useSearchForm?: boolean; useSearchForm?: boolean;
// //

View File

@ -123,11 +123,17 @@
style: { textAlign: 'left' }, style: { textAlign: 'left' },
}, },
}, },
// //
defSort: { defSort: [
column: 'createTime,sex', {
order: 'desc', column: 'name',
}, order: 'desc',
},
{
column: 'sex',
order: 'asc',
},
],
striped: true, striped: true,
useSearchForm: true, useSearchForm: true,
showTableSetting: true, showTableSetting: true,