Compare commits
No commits in common. "master" and "v2026.01(jdk8/11)" have entirely different histories.
master
...
v2026.01(j
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 163 KiB |
|
Before Width: | Height: | Size: 79 KiB |
|
Before Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 216 KiB |
|
Before Width: | Height: | Size: 54 KiB |
33
README.md
|
|
@ -31,8 +31,8 @@
|
||||||
| 【完整版】[yudao-cloud](https://gitee.com/zhijiantianya/yudao-cloud) | [`master`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master-jdk17/) 分支 |
|
| 【完整版】[yudao-cloud](https://gitee.com/zhijiantianya/yudao-cloud) | [`master`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/zhijiantianya/yudao-cloud/tree/master-jdk17/) 分支 |
|
||||||
| 【精简版】[yudao-cloud-mini](https://gitee.com/yudaocode/yudao-cloud-mini) | [`master`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master-jdk17/) 分支 |
|
| 【精简版】[yudao-cloud-mini](https://gitee.com/yudaocode/yudao-cloud-mini) | [`master`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master/) 分支 | [`master-jdk17`](https://gitee.com/yudaocode/yudao-cloud-mini/tree/master-jdk17/) 分支 |
|
||||||
|
|
||||||
* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、AI 大模型、IoT 物联网 等功能
|
* 【完整版】:包括系统功能、基础设施、会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
|
||||||
* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP、WMS、MES、AI 大模型、IoT 物联网 等功能
|
* 【精简版】:只包括系统功能、基础设施功能,不包括会员中心、数据报表、工作流程、商城系统、微信公众号、CRM、ERP 等功能
|
||||||
|
|
||||||
可参考 [《迁移文档》](https://cloud.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】
|
可参考 [《迁移文档》](https://cloud.iocoder.cn/migrate-module/) ,只需要 5-10 分钟,即可将【完整版】按需迁移到【精简版】
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
|
|
||||||
* 通用模块(必选):系统功能、基础设施
|
* 通用模块(必选):系统功能、基础设施
|
||||||
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
* 通用模块(可选):工作流程、支付系统、数据报表、会员中心
|
||||||
* 业务系统(按需):Mall 电子商城、OA 办公自动化、ERP 企业资源计划系统、WMS 仓库管理系统、CRM 客户关系管理、CMS 内容管理系统、MES 执行制造系统、AI 大模型平台、IoT 物联网系统、IM 即时通讯系统、Mobile 手机移动端、Report 数据大屏
|
* 业务系统(按需):ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
|
||||||
|
|
||||||
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
|
> 友情提示:本项目基于 RuoYi-Vue 修改,**重构优化**后端的代码,**美化**前端的界面。
|
||||||
>
|
>
|
||||||
|
|
@ -273,28 +273,12 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### WMS 系统
|
|
||||||
|
|
||||||
演示地址:<https://cloud.iocoder.cn/wms-preview/>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### CRM 系统
|
### CRM 系统
|
||||||
|
|
||||||
演示地址:<https://cloud.iocoder.cn/crm-preview/>
|
演示地址:<https://cloud.iocoder.cn/crm-preview/>
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### MES 系统
|
|
||||||
|
|
||||||
演示地址:<https://cloud.iocoder.cn/mes-preview/>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### AI 大模型
|
### AI 大模型
|
||||||
|
|
||||||
演示地址:<https://cloud.iocoder.cn/ai-preview/>
|
演示地址:<https://cloud.iocoder.cn/ai-preview/>
|
||||||
|
|
@ -303,14 +287,6 @@
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
### IoT 物联网
|
|
||||||
|
|
||||||
演示地址:<https://cloud.iocoder.cn/iot/build>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## 🐨 技术栈
|
## 🐨 技术栈
|
||||||
|
|
||||||
### 微服务
|
### 微服务
|
||||||
|
|
@ -328,10 +304,7 @@
|
||||||
| `yudao-module-mall` | 商城系统的 Module 模块 |
|
| `yudao-module-mall` | 商城系统的 Module 模块 |
|
||||||
| `yudao-module-erp` | ERP 系统的 Module 模块 |
|
| `yudao-module-erp` | ERP 系统的 Module 模块 |
|
||||||
| `yudao-module-crm` | CRM 系统的 Module 模块 |
|
| `yudao-module-crm` | CRM 系统的 Module 模块 |
|
||||||
| `yudao-module-mes` | MES 系统的 Module 模块 |
|
|
||||||
| `yudao-module-wms` | WMS 系统的 Module 模块 |
|
|
||||||
| `yudao-module-ai` | AI 大模型的 Module 模块 |
|
| `yudao-module-ai` | AI 大模型的 Module 模块 |
|
||||||
| `yudao-module-iot` | IoT 物联网的 Module 模块 |
|
|
||||||
| `yudao-module-mp` | 微信公众号的 Module 模块 |
|
| `yudao-module-mp` | 微信公众号的 Module 模块 |
|
||||||
| `yudao-module-report` | 大屏报表 Module 模块 |
|
| `yudao-module-report` | 大屏报表 Module 模块 |
|
||||||
|
|
||||||
|
|
|
||||||
6
pom.xml
|
|
@ -24,11 +24,9 @@
|
||||||
<module>yudao-module-mall</module>
|
<module>yudao-module-mall</module>
|
||||||
<module>yudao-module-erp</module>
|
<module>yudao-module-erp</module>
|
||||||
<module>yudao-module-crm</module>
|
<module>yudao-module-crm</module>
|
||||||
<module>yudao-module-iot</module>
|
|
||||||
<module>yudao-module-mes</module>
|
|
||||||
<module>yudao-module-wms</module>
|
|
||||||
<!-- 友情提示:基于 Spring AI 实现 LLM 大模型的接入,需要使用 JDK17 版本,详细可见 https://doc.iocoder.cn/ai/build/ -->
|
<!-- 友情提示:基于 Spring AI 实现 LLM 大模型的接入,需要使用 JDK17 版本,详细可见 https://doc.iocoder.cn/ai/build/ -->
|
||||||
<!-- <module>yudao-module-ai</module>-->
|
<!-- <module>yudao-module-ai</module>-->
|
||||||
|
<module>yudao-module-iot</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<name>${project.artifactId}</name>
|
<name>${project.artifactId}</name>
|
||||||
|
|
@ -36,7 +34,7 @@
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2026.04-jdk8-SNAPSHOT</revision>
|
<revision>2026.01-jdk8-SNAPSHOT</revision>
|
||||||
<!-- Maven 相关 -->
|
<!-- Maven 相关 -->
|
||||||
<java.version>1.8</java.version>
|
<java.version>1.8</java.version>
|
||||||
<maven.compiler.source>${java.version}</maven.compiler.source>
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
|
|
|
||||||
|
|
@ -62,10 +62,6 @@ def load_and_clean(sql_file: str) -> str:
|
||||||
content = open(sql_file, encoding="utf-8").read()
|
content = open(sql_file, encoding="utf-8").read()
|
||||||
for replace_pair in REPLACE_PAIR_LIST:
|
for replace_pair in REPLACE_PAIR_LIST:
|
||||||
content = content.replace(*replace_pair)
|
content = content.replace(*replace_pair)
|
||||||
# 移除所有 CHARACTER SET / COLLATE 变体 (utf8mb3、utf8 等)
|
|
||||||
content = re.sub(r" CHARACTER SET \w+ COLLATE \w+", "", content)
|
|
||||||
content = re.sub(r" CHARACTER SET \w+", "", content)
|
|
||||||
content = re.sub(r" COLLATE \w+", "", content)
|
|
||||||
# 移除索引字段的前缀长度定义,例如: `name`(32) -> `name`
|
# 移除索引字段的前缀长度定义,例如: `name`(32) -> `name`
|
||||||
# 移除索引定义上的 USING BTREE COMMENT 部分
|
# 移除索引定义上的 USING BTREE COMMENT 部分
|
||||||
# 相关 issue:https://t.zsxq.com/96IFc 、https://t.zsxq.com/rC3A3
|
# 相关 issue:https://t.zsxq.com/96IFc 、https://t.zsxq.com/rC3A3
|
||||||
|
|
@ -81,11 +77,7 @@ class Convertor(ABC):
|
||||||
self.src = src
|
self.src = src
|
||||||
self.db_type = db_type
|
self.db_type = db_type
|
||||||
self.content = load_and_clean(self.src)
|
self.content = load_and_clean(self.src)
|
||||||
# original_content 保留原始 COMMENT 信息,用于注释提取
|
self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", self.content)
|
||||||
self.original_content = open(src, encoding="utf-8").read()
|
|
||||||
# 剥离列级 COMMENT 以避免 COMMENT 值内的分号截断 CREATE TABLE 正则
|
|
||||||
content_no_comment = re.sub(r" COMMENT '(?:[^'\\]|\\.)*'", "", self.content)
|
|
||||||
self.table_script_list = re.findall(r"CREATE TABLE [^;]*;", content_no_comment)
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str:
|
def translate_type(self, type: str, size: Optional[Union[int, Tuple[int]]]) -> str:
|
||||||
|
|
@ -190,8 +182,7 @@ class Convertor(ABC):
|
||||||
head = head.strip().replace("`", "").lower()
|
head = head.strip().replace("`", "").lower()
|
||||||
tail = tail.strip().replace(r"\"", '"')
|
tail = tail.strip().replace(r"\"", '"')
|
||||||
# tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'")
|
# tail = tail.replace("b'0'", "'0'").replace("b'1'", "'1'")
|
||||||
col_part = f" {head}" if head else ""
|
yield f"INSERT INTO {table_name.lower()} {head} VALUES {tail}"
|
||||||
yield f"INSERT INTO {table_name.lower()}{col_part} VALUES {tail}"
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def index(ddl: Dict) -> Generator:
|
def index(ddl: Dict) -> Generator:
|
||||||
|
|
@ -236,8 +227,7 @@ class Convertor(ABC):
|
||||||
yield field, comment_string
|
yield field, comment_string
|
||||||
|
|
||||||
def table_comment(self, table_sql: str) -> str:
|
def table_comment(self, table_sql: str) -> str:
|
||||||
# 兼容 COMMENT='xxx' / COMMENT = 'xxx' 等等号两侧空格变体;允许 COMMENT 内含转义单引号
|
match = re.search(r"COMMENT \='([^']+)';", table_sql)
|
||||||
match = re.search(r"COMMENT\s*=\s*'((?:[^'\\]|\\.)*)'", table_sql)
|
|
||||||
return match.group(1) if match else None
|
return match.group(1) if match else None
|
||||||
|
|
||||||
def print(self):
|
def print(self):
|
||||||
|
|
@ -261,9 +251,7 @@ class Convertor(ABC):
|
||||||
|
|
||||||
error_scripts = []
|
error_scripts = []
|
||||||
for table_sql in self.table_script_list:
|
for table_sql in self.table_script_list:
|
||||||
# 剥离 COMMENT 子句避免 DDLParser 无法处理中文等特殊字符
|
ddl = DDLParser(table_sql.replace("`", "")).run()
|
||||||
table_sql_for_parse = re.sub(r"\s+COMMENT\s+'[^']*'", "", table_sql)
|
|
||||||
ddl = DDLParser(table_sql_for_parse.replace("`", "")).run()
|
|
||||||
|
|
||||||
# 如果parse失败, 需要跟进
|
# 如果parse失败, 需要跟进
|
||||||
if len(ddl) == 0:
|
if len(ddl) == 0:
|
||||||
|
|
@ -278,23 +266,17 @@ class Convertor(ABC):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 解析注释
|
# 解析注释
|
||||||
# 从原始 SQL 提取注释(支持中文等 DDLParser 不能处理的内容)
|
|
||||||
# 按表名定位完整建表段(以「换行 + )」起始的 ENGINE 行作为终止),避免被列 COMMENT 内的分号截断
|
|
||||||
orig_match = re.search(
|
|
||||||
rf"CREATE TABLE\s+`{re.escape(table_name)}`[\s\S]*?\n\)[^;]*;",
|
|
||||||
self.original_content,
|
|
||||||
flags=re.IGNORECASE,
|
|
||||||
)
|
|
||||||
orig_table_sql = orig_match.group() if orig_match else table_sql
|
|
||||||
comments_dict = dict(Convertor.filed_comments(orig_table_sql))
|
|
||||||
for column in table_ddl["columns"]:
|
for column in table_ddl["columns"]:
|
||||||
column["comment"] = comments_dict.get(column["name"], "")
|
column["comment"] = bytes(column["comment"], "utf-8").decode(
|
||||||
table_ddl["comment"] = self.table_comment(orig_table_sql) or ""
|
r"unicode_escape"
|
||||||
|
)[1:-1]
|
||||||
|
table_ddl["comment"] = bytes(table_ddl["comment"], "utf-8").decode(
|
||||||
|
r"unicode_escape"
|
||||||
|
)[1:-1]
|
||||||
|
|
||||||
# 为每个表生成个6个基本部分
|
# 为每个表生成个6个基本部分
|
||||||
create = self.gen_create(table_ddl)
|
create = self.gen_create(table_ddl)
|
||||||
has_id = any(col["name"].lower() == "id" for col in table_ddl["columns"])
|
pk = self.gen_pk(table_name)
|
||||||
pk = self.gen_pk(table_name) if has_id else ""
|
|
||||||
uk = self.gen_uk(table_ddl)
|
uk = self.gen_uk(table_ddl)
|
||||||
index = self.gen_index(table_ddl)
|
index = self.gen_index(table_ddl)
|
||||||
comment = self.gen_comment(table_ddl)
|
comment = self.gen_comment(table_ddl)
|
||||||
|
|
@ -338,31 +320,25 @@ class PostgreSQLConvertor(Convertor):
|
||||||
|
|
||||||
if type == "varchar":
|
if type == "varchar":
|
||||||
return f"varchar({size})"
|
return f"varchar({size})"
|
||||||
if type in ("int", "int unsigned", "int unsigned zerofill"):
|
if type in ("int", "int unsigned"):
|
||||||
return "int4"
|
return "int4"
|
||||||
if type in ("bigint", "bigint unsigned"):
|
if type in ("bigint", "bigint unsigned"):
|
||||||
return "int8"
|
return "int8"
|
||||||
if type in ("tinyint", "smallint", "tinyint unsigned"):
|
if type == "datetime":
|
||||||
return "int2"
|
|
||||||
if type in ("datetime", "timestamp null"):
|
|
||||||
return "timestamp"
|
return "timestamp"
|
||||||
if type == "date":
|
|
||||||
return "date"
|
|
||||||
if type == "json":
|
|
||||||
return "jsonb"
|
|
||||||
if type == "double":
|
|
||||||
return "double precision"
|
|
||||||
if type == "timestamp":
|
if type == "timestamp":
|
||||||
return f"timestamp({size})" if size else "timestamp"
|
return f"timestamp({size})"
|
||||||
if type == "bit":
|
if type == "bit":
|
||||||
return "bool"
|
return "bool"
|
||||||
|
if type in ("tinyint", "smallint"):
|
||||||
|
return "int2"
|
||||||
if type in ("text", "longtext"):
|
if type in ("text", "longtext"):
|
||||||
return "text"
|
return "text"
|
||||||
if type in ("blob", "mediumblob", "longblob"):
|
if type in ("blob", "mediumblob"):
|
||||||
return "bytea"
|
return "bytea"
|
||||||
if type == "decimal":
|
if type == "decimal":
|
||||||
return (
|
return (
|
||||||
f"numeric({','.join(str(s) for s in size)})" if size and len(size) else "numeric"
|
f"numeric({','.join(str(s) for s in size)})" if len(size) else "numeric"
|
||||||
)
|
)
|
||||||
|
|
||||||
def gen_create(self, ddl: Dict) -> str:
|
def gen_create(self, ddl: Dict) -> str:
|
||||||
|
|
@ -375,10 +351,6 @@ class PostgreSQLConvertor(Convertor):
|
||||||
|
|
||||||
type = col["type"].lower()
|
type = col["type"].lower()
|
||||||
full_type = self.translate_type(type, col["size"])
|
full_type = self.translate_type(type, col["size"])
|
||||||
if full_type is None:
|
|
||||||
raise NotImplementedError(
|
|
||||||
f"未支持的类型: '{col['type']}' (列: {name}, 表: {ddl['table_name']})"
|
|
||||||
)
|
|
||||||
nullable = "NULL" if col["nullable"] else "NOT NULL"
|
nullable = "NULL" if col["nullable"] else "NOT NULL"
|
||||||
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
|
default = f"DEFAULT {col['default']}" if col["default"] is not None else ""
|
||||||
return f"{name} {full_type} {nullable} {default}"
|
return f"{name} {full_type} {nullable} {default}"
|
||||||
|
|
@ -435,8 +407,6 @@ CREATE TABLE {table_name} (
|
||||||
"""生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
|
"""生成 insert 语句,以及根据最后的 insert id+1 生成 Sequence"""
|
||||||
|
|
||||||
inserts = list(Convertor.inserts(table_name, self.content))
|
inserts = list(Convertor.inserts(table_name, self.content))
|
||||||
# 转换 MySQL 字符串转义为 PostgreSQL 格式:\\ -> \,\' -> ''
|
|
||||||
inserts = [re.sub(r"\\\\|\\'", lambda m: "\\" if m.group() == "\\\\" else "''", s) for s in inserts]
|
|
||||||
## 生成 insert 脚本
|
## 生成 insert 脚本
|
||||||
script = ""
|
script = ""
|
||||||
last_id = 0
|
last_id = 0
|
||||||
|
|
|
||||||
|
|
@ -14,30 +14,30 @@
|
||||||
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
<url>https://github.com/YunaiV/ruoyi-vue-pro</url>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<revision>2026.04-jdk8-SNAPSHOT</revision>
|
<revision>2026.01-jdk8-SNAPSHOT</revision>
|
||||||
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
<flatten-maven-plugin.version>1.6.0</flatten-maven-plugin.version>
|
||||||
<!-- 统一依赖管理 -->
|
<!-- 统一依赖管理 -->
|
||||||
<spring.framework.version>5.3.39</spring.framework.version>
|
<spring.framework.version>5.3.39</spring.framework.version>
|
||||||
<spring.security.version>5.8.16</spring.security.version>
|
<spring.security.version>5.8.16</spring.security.version>
|
||||||
<spring.boot.version>2.7.18</spring.boot.version>
|
<spring.boot.version>2.7.18</spring.boot.version>
|
||||||
<spring.cloud.version>2021.0.9</spring.cloud.version> <!-- Spring Boot 2.X 最多使用 2021.0.9 版本 -->
|
<spring.cloud.version>2021.0.9</spring.cloud.version>
|
||||||
<spring.cloud.alibaba.version>2021.0.6.2</spring.cloud.alibaba.version> <!-- Spring Boot 2.X 最多使用 2021.0.6.2 版本 -->
|
<spring.cloud.alibaba.version>2021.0.6.2</spring.cloud.alibaba.version>
|
||||||
<!-- Web 相关 -->
|
<!-- Web 相关 -->
|
||||||
<servlet.versoin>2.5</servlet.versoin>
|
<servlet.versoin>2.5</servlet.versoin>
|
||||||
<springdoc.version>1.8.0</springdoc.version>
|
<springdoc.version>1.8.0</springdoc.version>
|
||||||
<knife4j.version>4.5.0</knife4j.version>
|
<knife4j.version>4.5.0</knife4j.version>
|
||||||
<!-- DB 相关 -->
|
<!-- DB 相关 -->
|
||||||
<druid.version>1.2.28</druid.version>
|
<druid.version>1.2.27</druid.version>
|
||||||
<mybatis.version>3.5.19</mybatis.version>
|
<mybatis.version>3.5.19</mybatis.version>
|
||||||
<mybatis-plus.version>3.5.16</mybatis-plus.version>
|
<mybatis-plus.version>3.5.15</mybatis-plus.version>
|
||||||
<mybatis-plus-join.version>1.5.7</mybatis-plus-join.version>
|
<mybatis-plus-join.version>1.5.5</mybatis-plus-join.version>
|
||||||
<dynamic-datasource.version>4.5.0</dynamic-datasource.version>
|
<dynamic-datasource.version>4.5.0</dynamic-datasource.version>
|
||||||
<easy-trans.version>3.0.6</easy-trans.version>
|
<easy-trans.version>3.0.6</easy-trans.version>
|
||||||
<redisson.version>4.3.1</redisson.version>
|
<redisson.version>3.52.0</redisson.version>
|
||||||
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
<dm8.jdbc.version>8.1.3.140</dm8.jdbc.version>
|
||||||
<kingbase.jdbc.version>9.0.1.jre7</kingbase.jdbc.version>
|
<kingbase.jdbc.version>8.6.0</kingbase.jdbc.version>
|
||||||
<opengauss.jdbc.version>7.0.0-RC3-og</opengauss.jdbc.version>
|
<opengauss.jdbc.version>5.1.0</opengauss.jdbc.version>
|
||||||
<taos.version>3.8.3</taos.version>
|
<taos.version>3.7.9</taos.version>
|
||||||
<!-- 消息队列 -->
|
<!-- 消息队列 -->
|
||||||
<rocketmq-spring.version>2.3.5</rocketmq-spring.version>
|
<rocketmq-spring.version>2.3.5</rocketmq-spring.version>
|
||||||
<!-- RPC 相关 -->
|
<!-- RPC 相关 -->
|
||||||
|
|
@ -55,41 +55,38 @@
|
||||||
<jedis-mock.version>1.1.12</jedis-mock.version>
|
<jedis-mock.version>1.1.12</jedis-mock.version>
|
||||||
<mockito-inline.version>4.11.0</mockito-inline.version>
|
<mockito-inline.version>4.11.0</mockito-inline.version>
|
||||||
<!-- Bpm 工作流相关 -->
|
<!-- Bpm 工作流相关 -->
|
||||||
<flowable.version>6.8.1</flowable.version>
|
<flowable.version>6.8.0</flowable.version>
|
||||||
<!-- 工具类相关 -->
|
<!-- 工具类相关 -->
|
||||||
<anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
|
<anji-plus-captcha.version>1.4.0</anji-plus-captcha.version>
|
||||||
<jsoup.version>1.22.2</jsoup.version>
|
<jsoup.version>1.21.2</jsoup.version>
|
||||||
<lombok.version>1.18.46</lombok.version>
|
<lombok.version>1.18.42</lombok.version>
|
||||||
<mapstruct.version>1.6.3</mapstruct.version>
|
<mapstruct.version>1.6.3</mapstruct.version>
|
||||||
<hutool-5.version>5.8.44</hutool-5.version>
|
<hutool-5.version>5.8.42</hutool-5.version>
|
||||||
<fastexcel.version>1.3.0</fastexcel.version>
|
<fastexcel.version>1.3.0</fastexcel.version>
|
||||||
<velocity.version>2.4</velocity.version> <!-- JDK8 不能从 2.4 升级到 2.4.1,会报包不存在!!!! -->
|
<velocity.version>2.4</velocity.version> <!-- JDK8 不能从 2.4 升级到 2.4.1,会报包不存在!!!! -->
|
||||||
<fastjson.version>1.2.83</fastjson.version>
|
<fastjson.version>1.2.83</fastjson.version>
|
||||||
<guava.version>33.6.0-jre</guava.version>
|
<guava.version>33.5.0-jre</guava.version>
|
||||||
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
<transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>
|
||||||
<commons-net.version>3.13.0</commons-net.version>
|
<commons-net.version>3.12.0</commons-net.version>
|
||||||
<commons-lang3.version>3.20.0</commons-lang3.version>
|
<commons-lang3.version>3.20.0</commons-lang3.version>
|
||||||
<jsch.version>2.28.2</jsch.version>
|
<jsch.version>2.27.7</jsch.version>
|
||||||
<tika-core.version>2.9.3</tika-core.version> <!-- JDK8 不能从 2.9.3 升级到 3.X,会报 JDK8 不支持 -->
|
<tika-core.version>2.9.3</tika-core.version> <!-- JDK8 不能从 2.9.3 升级到 3.X,会报 JDK8 不支持 -->
|
||||||
<ip2region.version>2.7.0</ip2region.version>
|
<ip2region.version>2.7.0</ip2region.version>
|
||||||
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
<bizlog-sdk.version>3.0.6</bizlog-sdk.version>
|
||||||
<reflections.version>0.10.2</reflections.version>
|
<reflections.version>0.10.2</reflections.version>
|
||||||
<netty.version>4.2.12.Final</netty.version>
|
<netty.version>4.2.9.Final</netty.version>
|
||||||
<mqtt.version>1.2.5</mqtt.version>
|
<mqtt.version>1.2.5</mqtt.version>
|
||||||
<vertx.version>4.5.26</vertx.version>
|
<vertx.version>4.5.22</vertx.version>
|
||||||
<okhttp.version>4.12.0</okhttp.version>
|
<okhttp.version>4.12.0</okhttp.version>
|
||||||
<californium.version>3.14.0</californium.version>
|
<californium.version>3.12.0</californium.version>
|
||||||
<j2mod.version>3.3.0</j2mod.version>
|
|
||||||
<httpclient5.version>5.5.2</httpclient5.version> <!-- WxJava 4.8.x 需要 HttpClient5 5.4+,Spring Boot 2.7 默认 5.1.4 不兼容 -->
|
|
||||||
<httpcore5.version>5.3.6</httpcore5.version> <!-- 配套 httpclient5 5.5.2,Spring Boot 2.7 默认 5.1.5 不兼容 -->
|
|
||||||
<!-- 三方云服务相关 -->
|
<!-- 三方云服务相关 -->
|
||||||
<awssdk.version>2.44.0</awssdk.version>
|
<awssdk.version>2.40.15</awssdk.version>
|
||||||
<justauth.version>1.16.7</justauth.version>
|
<justauth.version>1.16.7</justauth.version>
|
||||||
<justauth-starter.version>1.4.0</justauth-starter.version>
|
<justauth-starter.version>1.4.0</justauth-starter.version>
|
||||||
<jimureport.version>2.3.2</jimureport.version>
|
<jimureport.version>2.1.3</jimureport.version>
|
||||||
<jimubi.version>2.3.2</jimubi.version>
|
<jimubi.version>2.3.0</jimubi.version>
|
||||||
<weixin-java.version>4.8.2-20260501.180637</weixin-java.version>
|
<weixin-java.version>4.7.9-20251224.161447</weixin-java.version>
|
||||||
<alipay-sdk-java.version>4.40.771.ALL</alipay-sdk-java.version>
|
<alipay-sdk-java.version>4.40.607.ALL</alipay-sdk-java.version>
|
||||||
<!-- 专属于 JDK8 安全漏洞升级 -->
|
<!-- 专属于 JDK8 安全漏洞升级 -->
|
||||||
<logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
|
<logback.version>1.2.13</logback.version> <!-- 无法使用 1.3.X 版本,启动会报错 -->
|
||||||
</properties>
|
</properties>
|
||||||
|
|
@ -310,7 +307,7 @@
|
||||||
<exclusion>
|
<exclusion>
|
||||||
<groupId>org.redisson</groupId>
|
<groupId>org.redisson</groupId>
|
||||||
<!-- 使用 redisson-spring-data-27 替代,解决 Tuple NoClassDefFoundError 报错 -->
|
<!-- 使用 redisson-spring-data-27 替代,解决 Tuple NoClassDefFoundError 报错 -->
|
||||||
<artifactId>redisson-spring-data-40</artifactId> <!-- Redisson 4.x 默认依赖 spring-data-40,排除后使用 spring-data-27 适配 Spring Boot 2.7 -->
|
<artifactId>redisson-spring-data-35</artifactId>
|
||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
@ -674,30 +671,6 @@
|
||||||
<version>${californium.version}</version>
|
<version>${californium.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- Modbus 相关 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>com.ghgande</groupId>
|
|
||||||
<artifactId>j2mod</artifactId>
|
|
||||||
<version>${j2mod.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- WxJava 4.8.x 需要 HttpClient5 5.4+,覆盖 Spring Boot 2.7 默认的 5.1.4 -->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents.client5</groupId>
|
|
||||||
<artifactId>httpclient5</artifactId>
|
|
||||||
<version>${httpclient5.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
|
||||||
<artifactId>httpcore5</artifactId>
|
|
||||||
<version>${httpcore5.version}</version>
|
|
||||||
</dependency>
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents.core5</groupId>
|
|
||||||
<artifactId>httpcore5-h2</artifactId>
|
|
||||||
<version>${httpcore5.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 三方云服务相关 -->
|
<!-- 三方云服务相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>software.amazon.awssdk</groupId>
|
<groupId>software.amazon.awssdk</groupId>
|
||||||
|
|
|
||||||
|
|
@ -349,27 +349,4 @@ public class CollectionUtils {
|
||||||
return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
|
return (LinkedHashSet<T>) toCollection(LinkedHashSet.class, elementType, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean dfs(Long node, Map<Long, Set<Long>> graph) {
|
|
||||||
return dfs(node, graph, new HashSet<>(), new HashSet<>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean dfs(Long node, Map<Long, Set<Long>> graph, Set<Long> visited, Set<Long> inStack) {
|
|
||||||
if (inStack.contains(node)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (visited.contains(node)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
visited.add(node);
|
|
||||||
inStack.add(node);
|
|
||||||
Set<Long> neighbors = graph.getOrDefault(node, Collections.emptySet());
|
|
||||||
for (Long neighbor : neighbors) {
|
|
||||||
if (dfs(neighbor, graph, visited, inStack)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
inStack.remove(node);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -302,21 +302,6 @@ public class LocalDateTimeUtils {
|
||||||
return timeRanges;
|
return timeRanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取从开始日期起的日期列表
|
|
||||||
*
|
|
||||||
* @param startDate 开始日期
|
|
||||||
* @param days 天数
|
|
||||||
* @return 日期列表,包含开始日期
|
|
||||||
*/
|
|
||||||
public static List<LocalDate> getDateList(LocalDate startDate, int days) {
|
|
||||||
List<LocalDate> dateList = new ArrayList<>(days);
|
|
||||||
for (int i = 0; i < days; i++) {
|
|
||||||
dateList.add(startDate.plusDays(i));
|
|
||||||
}
|
|
||||||
return dateList;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 格式化时间范围
|
* 格式化时间范围
|
||||||
*
|
*
|
||||||
|
|
@ -350,27 +335,6 @@ public class LocalDateTimeUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定日期所在季度的第一天
|
|
||||||
*
|
|
||||||
* @param date 日期
|
|
||||||
* @return 所在季度的第一天
|
|
||||||
*/
|
|
||||||
public static LocalDate getQuarterStart(LocalDate date) {
|
|
||||||
Month firstMonthOfQuarter = date.getMonth().firstMonthOfQuarter();
|
|
||||||
return LocalDate.of(date.getYear(), firstMonthOfQuarter, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取指定日期所在周的第一天(周一)
|
|
||||||
*
|
|
||||||
* @param date 日期
|
|
||||||
* @return 所在周的周一
|
|
||||||
*/
|
|
||||||
public static LocalDate getWeekStart(LocalDate date) {
|
|
||||||
return date.with(DayOfWeek.MONDAY);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
|
* 将给定的 {@link LocalDateTime} 转换为自 Unix 纪元时间(1970-01-01T00:00:00Z)以来的秒数。
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
package cn.iocoder.yudao.framework.common.util.http;
|
package cn.iocoder.yudao.framework.common.util.http;
|
||||||
|
|
||||||
import cn.hutool.core.codec.Base64;
|
import cn.hutool.core.codec.Base64;
|
||||||
|
import cn.hutool.core.map.TableMap;
|
||||||
import cn.hutool.core.net.url.UrlBuilder;
|
import cn.hutool.core.net.url.UrlBuilder;
|
||||||
|
import cn.hutool.core.util.ReflectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.http.HttpRequest;
|
import cn.hutool.http.HttpRequest;
|
||||||
import cn.hutool.http.HttpResponse;
|
import cn.hutool.http.HttpResponse;
|
||||||
|
|
@ -37,10 +39,8 @@ public class HttpUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解码 URL 参数(query parameter)
|
* 解码 URL 参数
|
||||||
* 注意:此方法会将 + 解码为空格,适用于 query parameter,不适用于 URL path
|
|
||||||
*
|
*
|
||||||
* @see #decodeUrlPath(String)
|
|
||||||
* @param value 参数
|
* @param value 参数
|
||||||
* @return 解码后的参数
|
* @return 解码后的参数
|
||||||
*/
|
*/
|
||||||
|
|
@ -49,25 +49,14 @@ public class HttpUtils {
|
||||||
return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
|
return URLDecoder.decode(value, StandardCharsets.UTF_8.name());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@SuppressWarnings("unchecked")
|
||||||
* 解码 URL 路径
|
|
||||||
* 与 {@link #decodeUtf8(String)} 不同,此方法不会将 + 解码为空格,保持 + 为字面字符
|
|
||||||
* 适用于 URL path 部分的解码
|
|
||||||
*
|
|
||||||
* @param path URL 路径
|
|
||||||
* @return 解码后的路径
|
|
||||||
*/
|
|
||||||
@SneakyThrows
|
|
||||||
public static String decodeUrlPath(String path) {
|
|
||||||
// 先将 + 替换为 %2B,避免被 URLDecoder 解码为空格
|
|
||||||
String encoded = path.replace("+", "%2B");
|
|
||||||
return URLDecoder.decode(encoded, StandardCharsets.UTF_8.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String replaceUrlQuery(String url, String key, String value) {
|
public static String replaceUrlQuery(String url, String key, String value) {
|
||||||
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
|
UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());
|
||||||
// 先移除;再添加
|
// 先移除
|
||||||
builder.getQuery().remove(key);
|
TableMap<CharSequence, CharSequence> query = (TableMap<CharSequence, CharSequence>)
|
||||||
|
ReflectUtil.getFieldValue(builder.getQuery(), "query");
|
||||||
|
query.remove(key);
|
||||||
|
// 后添加
|
||||||
builder.addQuery(key, value);
|
builder.addQuery(key, value);
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -173,24 +173,6 @@ public class JsonUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析 JSON 字符串成指定类型的对象,如果解析失败,则返回 null
|
|
||||||
*
|
|
||||||
* @param text 字符串
|
|
||||||
* @param clazz 类型
|
|
||||||
* @return 指定类型的对象
|
|
||||||
*/
|
|
||||||
public static <T> T parseObjectQuietly(String text, Class<T> clazz) {
|
|
||||||
if (StrUtil.isEmpty(text)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return objectMapper.readValue(text, clazz);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> List<T> parseArray(String text, Class<T> clazz) {
|
public static <T> List<T> parseArray(String text, Class<T> clazz) {
|
||||||
if (StrUtil.isEmpty(text)) {
|
if (StrUtil.isEmpty(text)) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
|
|
|
||||||
|
|
@ -60,11 +60,6 @@ public class ObjectUtils {
|
||||||
return Arrays.asList(array).contains(obj);
|
return Arrays.asList(array).contains(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public static <T> boolean notEqualsAny(T obj, T... array) {
|
|
||||||
return !Arrays.asList(array).contains(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isNotAllEmpty(Object... objs) {
|
public static boolean isNotAllEmpty(Object... objs) {
|
||||||
return !ObjectUtil.isAllEmpty(objs);
|
return !ObjectUtil.isAllEmpty(objs);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
package cn.iocoder.yudao.framework.common.util.http;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link HttpUtils} 的单元测试
|
|
||||||
*/
|
|
||||||
public class HttpUtilsTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReplaceUrlQuery_replace() {
|
|
||||||
// 准备参数
|
|
||||||
String url = "https://www.iocoder.cn/path?a=1&b=2";
|
|
||||||
// 调用
|
|
||||||
String result = HttpUtils.replaceUrlQuery(url, "a", "3");
|
|
||||||
// 断言:被替换的 key 会移到末尾,原顺序的其它参数保留
|
|
||||||
assertEquals("https://www.iocoder.cn/path?b=2&a=3", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReplaceUrlQuery_add() {
|
|
||||||
// 准备参数
|
|
||||||
String url = "https://www.iocoder.cn/path?a=1";
|
|
||||||
// 调用
|
|
||||||
String result = HttpUtils.replaceUrlQuery(url, "b", "2");
|
|
||||||
// 断言
|
|
||||||
assertEquals("https://www.iocoder.cn/path?a=1&b=2", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReplaceUrlQuery_noQuery() {
|
|
||||||
// 准备参数:原 URL 没有 query
|
|
||||||
String url = "https://www.iocoder.cn/path";
|
|
||||||
// 调用
|
|
||||||
String result = HttpUtils.replaceUrlQuery(url, "a", "1");
|
|
||||||
// 断言
|
|
||||||
assertEquals("https://www.iocoder.cn/path?a=1", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testReplaceUrlQuery_emptyValue() {
|
|
||||||
// 准备参数:value 为空字符串
|
|
||||||
String url = "https://www.iocoder.cn/path?a=1";
|
|
||||||
// 调用
|
|
||||||
String result = HttpUtils.replaceUrlQuery(url, "a", "");
|
|
||||||
// 断言:保留 key,value 为空
|
|
||||||
assertEquals("https://www.iocoder.cn/path?a=", result);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -8,7 +8,6 @@ import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||||
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
||||||
import lombok.NonNull;
|
import lombok.NonNull;
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -26,46 +25,44 @@ import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@UtilityClass
|
|
||||||
public class AreaUtils {
|
public class AreaUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 SEARCHER
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||||
|
private final static AreaUtils INSTANCE = new AreaUtils();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Area 内存缓存,提升访问速度
|
* Area 内存缓存,提升访问速度
|
||||||
*/
|
*/
|
||||||
private static Map<Integer, Area> areas;
|
private static Map<Integer, Area> areas;
|
||||||
|
|
||||||
static {
|
private AreaUtils() {
|
||||||
init();
|
long now = System.currentTimeMillis();
|
||||||
}
|
areas = new HashMap<>();
|
||||||
|
areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0,
|
||||||
/**
|
null, new ArrayList<>()));
|
||||||
* 初始化
|
// 从 csv 中加载数据
|
||||||
*/
|
List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows();
|
||||||
private static void init() {
|
rows.remove(0); // 删除 header
|
||||||
try {
|
for (CsvRow row : rows) {
|
||||||
long now = System.currentTimeMillis();
|
// 创建 Area 对象
|
||||||
areas = new HashMap<>();
|
Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)),
|
||||||
areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, "全球", 0, null, new ArrayList<>()));
|
null, new ArrayList<>());
|
||||||
// 从 csv 中加载数据
|
// 添加到 areas 中
|
||||||
List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader("area.csv")).getRows();
|
areas.put(area.getId(), area);
|
||||||
rows.remove(0); // 删除 header
|
|
||||||
for (CsvRow row : rows) {
|
|
||||||
Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)), null, new ArrayList<>());
|
|
||||||
areas.put(area.getId(), area);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取
|
|
||||||
for (CsvRow row : rows) {
|
|
||||||
Area area = areas.get(Integer.valueOf(row.get(0))); // 自己
|
|
||||||
Area parent = areas.get(Integer.valueOf(row.get(3))); // 父
|
|
||||||
Assert.isTrue(area != parent, "{}:父子节点相同", area.getName());
|
|
||||||
area.setParent(parent);
|
|
||||||
parent.getChildren().add(area);
|
|
||||||
}
|
|
||||||
log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("AreaUtils 初始化失败", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 构建父子关系:因为 Area 中没有 parentId 字段,所以需要重复读取
|
||||||
|
for (CsvRow row : rows) {
|
||||||
|
Area area = areas.get(Integer.valueOf(row.get(0))); // 自己
|
||||||
|
Area parent = areas.get(Integer.valueOf(row.get(3))); // 父
|
||||||
|
Assert.isTrue(area != parent, "{}:父子节点相同", area.getName());
|
||||||
|
area.setParent(parent);
|
||||||
|
parent.getChildren().add(area);
|
||||||
|
}
|
||||||
|
log.info("启动加载 AreaUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,11 @@ package cn.iocoder.yudao.framework.ip.core.utils;
|
||||||
import cn.hutool.core.io.resource.ResourceUtil;
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import lombok.experimental.UtilityClass;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.lionsoul.ip2region.xdb.Searcher;
|
import org.lionsoul.ip2region.xdb.Searcher;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP 工具类
|
* IP 工具类
|
||||||
*
|
*
|
||||||
|
|
@ -15,29 +16,30 @@ import org.lionsoul.ip2region.xdb.Searcher;
|
||||||
* @author wanglhup
|
* @author wanglhup
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@UtilityClass
|
|
||||||
public class IPUtils {
|
public class IPUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 SEARCHER
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("InstantiationOfUtilityClass")
|
||||||
|
private final static IPUtils INSTANCE = new IPUtils();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IP 查询器,启动加载到内存中
|
* IP 查询器,启动加载到内存中
|
||||||
*/
|
*/
|
||||||
private static Searcher SEARCHER;
|
private static Searcher SEARCHER;
|
||||||
|
|
||||||
static {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化
|
* 私有化构造
|
||||||
*/
|
*/
|
||||||
private static void init() {
|
private IPUtils() {
|
||||||
try {
|
try {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
byte[] bytes = ResourceUtil.readBytes("ip2region.xdb");
|
byte[] bytes = ResourceUtil.readBytes("ip2region.xdb");
|
||||||
SEARCHER = Searcher.newWithBuffer(bytes);
|
SEARCHER = Searcher.newWithBuffer(bytes);
|
||||||
log.info("启动加载 IPUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
log.info("启动加载 IPUtils 成功,耗时 ({}) 毫秒", System.currentTimeMillis() - now);
|
||||||
} catch (Exception e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("IPUtils 初始化失败", e);
|
log.error("启动加载 IPUtils 失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,7 @@ import cn.iocoder.yudao.framework.ip.core.Area;
|
||||||
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AreaUtils} 的单元测试
|
* {@link AreaUtils} 的单元测试
|
||||||
|
|
@ -36,46 +31,6 @@ public class AreaUtilsTest {
|
||||||
assertEquals(AreaUtils.format(110105), "北京市 北京市 朝阳区");
|
assertEquals(AreaUtils.format(110105), "北京市 北京市 朝阳区");
|
||||||
assertEquals(AreaUtils.format(1), "中国");
|
assertEquals(AreaUtils.format(1), "中国");
|
||||||
assertEquals(AreaUtils.format(2), "蒙古");
|
assertEquals(AreaUtils.format(2), "蒙古");
|
||||||
// 中国台湾省:省/市/区三级
|
|
||||||
assertEquals(AreaUtils.format(710101), "台湾省 台北市 中正区");
|
|
||||||
// 自定义分隔符
|
|
||||||
assertEquals(AreaUtils.format(110105, "/"), "北京市/北京市/朝阳区");
|
|
||||||
// 不存在的编号
|
|
||||||
assertNull(AreaUtils.format(-1));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseArea() {
|
|
||||||
// 调用:通过路径解析得到地区
|
|
||||||
Area area = AreaUtils.parseArea("北京市/北京市/朝阳区");
|
|
||||||
// 断言
|
|
||||||
assertNotNull(area);
|
|
||||||
assertEquals(area.getId(), 110105);
|
|
||||||
// 路径不存在时返回 null
|
|
||||||
assertNull(AreaUtils.parseArea("不存在/路径"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetParentIdByType() {
|
|
||||||
// 调用:朝阳区向上找省
|
|
||||||
Integer provinceId = AreaUtils.getParentIdByType(110105, AreaTypeEnum.PROVINCE);
|
|
||||||
// 断言
|
|
||||||
assertEquals(provinceId, 110000);
|
|
||||||
// 自身就是目标类型
|
|
||||||
assertEquals(AreaUtils.getParentIdByType(110000, AreaTypeEnum.PROVINCE), 110000);
|
|
||||||
// 不存在的编号返回 null
|
|
||||||
assertNull(AreaUtils.getParentIdByType(-1, AreaTypeEnum.PROVINCE));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testGetByType() {
|
|
||||||
// 调用:获取所有省份
|
|
||||||
List<Area> provinces = AreaUtils.getByType(AreaTypeEnum.PROVINCE, area -> area);
|
|
||||||
// 断言:包含北京、台湾、香港、澳门
|
|
||||||
assertTrue(provinces.stream().anyMatch(area -> "北京市".equals(area.getName())));
|
|
||||||
assertTrue(provinces.stream().anyMatch(area -> "台湾省".equals(area.getName())));
|
|
||||||
assertTrue(provinces.stream().anyMatch(area -> "香港特别行政区".equals(area.getName())));
|
|
||||||
assertTrue(provinces.stream().anyMatch(area -> "澳门特别行政区".equals(area.getName())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -119,31 +119,6 @@ public interface BaseMapperX<T> extends MPJBaseMapper<T> {
|
||||||
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2).eq(field3, value3));
|
return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2).eq(field3, value3));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获得满足条件的一条记录,并使用 FOR UPDATE 锁定。
|
|
||||||
*
|
|
||||||
* 注意:需要在事务中调用,否则锁会立即释放。
|
|
||||||
*
|
|
||||||
* @param queryWrapper 查询条件
|
|
||||||
* @return 实体
|
|
||||||
*/
|
|
||||||
default T selectOneForUpdate(LambdaQueryWrapper<T> queryWrapper) {
|
|
||||||
return selectOne(queryWrapper.last("FOR UPDATE"));
|
|
||||||
}
|
|
||||||
|
|
||||||
default T selectOneForUpdate(SFunction<T, ?> field, Object value) {
|
|
||||||
return selectOneForUpdate(new LambdaQueryWrapper<T>().eq(field, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
default T selectOneForUpdate(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {
|
|
||||||
return selectOneForUpdate(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));
|
|
||||||
}
|
|
||||||
|
|
||||||
default T selectOneForUpdate(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,
|
|
||||||
SFunction<T, ?> field3, Object value3) {
|
|
||||||
return selectOneForUpdate(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2).eq(field3, value3));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取满足条件的第 1 条记录
|
* 获取满足条件的第 1 条记录
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -36,17 +36,6 @@
|
||||||
<groupId>io.github.openfeign</groupId>
|
<groupId>io.github.openfeign</groupId>
|
||||||
<artifactId>feign-okhttp</artifactId>
|
<artifactId>feign-okhttp</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!--
|
|
||||||
TODO 芋艿:WxJava 4.8.x 的 AbstractWxMpConfigStorageConfiguration 仍引用了 HttpClient 4.x 的
|
|
||||||
org.apache.http.ssl.TrustStrategy 类。升级 Spring Cloud Alibaba 到 2025.0.0.0 后,Nacos 不再
|
|
||||||
传递 HttpClient 4.x(httpcore),导致 ClassNotFoundException。
|
|
||||||
临时解决:显式引入 httpclient 4.x。待 WxJava 修复后移除。
|
|
||||||
-->
|
|
||||||
<dependency>
|
|
||||||
<groupId>org.apache.httpcomponents</groupId>
|
|
||||||
<artifactId>httpclient</artifactId>
|
|
||||||
<version>4.5.14</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- 工具相关 -->
|
<!-- 工具相关 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import uk.co.jemos.podam.api.PodamFactory;
|
||||||
import uk.co.jemos.podam.api.PodamFactoryImpl;
|
import uk.co.jemos.podam.api.PodamFactoryImpl;
|
||||||
|
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
@ -53,10 +52,6 @@ public class RandomUtils {
|
||||||
}
|
}
|
||||||
return RandomUtil.randomInt();
|
return RandomUtil.randomInt();
|
||||||
});
|
});
|
||||||
// BigDecimal:限制精度在 DECIMAL(10,2) 范围内,避免 H2 等数据库溢出
|
|
||||||
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(BigDecimal.class,
|
|
||||||
(dataProviderStrategy, attributeMetadata, map) ->
|
|
||||||
BigDecimal.valueOf(RandomUtil.randomInt(0, 10000000), 2));
|
|
||||||
// LocalDateTime
|
// LocalDateTime
|
||||||
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(LocalDateTime.class,
|
PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(LocalDateTime.class,
|
||||||
(dataProviderStrategy, attributeMetadata, map) -> randomLocalDateTime());
|
(dataProviderStrategy, attributeMetadata, map) -> randomLocalDateTime());
|
||||||
|
|
|
||||||
|
|
@ -37,14 +37,8 @@ public class BannerApplicationRunner implements ApplicationRunner {
|
||||||
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
||||||
// ERP 系统
|
// ERP 系统
|
||||||
System.out.println("[ERP 系统 yudao-module-erp - 教程][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
System.out.println("[ERP 系统 yudao-module-erp - 教程][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
||||||
// WMS 仓库管理系统
|
|
||||||
System.out.println("[WMS 仓库管理系统 yudao-module-wms - 教程][参考 https://cloud.iocoder.cn/wms/build/ 开启]");
|
|
||||||
// CRM 系统
|
// CRM 系统
|
||||||
System.out.println("[CRM 系统 yudao-module-crm - 教程][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
System.out.println("[CRM 系统 yudao-module-crm - 教程][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
||||||
// MES 系统
|
|
||||||
System.out.println("[MES 系统 yudao-module-mes - 教程][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
|
|
||||||
// IM 即时通讯
|
|
||||||
System.out.println("[IM 即时通讯 yudao-module-im - 教程][参考 https://cloud.iocoder.cn/im/build/ 开启]");
|
|
||||||
// 微信公众号
|
// 微信公众号
|
||||||
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
||||||
// 支付平台
|
// 支付平台
|
||||||
|
|
|
||||||
|
|
@ -410,43 +410,25 @@ public class GlobalExceptionHandler {
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||||
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
"[ERP 系统 yudao-module-erp - 表结构未导入][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
||||||
}
|
}
|
||||||
// 6. WMS 仓库管理系统
|
// 6. CRM 系统
|
||||||
if (message.contains("wms_")) {
|
|
||||||
log.error("[WMS 仓库管理系统 yudao-module-wms - 表结构未导入][参考 https://cloud.iocoder.cn/wms/build/ 开启]");
|
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
|
||||||
"[WMS 仓库管理系统 yudao-module-wms - 表结构未导入][参考 https://cloud.iocoder.cn/wms/build/ 开启]");
|
|
||||||
}
|
|
||||||
// 7. CRM 系统
|
|
||||||
if (message.contains("crm_")) {
|
if (message.contains("crm_")) {
|
||||||
log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
log.error("[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||||
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
"[CRM 系统 yudao-module-crm - 表结构未导入][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
||||||
}
|
}
|
||||||
// 8. MES 系统
|
// 7. 支付平台
|
||||||
if (message.contains("mes_")) {
|
|
||||||
log.error("[MES 系统 yudao-module-mes - 表结构未导入][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
|
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
|
||||||
"[MES 系统 yudao-module-mes - 表结构未导入][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
|
|
||||||
}
|
|
||||||
// 9. IM 即时通讯
|
|
||||||
if (message.contains("im_")) {
|
|
||||||
log.error("[IM 即时通讯 yudao-module-im - 表结构未导入][参考 https://cloud.iocoder.cn/im/build/ 开启]");
|
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
|
||||||
"[IM 即时通讯 yudao-module-im - 表结构未导入][参考 https://cloud.iocoder.cn/im/build/ 开启]");
|
|
||||||
}
|
|
||||||
// 10. 支付平台
|
|
||||||
if (message.contains("pay_")) {
|
if (message.contains("pay_")) {
|
||||||
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
log.error("[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||||
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
"[支付模块 yudao-module-pay - 表结构未导入][参考 https://cloud.iocoder.cn/pay/build/ 开启]");
|
||||||
}
|
}
|
||||||
// 11. AI 大模型
|
// 8. AI 大模型
|
||||||
if (message.contains("ai_")) {
|
if (message.contains("ai_")) {
|
||||||
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
log.error("[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||||
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
|
||||||
}
|
}
|
||||||
// 12. IoT 物联网
|
// 9. IoT 物联网
|
||||||
if (message.contains("iot_")) {
|
if (message.contains("iot_")) {
|
||||||
log.error("[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
|
log.error("[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
|
||||||
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
|
||||||
|
|
|
||||||
|
|
@ -39,14 +39,8 @@ public class BannerApplicationRunner implements ApplicationRunner {
|
||||||
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
||||||
// ERP 系统
|
// ERP 系统
|
||||||
System.out.println("[ERP 系统 yudao-module-erp - 教程][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
System.out.println("[ERP 系统 yudao-module-erp - 教程][参考 https://cloud.iocoder.cn/erp/build/ 开启]");
|
||||||
// WMS 仓库管理系统
|
|
||||||
System.out.println("[WMS 仓库管理系统 yudao-module-wms - 教程][参考 https://cloud.iocoder.cn/wms/build/ 开启]");
|
|
||||||
// CRM 系统
|
// CRM 系统
|
||||||
System.out.println("[CRM 系统 yudao-module-crm - 教程][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
System.out.println("[CRM 系统 yudao-module-crm - 教程][参考 https://cloud.iocoder.cn/crm/build/ 开启]");
|
||||||
// MES 系统
|
|
||||||
System.out.println("[MES 系统 yudao-module-mes - 教程][参考 https://cloud.iocoder.cn/mes/build/ 开启]");
|
|
||||||
// IM 即时通讯
|
|
||||||
System.out.println("[IM 即时通讯 yudao-module-im - 教程][参考 https://cloud.iocoder.cn/im/build/ 开启]");
|
|
||||||
// 微信公众号
|
// 微信公众号
|
||||||
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
||||||
// 支付平台
|
// 支付平台
|
||||||
|
|
|
||||||
|
|
@ -192,20 +192,6 @@ spring:
|
||||||
- Path=/admin-api/iot/**
|
- Path=/admin-api/iot/**
|
||||||
filters:
|
filters:
|
||||||
- RewritePath=/admin-api/iot/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
- RewritePath=/admin-api/iot/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
||||||
## mes-server 服务
|
|
||||||
- id: mes-admin-api # 路由的编号
|
|
||||||
uri: grayLb://mes-server
|
|
||||||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
|
||||||
- Path=/admin-api/mes/**
|
|
||||||
filters:
|
|
||||||
- RewritePath=/admin-api/mes/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
|
||||||
## wms-server 服务
|
|
||||||
- id: wms-admin-api # 路由的编号
|
|
||||||
uri: grayLb://wms-server
|
|
||||||
predicates: # 断言,作为路由的匹配条件,对应 RouteDefinition 数组
|
|
||||||
- Path=/admin-api/wms/**
|
|
||||||
filters:
|
|
||||||
- RewritePath=/admin-api/wms/v3/api-docs, /v3/api-docs # 配置,保证转发到 /v3/api-docs
|
|
||||||
x-forwarded:
|
x-forwarded:
|
||||||
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
prefix-enabled: false # 避免 Swagger 重复带上额外的 /admin-api/system 前缀
|
||||||
default-filters: # 全局过滤器,对应 GatewayFilterDefinition 数组
|
default-filters: # 全局过滤器,对应 GatewayFilterDefinition 数组
|
||||||
|
|
@ -265,15 +251,9 @@ knife4j:
|
||||||
- name: iot-server
|
- name: iot-server
|
||||||
service-name: iot-server
|
service-name: iot-server
|
||||||
url: /admin-api/iot/v3/api-docs
|
url: /admin-api/iot/v3/api-docs
|
||||||
- name: mes-server
|
|
||||||
service-name: mes-server
|
|
||||||
url: /admin-api/mes/v3/api-docs
|
|
||||||
- name: wms-server
|
|
||||||
service-name: wms-server
|
|
||||||
url: /admin-api/wms/v3/api-docs
|
|
||||||
|
|
||||||
--- #################### 芋道相关配置 ####################
|
--- #################### 芋道相关配置 ####################
|
||||||
|
|
||||||
yudao:
|
yudao:
|
||||||
info:
|
info:
|
||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
|
|
@ -19,9 +19,9 @@
|
||||||
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
国外:OpenAI、Ollama、Midjourney、StableDiffusion、Suno
|
||||||
</description>
|
</description>
|
||||||
<properties>
|
<properties>
|
||||||
<spring-ai.version>1.1.5</spring-ai.version>
|
<spring-ai.version>1.1.2</spring-ai.version>
|
||||||
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
|
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud.ai/spring-ai-alibaba -->
|
||||||
<alibaba-ai.version>1.1.2.2</alibaba-ai.version>
|
<alibaba-ai.version>1.1.0.0-RC2</alibaba-ai.version>
|
||||||
<tinyflow.version>1.2.6</tinyflow.version>
|
<tinyflow.version>1.2.6</tinyflow.version>
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,15 +83,6 @@ public class AiKnowledgeSegmentController {
|
||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@DeleteMapping("/delete")
|
|
||||||
@Operation(summary = "删除段落")
|
|
||||||
@Parameter(name = "id", description = "段落编号", required = true, example = "1024")
|
|
||||||
@PreAuthorize("@ss.hasPermission('ai:knowledge:delete')")
|
|
||||||
public CommonResult<Boolean> deleteKnowledgeSegment(@RequestParam("id") Long id) {
|
|
||||||
segmentService.deleteKnowledgeSegment(id);
|
|
||||||
return success(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@GetMapping("/split")
|
@GetMapping("/split")
|
||||||
@Operation(summary = "切片内容")
|
@Operation(summary = "切片内容")
|
||||||
@Parameters({
|
@Parameters({
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,6 @@ public class AiImageServiceImpl implements AiImageService {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Async
|
@Async
|
||||||
@SuppressWarnings("ConstantValue")
|
|
||||||
public void executeDrawImage(AiImageDO image, AiImageDrawReqVO reqVO, AiModelDO model) {
|
public void executeDrawImage(AiImageDO image, AiImageDrawReqVO reqVO, AiModelDO model) {
|
||||||
try {
|
try {
|
||||||
// 1.1 构建请求
|
// 1.1 构建请求
|
||||||
|
|
@ -165,8 +164,8 @@ public class AiImageServiceImpl implements AiImageService {
|
||||||
.build();
|
.build();
|
||||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
|
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.TONG_YI.getPlatform())) {
|
||||||
return DashScopeImageOptions.builder()
|
return DashScopeImageOptions.builder()
|
||||||
.model(model.getModel()).n(1)
|
.withModel(model.getModel()).withN(1)
|
||||||
.height(draw.getHeight()).width(draw.getWidth())
|
.withHeight(draw.getHeight()).withWidth(draw.getWidth())
|
||||||
.build();
|
.build();
|
||||||
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
|
} else if (ObjUtil.equal(model.getPlatform(), AiPlatformEnum.YI_YAN.getPlatform())) {
|
||||||
return QianFanImageOptions.builder()
|
return QianFanImageOptions.builder()
|
||||||
|
|
|
||||||
|
|
@ -98,13 +98,6 @@ public interface AiKnowledgeSegmentService {
|
||||||
*/
|
*/
|
||||||
void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
|
void updateKnowledgeSegmentStatus(AiKnowledgeSegmentUpdateStatusReqVO reqVO);
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除知识库段落
|
|
||||||
*
|
|
||||||
* @param id 段落编号
|
|
||||||
*/
|
|
||||||
void deleteKnowledgeSegment(Long id);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重新索引知识库下的所有文档段落
|
* 重新索引知识库下的所有文档段落
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -141,19 +141,6 @@ public class AiKnowledgeSegmentServiceImpl implements AiKnowledgeSegmentService
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void deleteKnowledgeSegment(Long id) {
|
|
||||||
// 1. 校验段落存在
|
|
||||||
AiKnowledgeSegmentDO segment = validateKnowledgeSegmentExists(id);
|
|
||||||
|
|
||||||
// 2. 删除向量
|
|
||||||
VectorStore vectorStore = getVectorStoreById(segment.getKnowledgeId());
|
|
||||||
deleteVectorStore(vectorStore, segment);
|
|
||||||
|
|
||||||
// 3. 删除段落记录
|
|
||||||
segmentMapper.deleteById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void deleteKnowledgeSegmentByDocumentId(Long documentId) {
|
public void deleteKnowledgeSegmentByDocumentId(Long documentId) {
|
||||||
// 1. 查询需要删除的段落
|
// 1. 查询需要删除的段落
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,7 @@
|
||||||
package cn.iocoder.yudao.module.ai.util;
|
package cn.iocoder.yudao.module.ai.util;
|
||||||
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
|
||||||
import cn.hutool.core.util.ObjUtil;
|
import cn.hutool.core.util.ObjUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
|
||||||
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||||
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
import cn.iocoder.yudao.module.ai.enums.model.AiPlatformEnum;
|
||||||
|
|
@ -35,28 +33,6 @@ public class AiUtils {
|
||||||
public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER";
|
public static final String TOOL_CONTEXT_LOGIN_USER = "LOGIN_USER";
|
||||||
public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID";
|
public static final String TOOL_CONTEXT_TENANT_ID = "TENANT_ID";
|
||||||
|
|
||||||
/**
|
|
||||||
* 通义千问支持多模态的模型
|
|
||||||
*
|
|
||||||
* @see <a href="https://bailian.console.aliyun.com/cn-beijing/?tab=model#/model-market/all?providers=qwen&capabilities=VU">模型广场</a>
|
|
||||||
* @see <a href="https://help.aliyun.com/zh/model-studio/error-code#error-url">必须开启 withMultiModel 参数</a>
|
|
||||||
*/
|
|
||||||
public static final Set<String> TONG_YI_MULTI_MODELS = SetUtils.asSet(
|
|
||||||
// qwen3.5 / 3.6 系列(统一多模态主干)
|
|
||||||
"qwen3.6-plus", "qwen3.6-flash",
|
|
||||||
"qwen3.5-plus", "qwen3.5-flash",
|
|
||||||
// qwen-vl 视觉理解
|
|
||||||
"qwen3-vl-plus", "qwen3-vl-flash",
|
|
||||||
"qwen-vl-max", "qwen-vl-plus",
|
|
||||||
"qwen2.5-vl-72b-instruct", "qwen2.5-vl-32b-instruct",
|
|
||||||
"qwen2.5-vl-7b-instruct", "qwen2.5-vl-3b-instruct",
|
|
||||||
// qvq 视觉推理
|
|
||||||
"qvq-max", "qvq-plus",
|
|
||||||
// qwen-omni 全模态
|
|
||||||
"qwen3.5-omni-plus", "qwen3.5-omni-flash",
|
|
||||||
"qwen3-omni-flash", "qwen-omni-turbo"
|
|
||||||
);
|
|
||||||
|
|
||||||
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
|
public static ChatOptions buildChatOptions(AiPlatformEnum platform, String model, Double temperature, Integer maxTokens) {
|
||||||
return buildChatOptions(platform, model, temperature, maxTokens, null, null);
|
return buildChatOptions(platform, model, temperature, maxTokens, null, null);
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +44,9 @@ public class AiUtils {
|
||||||
// noinspection EnhancedSwitchMigration
|
// noinspection EnhancedSwitchMigration
|
||||||
switch (platform) {
|
switch (platform) {
|
||||||
case TONG_YI:
|
case TONG_YI:
|
||||||
return DashScopeChatOptions.builder().model(model).temperature(temperature).maxToken(maxTokens)
|
return DashScopeChatOptions.builder().withModel(model).withTemperature(temperature).withMaxToken(maxTokens)
|
||||||
.enableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
|
.withEnableThinking(true) // TODO 芋艿:默认都开启 thinking 模式,后续可以让用户配置
|
||||||
.multiModel(TONG_YI_MULTI_MODELS.contains(model)) // 是否多模态模型
|
.withToolCallbacks(toolCallbacks).withToolContext(toolContext).build();
|
||||||
.toolCallbacks(toolCallbacks).toolContext(toolContext).build();
|
|
||||||
case YI_YAN:
|
case YI_YAN:
|
||||||
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
return QianFanChatOptions.builder().model(model).temperature(temperature).maxTokens(maxTokens).build();
|
||||||
case DEEP_SEEK:
|
case DEEP_SEEK:
|
||||||
|
|
@ -150,13 +125,10 @@ public class AiUtils {
|
||||||
|| response.getResult().getOutput() == null) {
|
|| response.getResult().getOutput() == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
AssistantMessage output = response.getResult().getOutput();
|
if (response.getResult().getOutput() instanceof DeepSeekAssistantMessage) {
|
||||||
// DeepSeek 通过专属 AssistantMessage 暴露 reasoningContent
|
return ((DeepSeekAssistantMessage) (response.getResult().getOutput())).getReasoningContent();
|
||||||
if (output instanceof DeepSeekAssistantMessage) {
|
|
||||||
return ((DeepSeekAssistantMessage) output).getReasoningContent();
|
|
||||||
}
|
}
|
||||||
// 通义千问等通过 metadata 透传 reasoningContent
|
return null;
|
||||||
return MapUtil.getStr(output.getMetadata(), "reasoningContent");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,15 +34,15 @@ public class TongYiChatModelTests {
|
||||||
|
|
||||||
private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
|
private final DashScopeChatModel chatModel = DashScopeChatModel.builder()
|
||||||
.dashScopeApi(DashScopeApi.builder()
|
.dashScopeApi(DashScopeApi.builder()
|
||||||
.apiKey("sk-cd9f39e99ea54840bd1888282325f55a") // https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key 获取密钥
|
.apiKey("sk-47aa124781be4bfb95244cc62f63f7d0")
|
||||||
.build())
|
.build())
|
||||||
.defaultOptions(DashScopeChatOptions.builder()
|
.defaultOptions(DashScopeChatOptions.builder()
|
||||||
.multiModel(true) // 注意:当使用 qwen3.6-plus 等多模态模型,需要设置为 true,可见 https://help.aliyun.com/zh/model-studio/error-code#error-url 链接
|
// .withModel("qwen1.5-72b-chat") // 模型
|
||||||
.model("qwen3.6-plus") // 模型
|
.withModel("qwen3-235b-a22b-thinking-2507") // 模型
|
||||||
// .model("deepseek-r1") // 模型(deepseek-r1)
|
// .withModel("deepseek-r1") // 模型(deepseek-r1)
|
||||||
// .model("deepseek-v3") // 模型(deepseek-v3)
|
// .withModel("deepseek-v3") // 模型(deepseek-v3)
|
||||||
// .model("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
|
// .withModel("deepseek-r1-distill-qwen-1.5b") // 模型(deepseek-r1-distill-qwen-1.5b)
|
||||||
// .enableThinking(true)
|
// .withEnableThinking(true)
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
|
@ -85,9 +85,9 @@ public class TongYiChatModelTests {
|
||||||
List<Message> messages = new ArrayList<>();
|
List<Message> messages = new ArrayList<>();
|
||||||
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
messages.add(new UserMessage("详细分析下,如何设计一个电商系统?"));
|
||||||
DashScopeChatOptions options = DashScopeChatOptions.builder()
|
DashScopeChatOptions options = DashScopeChatOptions.builder()
|
||||||
.model("qwen3.6-plus").multiModel(true)
|
.withModel("qwen3-235b-a22b-thinking-2507")
|
||||||
// .withModel("qwen-max-2025-01-25")
|
// .withModel("qwen-max-2025-01-25")
|
||||||
.enableThinking(true) // 必须设置,否则会报错
|
.withEnableThinking(true) // 必须设置,否则会报错
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
|
|
@ -112,8 +112,8 @@ public class TongYiChatModelTests {
|
||||||
Document document01 = new Document("abc");
|
Document document01 = new Document("abc");
|
||||||
Document document02 = new Document("sapring");
|
Document document02 = new Document("sapring");
|
||||||
RerankOptions options = DashScopeRerankOptions.builder()
|
RerankOptions options = DashScopeRerankOptions.builder()
|
||||||
.topN(1)
|
.withTopN(1)
|
||||||
.model("gte-rerank-v2")
|
.withModel("gte-rerank-v2")
|
||||||
.build();
|
.build();
|
||||||
RerankRequest rerankRequest = new RerankRequest(
|
RerankRequest rerankRequest = new RerankRequest(
|
||||||
query,
|
query,
|
||||||
|
|
|
||||||
|
|
@ -12,34 +12,23 @@ import org.springframework.ai.image.ImageResponse;
|
||||||
/**
|
/**
|
||||||
* {@link DashScopeImageModel} 集成测试类
|
* {@link DashScopeImageModel} 集成测试类
|
||||||
*
|
*
|
||||||
* TODO @芋艿:注:spring-ai-alibaba-dashscope(1.1.2.2)的 {@code DashScopeImageApi#resolveImagePath} 未给 {@code wan2.7-image} 加路由分支,
|
|
||||||
* 会落到默认的 {@code text2image/image-synthesis} 异步端点 + 旧版 {@code prompt} 入参,
|
|
||||||
* 而该模型实际要求 {@code multimodal-generation/generation} 同步端点 + {@code messages} 入参,
|
|
||||||
* 端点、异步头、入参结构全部对不上,所以走 SDK 直接调用必失败。
|
|
||||||
*
|
|
||||||
* 临时方案:改用 SDK 已支持的 {@code wan2.6-image}(异步)或 {@code qwen-image}(同步);
|
|
||||||
* 或在项目内同包同名覆盖 {@code DashScopeImageApi},把 {@code wan2.7*} 也路由到 {@code wan2.6-image} 那条 {@code image-generation/generation} 异步分支。
|
|
||||||
*
|
|
||||||
* @author fansili
|
* @author fansili
|
||||||
*/
|
*/
|
||||||
public class TongYiImagesModelTest {
|
public class TongYiImagesModelTest {
|
||||||
|
|
||||||
private final DashScopeImageModel imageModel = DashScopeImageModel.builder()
|
private final DashScopeImageModel imageModel = DashScopeImageModel.builder()
|
||||||
.dashScopeApi(DashScopeImageApi.builder()
|
.dashScopeApi(DashScopeImageApi.builder()
|
||||||
.apiKey("sk-cd9f39e99ea54840bd1888282325f55a") // https://bailian.console.aliyun.com/cn-beijing/?tab=model#/api-key 获取密钥
|
.apiKey("sk-47aa124781be4bfb95244cc62f63f7d0")
|
||||||
.build())
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// TODO @芋艿:
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
@Disabled
|
||||||
public void imageCallTest() {
|
public void imageCallTest() {
|
||||||
// 准备参数
|
// 准备参数
|
||||||
ImageOptions options = DashScopeImageOptions.builder()
|
ImageOptions options = DashScopeImageOptions.builder()
|
||||||
.model("wan2.7-image")
|
.withModel("wanx-v1")
|
||||||
// .withSize("2k")
|
.withHeight(256).withWidth(256)
|
||||||
.height(768).width(768)
|
|
||||||
.n(1)
|
|
||||||
.build();
|
.build();
|
||||||
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
|
ImagePrompt prompt = new ImagePrompt("中国长城!", options);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.bpm.enums.definition;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.RequiredArgsConstructor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* BPM 条件表达式操作符枚举
|
|
||||||
*
|
|
||||||
* @author Lesan
|
|
||||||
*/
|
|
||||||
@RequiredArgsConstructor
|
|
||||||
@Getter
|
|
||||||
public enum BpmConditionOpCodeEnum {
|
|
||||||
|
|
||||||
EQ("==", "等于", " var:getOrDefault(%s, null) == %s "),
|
|
||||||
NE("!=", "不等于", " var:getOrDefault(%s, null) != %s "),
|
|
||||||
GT(">", "大于", " var:getOrDefault(%s, null) > %s "),
|
|
||||||
GE(">=", "大于等于", " var:getOrDefault(%s, null) >= %s "),
|
|
||||||
LT("<", "小于", " var:getOrDefault(%s, null) < %s "),
|
|
||||||
LE("<=", "小于等于", " var:getOrDefault(%s, null) <= %s "),
|
|
||||||
|
|
||||||
CONTAINS("contain", "包含", " var:contains(%s, %s) "),
|
|
||||||
NOT_CONTAINS("!contain", "不包含", " !var:contains(%s, %s) ");
|
|
||||||
|
|
||||||
private final String code;
|
|
||||||
private final String des;
|
|
||||||
private final String symbol;
|
|
||||||
|
|
||||||
public static BpmConditionOpCodeEnum fromCode(String code) {
|
|
||||||
for (BpmConditionOpCodeEnum op : BpmConditionOpCodeEnum.values()) {
|
|
||||||
if (op.code.equalsIgnoreCase(code)) {
|
|
||||||
return op;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("未知操作符: " + code);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.form;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 流程表单字段 VO
|
* 流程表单字段 VO
|
||||||
*/
|
*/
|
||||||
|
|
@ -22,9 +20,5 @@ public class BpmFormFieldVO {
|
||||||
* 字段标题
|
* 字段标题
|
||||||
*/
|
*/
|
||||||
private String title;
|
private String title;
|
||||||
/**
|
|
||||||
* 子表单字段(处理布局组件)
|
|
||||||
*/
|
|
||||||
private List<BpmFormFieldVO> children;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,15 +66,15 @@ public class BpmProcessInstanceCopyController {
|
||||||
Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
|
Map<String, HistoricProcessInstance> processInstanceMap = processInstanceService.getHistoricProcessInstanceMap(
|
||||||
convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
|
convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessInstanceId));
|
||||||
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
|
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertListByFlatMap(pageResult.getList(),
|
||||||
copy -> Stream.of(copy.getStartUserId(), copy.getUserId())));
|
copy -> Stream.of(copy.getStartUserId(), Long.parseLong(copy.getCreator()))));
|
||||||
Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
|
Map<String, BpmProcessDefinitionInfoDO> processDefinitionInfoMap = processDefinitionService.getProcessDefinitionInfoMap(
|
||||||
convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessDefinitionId));
|
convertSet(pageResult.getList(), BpmProcessInstanceCopyDO::getProcessDefinitionId));
|
||||||
return success(convertPage(pageResult, copy -> {
|
return success(convertPage(pageResult, copy -> {
|
||||||
BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
|
BpmProcessInstanceCopyRespVO copyVO = BeanUtils.toBean(copy, BpmProcessInstanceCopyRespVO.class);
|
||||||
MapUtils.findAndThen(userMap, copy.getUserId(),
|
MapUtils.findAndThen(userMap, Long.valueOf(copy.getCreator()),
|
||||||
user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
|
|
||||||
MapUtils.findAndThen(userMap, copy.getStartUserId(),
|
|
||||||
user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
|
user -> copyVO.setStartUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
|
||||||
|
MapUtils.findAndThen(userMap, copy.getStartUserId(),
|
||||||
|
user -> copyVO.setCreateUser(BeanUtils.toBean(user, UserSimpleBaseVO.class)));
|
||||||
MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
|
MapUtils.findAndThen(processInstanceMap, copyVO.getProcessInstanceId(),
|
||||||
processInstance -> {
|
processInstance -> {
|
||||||
copyVO.setSummary(FlowableUtils.getSummary(
|
copyVO.setSummary(FlowableUtils.getSummary(
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ public class BpmOALeaveDO extends BaseDO {
|
||||||
/**
|
/**
|
||||||
* 请假类型
|
* 请假类型
|
||||||
*/
|
*/
|
||||||
private Integer type;
|
private String type;
|
||||||
/**
|
/**
|
||||||
* 原因
|
* 原因
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ import org.flowable.engine.impl.bpmn.behavior.AbstractBpmnActivityBehavior;
|
||||||
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
|
import org.flowable.engine.impl.bpmn.behavior.SequentialMultiInstanceBehavior;
|
||||||
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
|
import org.flowable.engine.impl.persistence.entity.ExecutionEntity;
|
||||||
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
|
@ -54,7 +53,7 @@ public class BpmSequentialMultiInstanceBehavior extends SequentialMultiInstanceB
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
|
Set<Long> assigneeUserIds = (Set<Long>) execution.getVariableLocal(super.collectionVariable, Set.class);
|
||||||
if (assigneeUserIds == null) {
|
if (assigneeUserIds == null) {
|
||||||
assigneeUserIds = new LinkedHashSet<>(taskCandidateInvoker.calculateUsersByTask(execution));
|
assigneeUserIds = taskCandidateInvoker.calculateUsersByTask(execution);
|
||||||
if (CollUtil.isEmpty(assigneeUserIds)) {
|
if (CollUtil.isEmpty(assigneeUserIds)) {
|
||||||
// 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
|
// 特殊:如果没有处理人的情况下,至少有一个 null 空元素,避免自动通过!
|
||||||
// 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
|
// 这样,保证在 BpmUserTaskActivityBehavior 至少创建出一个 Task 任务
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,10 @@ import org.springframework.stereotype.Component;
|
||||||
/**
|
/**
|
||||||
* 根据流程变量 variable 的类型,转换参数的值
|
* 根据流程变量 variable 的类型,转换参数的值
|
||||||
*
|
*
|
||||||
* @deprecated 已无调用方
|
* 目前用于 ConditionNodeConvert 的 buildConditionExpression 方法中
|
||||||
|
*
|
||||||
* @author jason
|
* @author jason
|
||||||
*/
|
*/
|
||||||
@Deprecated // TODO @芋艿:兼容老版本,预计 27 年删除;
|
|
||||||
@Component
|
@Component
|
||||||
public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction {
|
public class VariableConvertByTypeExpressionFunction extends AbstractFlowableVariableExpressionFunction {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util;
|
||||||
|
|
||||||
import cn.hutool.core.map.MapUtil;
|
import cn.hutool.core.map.MapUtil;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.hutool.extra.spring.SpringUtil;
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
import cn.iocoder.yudao.framework.common.core.KeyValue;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
|
|
@ -248,7 +247,9 @@ public class FlowableUtils {
|
||||||
Map<String, BpmFormFieldVO> formFieldsMap = new HashMap<>();
|
Map<String, BpmFormFieldVO> formFieldsMap = new HashMap<>();
|
||||||
processDefinitionInfo.getFormFields().forEach(formFieldStr -> {
|
processDefinitionInfo.getFormFields().forEach(formFieldStr -> {
|
||||||
BpmFormFieldVO formField = JsonUtils.parseObject(formFieldStr, BpmFormFieldVO.class);
|
BpmFormFieldVO formField = JsonUtils.parseObject(formFieldStr, BpmFormFieldVO.class);
|
||||||
parseFormField(formField, formFieldsMap);
|
if (formField != null) {
|
||||||
|
formFieldsMap.put(formField.getField(), formField);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 情况一:当自定义了摘要
|
// 情况一:当自定义了摘要
|
||||||
|
|
@ -272,26 +273,6 @@ public class FlowableUtils {
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 递归解析表单字段
|
|
||||||
*/
|
|
||||||
private static void parseFormField(BpmFormFieldVO formField, Map<String, BpmFormFieldVO> formFieldsMap) {
|
|
||||||
if (formField == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 如果存在 children -> 说明是布局组件
|
|
||||||
if (formField.getChildren() != null && !formField.getChildren().isEmpty()) {
|
|
||||||
for (BpmFormFieldVO child : formField.getChildren()) {
|
|
||||||
parseFormField(child, formFieldsMap);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// 真实字段才加入 map
|
|
||||||
if (StrUtil.isNotBlank(formField.getField())) {
|
|
||||||
formFieldsMap.put(formField.getField(), formField);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Task 相关的工具方法 ==========
|
// ========== Task 相关的工具方法 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -707,9 +707,10 @@ public class SimpleModelUtils {
|
||||||
List<String> list = convertList(item.getRules(), (rule) -> {
|
List<String> list = convertList(item.getRules(), (rule) -> {
|
||||||
String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
|
String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
|
||||||
: "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
|
: "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
|
||||||
return String.format(BpmConditionOpCodeEnum.fromCode(rule.getOpCode()).getSymbol(),
|
return String.format(" vars:getOrDefault(%s, null) %s var:convertByType(%s,%s) ",
|
||||||
rule.getLeftSide(), // 左侧:读取变量
|
rule.getLeftSide(), // 左侧:读取变量
|
||||||
rightSide); // 右侧:取值变量
|
rule.getOpCode(), // 中间:操作符,比较
|
||||||
|
rule.getLeftSide(), rightSide); // 右侧:转换变量,VariableConvertByTypeExpressionFunction
|
||||||
});
|
});
|
||||||
// 构造条件组的表达式
|
// 构造条件组的表达式
|
||||||
Boolean and = item.getAnd();
|
Boolean and = item.getAnd();
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package cn.iocoder.yudao.module.bpm.service.definition.dto;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BPM 流程 MetaInfo Response DTO
|
||||||
|
* 主要用于 { Model#setMetaInfo(String)} 的存储
|
||||||
|
*
|
||||||
|
* 最终,它的字段和 {@link cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO} 是一致的
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class BpmModelMetaInfoRespDTO {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流程图标
|
||||||
|
*/
|
||||||
|
private String icon;
|
||||||
|
/**
|
||||||
|
* 流程描述
|
||||||
|
*/
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表单类型
|
||||||
|
*/
|
||||||
|
private Integer formType;
|
||||||
|
/**
|
||||||
|
* 表单编号
|
||||||
|
* 在表单类型为 {@link BpmModelFormTypeEnum#NORMAL} 时
|
||||||
|
*/
|
||||||
|
private Long formId;
|
||||||
|
/**
|
||||||
|
* 自定义表单的提交路径,使用 Vue 的路由地址
|
||||||
|
* 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
|
||||||
|
*/
|
||||||
|
private String formCustomCreatePath;
|
||||||
|
/**
|
||||||
|
* 自定义表单的查看路径,使用 Vue 的路由地址
|
||||||
|
* 在表单类型为 {@link BpmModelFormTypeEnum#CUSTOM} 时
|
||||||
|
*/
|
||||||
|
private String formCustomViewPath;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -87,7 +87,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,12 +27,10 @@ public interface CrmCustomerLimitConfigMapper extends BaseMapperX<CrmCustomerLim
|
||||||
Integer type, Long userId, Long deptId) {
|
Integer type, Long userId, Long deptId) {
|
||||||
LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
|
LambdaQueryWrapperX<CrmCustomerLimitConfigDO> query = new LambdaQueryWrapperX<CrmCustomerLimitConfigDO>()
|
||||||
.eq(CrmCustomerLimitConfigDO::getType, type);
|
.eq(CrmCustomerLimitConfigDO::getType, type);
|
||||||
query.and(w -> {
|
query.apply("FIND_IN_SET({0}, user_ids) > 0", userId);
|
||||||
w.apply("FIND_IN_SET({0}, user_ids) > 0", userId);
|
if (deptId != null) {
|
||||||
if (deptId != null) {
|
query.apply("FIND_IN_SET({0}, dept_ids) > 0", deptId);
|
||||||
w.or().apply("FIND_IN_SET({0}, dept_ids) > 0", deptId);
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
return selectList(query);
|
return selectList(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -55,10 +55,4 @@ public class CodegenProperties {
|
||||||
@NotNull(message = "是否生成单元测试不能为空")
|
@NotNull(message = "是否生成单元测试不能为空")
|
||||||
private Boolean unitTestEnable;
|
private Boolean unitTestEnable;
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否生成 Excel 导入接口
|
|
||||||
*/
|
|
||||||
@NotNull(message = "是否生成 Excel 导入接口不能为空")
|
|
||||||
private Boolean importEnable;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,6 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||||
AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
|
AwsCredentialsProvider credentialsProvider = StaticCredentialsProvider.create(
|
||||||
AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
|
AwsBasicCredentials.create(config.getAccessKey(), config.getAccessSecret()));
|
||||||
URI endpoint = URI.create(buildEndpoint());
|
URI endpoint = URI.create(buildEndpoint());
|
||||||
URI presignerEndpoint = URI.create(buildPresignerEndpoint());
|
|
||||||
S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
|
S3Configuration serviceConfiguration = S3Configuration.builder() // Path-style 访问
|
||||||
.pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess()))
|
.pathStyleAccessEnabled(Boolean.TRUE.equals(config.getEnablePathStyleAccess()))
|
||||||
.chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57
|
.chunkedEncodingEnabled(false) // 禁用分块编码,参见 https://t.zsxq.com/kBy57
|
||||||
|
|
@ -67,7 +66,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||||
presigner = S3Presigner.builder()
|
presigner = S3Presigner.builder()
|
||||||
.credentialsProvider(credentialsProvider)
|
.credentialsProvider(credentialsProvider)
|
||||||
.region(region)
|
.region(region)
|
||||||
.endpointOverride(presignerEndpoint)
|
.endpointOverride(endpoint)
|
||||||
.serviceConfiguration(serviceConfiguration)
|
.serviceConfiguration(serviceConfiguration)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +116,7 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||||
public String presignGetUrl(String url, Integer expirationSeconds) {
|
public String presignGetUrl(String url, Integer expirationSeconds) {
|
||||||
// 1. 将 url 转换为 path
|
// 1. 将 url 转换为 path
|
||||||
String path = StrUtil.removePrefix(url, config.getDomain() + "/");
|
String path = StrUtil.removePrefix(url, config.getDomain() + "/");
|
||||||
path = HttpUtils.decodeUrlPath(HttpUtils.removeUrlQuery(path));
|
path = HttpUtils.decodeUtf8(HttpUtils.removeUrlQuery(path));
|
||||||
|
|
||||||
// 2.1 情况一:公开访问:无需签名
|
// 2.1 情况一:公开访问:无需签名
|
||||||
// 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名
|
// 考虑到老版本的兼容,所以必须是 config.getEnablePublicAccess() 为 false 时,才进行签名
|
||||||
|
|
@ -162,23 +161,6 @@ public class S3FileClient extends AbstractFileClient<S3FileClientConfig> {
|
||||||
return StrUtil.format("https://{}", config.getEndpoint());
|
return StrUtil.format("https://{}", config.getEndpoint());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* presigner 节点地址
|
|
||||||
*
|
|
||||||
* @return 节点地址
|
|
||||||
*/
|
|
||||||
private String buildPresignerEndpoint() {
|
|
||||||
// 补全 domain
|
|
||||||
if (StrUtil.isEmpty(config.getDomain())) {
|
|
||||||
config.setDomain(buildDomain());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Boolean.TRUE.equals(config.getEnablePathStyleAccess())) {
|
|
||||||
return StrUtil.removeSuffix(config.getDomain(), StrUtil.format("/{}", config.getBucket()));
|
|
||||||
}
|
|
||||||
return StrUtil.replace(config.getDomain(), StrUtil.format("://{}.", config.getBucket()), "://");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析 AWS 区域
|
* 解析 AWS 区域
|
||||||
* 优先级:配置的 region > 从 endpoint 解析的 region > 默认值 us-east-1
|
* 优先级:配置的 region > 从 endpoint 解析的 region > 默认值 us-east-1
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.security.config.Customizer;
|
import org.springframework.security.config.Customizer;
|
||||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
|
|
||||||
import org.springframework.security.core.userdetails.User;
|
import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
|
|
@ -41,9 +40,6 @@ public class AdminServerConfiguration {
|
||||||
@Value("${spring.boot.admin.client.password:admin}")
|
@Value("${spring.boot.admin.client.password:admin}")
|
||||||
private String password;
|
private String password;
|
||||||
|
|
||||||
@Value("${spring.boot.admin.frame-ancestors:'self'}")
|
|
||||||
private String frameAncestors;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring Boot Admin 专用的 InMemoryUserDetailsManager
|
* Spring Boot Admin 专用的 InMemoryUserDetailsManager
|
||||||
* 使用内存存储,与系统用户隔离
|
* 使用内存存储,与系统用户隔离
|
||||||
|
|
@ -104,16 +100,6 @@ public class AdminServerConfiguration {
|
||||||
adminSeverContextPath + "/instances", // Admin Client 注册端点忽略 CSRF
|
adminSeverContextPath + "/instances", // Admin Client 注册端点忽略 CSRF
|
||||||
adminSeverContextPath + "/actuator/**" // Actuator 端点忽略 CSRF
|
adminSeverContextPath + "/actuator/**" // Actuator 端点忽略 CSRF
|
||||||
)
|
)
|
||||||
)
|
|
||||||
.headers(headers -> headers
|
|
||||||
// 特殊:Spring Boot Admin 前端基于 Vue,需 unsafe-inline / unsafe-eval 支持内联脚本与表达式
|
|
||||||
.contentSecurityPolicy(csp -> csp.policyDirectives(
|
|
||||||
"default-src 'self'; "
|
|
||||||
+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
|
|
||||||
+ "style-src 'self' 'unsafe-inline'; "
|
|
||||||
+ "frame-ancestors " + frameAncestors))
|
|
||||||
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin) // 显式设置 X-Frame-Options 为 SAMEORIGIN
|
|
||||||
.cacheControl(HeadersConfigurer.CacheControlConfig::disable) // 禁用缓存,避免旧配置生效
|
|
||||||
);
|
);
|
||||||
return httpSecurity.build();
|
return httpSecurity.build();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import cn.iocoder.yudao.module.infra.enums.codegen.CodegenColumnListConditionEnu
|
||||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
||||||
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
import com.baomidou.mybatisplus.generator.config.po.TableField;
|
||||||
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
|
||||||
import com.google.common.annotations.VisibleForTesting;
|
|
||||||
import com.google.common.collect.Sets;
|
import com.google.common.collect.Sets;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
|
@ -118,8 +117,8 @@ public class CodegenBuilder {
|
||||||
table.setBusinessName(toCamelCase(subAfter(tableName, '_', false)).toLowerCase());
|
table.setBusinessName(toCamelCase(subAfter(tableName, '_', false)).toLowerCase());
|
||||||
// 驼峰 + 首字母大写;第一步,第一个 _ 前缀的后面,作为 class 名字;第二步,驼峰命名
|
// 驼峰 + 首字母大写;第一步,第一个 _ 前缀的后面,作为 class 名字;第二步,驼峰命名
|
||||||
table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false))));
|
table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false))));
|
||||||
// 去除结尾的表,作为类描述;注释中的英文引号替换为中文引号,避免破坏生成代码中的字符串字面量
|
// 去除结尾的表,作为类描述
|
||||||
table.setClassComment(StrUtil.removeSuffixIgnoreCase(sanitizeComment(table.getTableComment()), "表"));
|
table.setClassComment(StrUtil.removeSuffixIgnoreCase(table.getTableComment(), "表"));
|
||||||
table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
|
table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,7 +128,6 @@ public class CodegenBuilder {
|
||||||
for (CodegenColumnDO column : columns) {
|
for (CodegenColumnDO column : columns) {
|
||||||
column.setTableId(tableId);
|
column.setTableId(tableId);
|
||||||
column.setOrdinalPosition(index++);
|
column.setOrdinalPosition(index++);
|
||||||
column.setColumnComment(sanitizeComment(column.getColumnComment()));
|
|
||||||
// 特殊处理:Byte => Integer
|
// 特殊处理:Byte => Integer
|
||||||
if (Byte.class.getSimpleName().equals(column.getJavaType())) {
|
if (Byte.class.getSimpleName().equals(column.getJavaType())) {
|
||||||
column.setJavaType(Integer.class.getSimpleName());
|
column.setJavaType(Integer.class.getSimpleName());
|
||||||
|
|
@ -219,18 +217,4 @@ public class CodegenBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 将注释中的英文引号替换为中文引号,避免破坏生成代码中的字符串字面量
|
|
||||||
*
|
|
||||||
* @param comment 原始注释
|
|
||||||
* @return 清理后的注释
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
String sanitizeComment(String comment) {
|
|
||||||
if (StrUtil.isEmpty(comment)) {
|
|
||||||
return comment;
|
|
||||||
}
|
|
||||||
return comment.replace('"', '“').replace('\'', '‘');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,8 +72,6 @@ public class CodegenEngine {
|
||||||
.put(javaTemplatePath("controller/vo/listReqVO"), javaModuleImplVOFilePath("ListReqVO"))
|
.put(javaTemplatePath("controller/vo/listReqVO"), javaModuleImplVOFilePath("ListReqVO"))
|
||||||
.put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO"))
|
.put(javaTemplatePath("controller/vo/respVO"), javaModuleImplVOFilePath("RespVO"))
|
||||||
.put(javaTemplatePath("controller/vo/saveReqVO"), javaModuleImplVOFilePath("SaveReqVO"))
|
.put(javaTemplatePath("controller/vo/saveReqVO"), javaModuleImplVOFilePath("SaveReqVO"))
|
||||||
.put(javaTemplatePath("controller/vo/importExcelVO"), javaModuleImplVOFilePath("ImportExcelVO"))
|
|
||||||
.put(javaTemplatePath("controller/vo/importRespVO"), javaModuleImplVOFilePath("ImportRespVO"))
|
|
||||||
.put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
|
.put(javaTemplatePath("controller/controller"), javaModuleImplControllerFilePath())
|
||||||
.put(javaTemplatePath("dal/do"),
|
.put(javaTemplatePath("dal/do"),
|
||||||
javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
|
javaModuleImplMainFilePath("dal/dataobject/${table.businessName}/${table.className}DO"))
|
||||||
|
|
@ -128,8 +126,6 @@ public class CodegenEngine {
|
||||||
vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/form.vue"),
|
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/form.vue"),
|
||||||
vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
|
vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/import.vue"),
|
|
||||||
vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}ImportForm.vue"))
|
|
||||||
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/components/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/components/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
||||||
vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
|
vue3FilePath("views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/components/form_sub_inner.vue"), // 特殊:主子表专属逻辑
|
.put(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType(), vue3TemplatePath("views/components/form_sub_inner.vue"), // 特殊:主子表专属逻辑
|
||||||
|
|
@ -168,8 +164,6 @@ public class CodegenEngine {
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/form.vue"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/form.vue"),
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/import.vue"),
|
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/import-form.vue"))
|
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("api/api.ts"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("api/api.ts"),
|
||||||
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_SCHEMA.getType(), vue3Vben5AntdSchemaTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
||||||
|
|
@ -187,8 +181,6 @@ public class CodegenEngine {
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/form.vue"),
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/import.vue"),
|
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/import-form.vue"))
|
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("api/api.ts"),
|
||||||
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType(), vue3Vben5AntdGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
||||||
|
|
@ -208,8 +200,6 @@ public class CodegenEngine {
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/form.vue"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/form.vue"),
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/import.vue"),
|
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/import-form.vue"))
|
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("api/api.ts"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("api/api.ts"),
|
||||||
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_SCHEMA.getType(), vue3Vben5EpSchemaTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
||||||
|
|
@ -227,8 +217,6 @@ public class CodegenEngine {
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/form.vue"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/form.vue"),
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/import.vue"),
|
|
||||||
vue3VbenFilePath("views/${table.moduleName}/${table.businessName}/modules/import-form.vue"))
|
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("api/api.ts"),
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("api/api.ts"),
|
||||||
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
vue3VbenFilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
|
||||||
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
.put(CodegenFrontTypeEnum.VUE3_VBEN5_EP_GENERAL.getType(), vue3Vben5EpGeneralTemplatePath("views/modules/form_sub_normal.vue"), // 特殊:主子表专属逻辑
|
||||||
|
|
@ -296,7 +284,6 @@ public class CodegenEngine {
|
||||||
globalBindingMap.put("jakartaPackage", jakartaEnable ? "jakarta" : "javax");
|
globalBindingMap.put("jakartaPackage", jakartaEnable ? "jakarta" : "javax");
|
||||||
globalBindingMap.put("voType", codegenProperties.getVoType());
|
globalBindingMap.put("voType", codegenProperties.getVoType());
|
||||||
globalBindingMap.put("deleteBatchEnable", codegenProperties.getDeleteBatchEnable());
|
globalBindingMap.put("deleteBatchEnable", codegenProperties.getDeleteBatchEnable());
|
||||||
globalBindingMap.put("importEnable", codegenProperties.getImportEnable());
|
|
||||||
// 全局 Java Bean
|
// 全局 Java Bean
|
||||||
globalBindingMap.put("CommonResultClassName", CommonResult.class.getName());
|
globalBindingMap.put("CommonResultClassName", CommonResult.class.getName());
|
||||||
globalBindingMap.put("PageResultClassName", PageResult.class.getName());
|
globalBindingMap.put("PageResultClassName", PageResult.class.getName());
|
||||||
|
|
@ -356,11 +343,6 @@ public class CodegenEngine {
|
||||||
if (!CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
|
if (!CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (isImportTemplate(vmPath)) {
|
|
||||||
// 关闭 import 时,跳过 ImportExcelVO / ImportRespVO 的生成
|
|
||||||
if (!Boolean.TRUE.equals(codegenProperties.getImportEnable())) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 2.3 默认生成
|
// 2.3 默认生成
|
||||||
generateCode(result, vmPath, filePath, bindingMap);
|
generateCode(result, vmPath, filePath, bindingMap);
|
||||||
|
|
@ -694,9 +676,4 @@ public class CodegenEngine {
|
||||||
return path.contains("listReqVO");
|
return path.contains("listReqVO");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isImportTemplate(String path) {
|
|
||||||
return path.contains("importExcelVO") || path.contains("importRespVO")
|
|
||||||
|| path.contains("views/import.vue");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.infra.service.file;
|
||||||
|
|
||||||
import cn.hutool.core.io.resource.ResourceUtil;
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
|
||||||
|
|
@ -94,7 +93,7 @@ public class FileConfigServiceImpl implements FileConfigService {
|
||||||
fileConfigMapper.updateById(updateObj);
|
fileConfigMapper.updateById(updateObj);
|
||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
clearCache(config.getId(), config.getMaster());
|
clearCache(config.getId(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -133,7 +132,7 @@ public class FileConfigServiceImpl implements FileConfigService {
|
||||||
fileConfigMapper.deleteById(id);
|
fileConfigMapper.deleteById(id);
|
||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
clearCache(id, config.getMaster());
|
clearCache(id, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -150,7 +149,7 @@ public class FileConfigServiceImpl implements FileConfigService {
|
||||||
fileConfigMapper.deleteByIds(ids);
|
fileConfigMapper.deleteByIds(ids);
|
||||||
|
|
||||||
// 清空缓存
|
// 清空缓存
|
||||||
ids.forEach(id -> clearCache(id, false));
|
ids.forEach(id -> clearCache(id, null));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -192,7 +191,7 @@ public class FileConfigServiceImpl implements FileConfigService {
|
||||||
validateFileConfigExists(id);
|
validateFileConfigExists(id);
|
||||||
// 上传文件
|
// 上传文件
|
||||||
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
byte[] content = ResourceUtil.readBytes("file/erweima.jpg");
|
||||||
return getFileClient(id).upload(content, "public" + StrUtil.SLASH + IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
|
return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + ".jpg", "image/jpeg");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.infra.service.file;
|
||||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
import cn.hutool.core.lang.Assert;
|
import cn.hutool.core.lang.Assert;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.hutool.crypto.digest.DigestUtil;
|
import cn.hutool.crypto.digest.DigestUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
|
@ -42,19 +41,12 @@ public class FileServiceImpl implements FileService {
|
||||||
*/
|
*/
|
||||||
static boolean PATH_PREFIX_DATE_ENABLE = true;
|
static boolean PATH_PREFIX_DATE_ENABLE = true;
|
||||||
/**
|
/**
|
||||||
* 上传文件的后缀,是否启用
|
* 上传文件的后缀,是否包含时间戳
|
||||||
*
|
*
|
||||||
* 算法:当前时间戳(毫秒)+ 5 位随机数;目的是保证文件的唯一性,避免覆盖
|
* 目的:保证文件的唯一性,避免覆盖
|
||||||
* 定制:可按需调整成 UUID、或者其他方式
|
* 定制:可按需调整成 UUID、或者其他方式
|
||||||
*/
|
*/
|
||||||
static boolean PATH_SUFFIX_TIMESTAMP_ENABLE = false;
|
static boolean PATH_SUFFIX_TIMESTAMP_ENABLE = true;
|
||||||
/**
|
|
||||||
* 后缀是否作为上级目录
|
|
||||||
*
|
|
||||||
* true:{@code yyyyMMdd/<后缀>/原文件名.ext};保留原文件名
|
|
||||||
* false:{@code yyyyMMdd/原文件名_<后缀>.ext};后缀拼到文件名
|
|
||||||
*/
|
|
||||||
static boolean PATH_SUFFIX_AS_DIRECTORY = true;
|
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private FileConfigService fileConfigService;
|
private FileConfigService fileConfigService;
|
||||||
|
|
@ -109,21 +101,16 @@ public class FileServiceImpl implements FileService {
|
||||||
}
|
}
|
||||||
String suffix = null;
|
String suffix = null;
|
||||||
if (PATH_SUFFIX_TIMESTAMP_ENABLE) {
|
if (PATH_SUFFIX_TIMESTAMP_ENABLE) {
|
||||||
// 5 位随机数,避免同一毫秒内的重复
|
suffix = String.valueOf(System.currentTimeMillis());
|
||||||
suffix = String.valueOf(System.currentTimeMillis()) + RandomUtil.randomInt(10000, 100000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.1 先拼接 suffix 后缀
|
// 2.1 先拼接 suffix 后缀
|
||||||
if (StrUtil.isNotEmpty(suffix)) {
|
if (StrUtil.isNotEmpty(suffix)) {
|
||||||
if (PATH_SUFFIX_AS_DIRECTORY) {
|
String ext = FileUtil.extName(name);
|
||||||
name = suffix + StrUtil.SLASH + name;
|
if (StrUtil.isNotEmpty(ext)) {
|
||||||
|
name = FileUtil.mainName(name) + StrUtil.C_UNDERLINE + suffix + StrUtil.DOT + ext;
|
||||||
} else {
|
} else {
|
||||||
String ext = FileUtil.extName(name);
|
name = name + StrUtil.C_UNDERLINE + suffix;
|
||||||
if (StrUtil.isNotEmpty(ext)) {
|
|
||||||
name = FileUtil.mainName(name) + StrUtil.C_UNDERLINE + suffix + StrUtil.DOT + ext;
|
|
||||||
} else {
|
|
||||||
name = name + StrUtil.C_UNDERLINE + suffix;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 2.2 再拼接 prefix 前缀
|
// 2.2 再拼接 prefix 前缀
|
||||||
|
|
|
||||||
|
|
@ -102,7 +102,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ xxl:
|
||||||
# Lock4j 配置项
|
# Lock4j 配置项
|
||||||
lock4j:
|
lock4j:
|
||||||
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
acquire-timeout: 3000 # 获取分布式锁超时时间,默认为 3000 毫秒
|
||||||
expire: 30000 # 分布式锁的超时时间,默认为 30000 毫秒
|
expire: 30000 # 分布式锁的超时时间,默认为 30 毫秒
|
||||||
|
|
||||||
--- #################### 监控相关配置 ####################
|
--- #################### 监控相关配置 ####################
|
||||||
|
|
||||||
|
|
@ -137,8 +137,6 @@ spring:
|
||||||
password: admin
|
password: admin
|
||||||
# Spring Boot Admin Server 服务端的相关配置
|
# Spring Boot Admin Server 服务端的相关配置
|
||||||
context-path: /admin # 配置 Spring
|
context-path: /admin # 配置 Spring
|
||||||
# 允许嵌入 iframe 的域名(支持通配符),实际部署时,可以改为 "'self' [你的公网域名]"
|
|
||||||
frame-ancestors: "'self' localhost localhost:48082 127.0.0.1 127.0.0.1:48082"
|
|
||||||
|
|
||||||
# 日志文件配置
|
# 日志文件配置
|
||||||
logging:
|
logging:
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,6 @@ yudao:
|
||||||
vo-type: 10 # VO 的类型,参见 CodegenVOTypeEnum 枚举类
|
vo-type: 10 # VO 的类型,参见 CodegenVOTypeEnum 枚举类
|
||||||
delete-batch-enable: true # 是否生成批量删除接口
|
delete-batch-enable: true # 是否生成批量删除接口
|
||||||
unit-test-enable: false # 是否生成单元测试
|
unit-test-enable: false # 是否生成单元测试
|
||||||
import-enable: false # 是否生成 Excel 导入接口
|
|
||||||
tenant: # 多租户相关配置项
|
tenant: # 多租户相关配置项
|
||||||
enable: true
|
enable: true
|
||||||
ignore-urls:
|
ignore-urls:
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName};
|
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName};
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
#if ($importEnable)
|
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
|
||||||
#end
|
|
||||||
import ${jakartaPackage}.annotation.Resource;
|
import ${jakartaPackage}.annotation.Resource;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end
|
#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end
|
||||||
|
|
@ -162,29 +159,6 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
|
||||||
BeanUtils.toBean(list, ${table.className}RespVO.class));
|
BeanUtils.toBean(list, ${table.className}RespVO.class));
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
@GetMapping("/get-import-template")
|
|
||||||
@Operation(summary = "获得导入${table.classComment}模板")
|
|
||||||
#if ($sceneEnum.scene == 1)
|
|
||||||
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:import')")
|
|
||||||
#end
|
|
||||||
public void importTemplate(HttpServletResponse response) throws IOException {
|
|
||||||
ExcelUtils.write(response, "${table.classComment}导入模板.xls", "数据",
|
|
||||||
${sceneEnum.prefixClass}${table.className}ImportExcelVO.class, Collections.emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/import")
|
|
||||||
@Operation(summary = "导入${table.classComment}")
|
|
||||||
@Parameter(name = "file", description = "Excel 文件", required = true)
|
|
||||||
#if ($sceneEnum.scene == 1)
|
|
||||||
@PreAuthorize("@ss.hasPermission('${permissionPrefix}:import')")
|
|
||||||
#end
|
|
||||||
public CommonResult<${sceneEnum.prefixClass}${table.className}ImportRespVO> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
|
|
||||||
List<${sceneEnum.prefixClass}${table.className}ImportExcelVO> list = ExcelUtils.read(file, ${sceneEnum.prefixClass}${table.className}ImportExcelVO.class);
|
|
||||||
return success(${classNameVar}Service.import${simpleClassName}List(list));
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#foreach ($subTable in $subTables)
|
#foreach ($subTable in $subTables)
|
||||||
|
|
@ -294,4 +268,4 @@ public class ${sceneEnum.prefixClass}${table.className}Controller {
|
||||||
|
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
|
|
||||||
|
|
||||||
import cn.idev.excel.annotation.ExcelProperty;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.NoArgsConstructor;
|
|
||||||
#foreach ($column in $columns)
|
|
||||||
#if (${column.createOperation} && "$!column.dictType" != "")
|
|
||||||
import ${DictFormatClassName};
|
|
||||||
import ${DictConvertClassName};
|
|
||||||
#break
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ${table.classComment} Excel 导入 VO
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
@AllArgsConstructor
|
|
||||||
@NoArgsConstructor
|
|
||||||
public class ${sceneEnum.prefixClass}${table.className}ImportExcelVO {
|
|
||||||
|
|
||||||
## 逐个处理字段
|
|
||||||
#foreach ($column in $columns)
|
|
||||||
#if (${column.createOperation})
|
|
||||||
#if ("$!column.dictType" != "")
|
|
||||||
@ExcelProperty(value = "${column.columnComment}", converter = DictConvert.class)
|
|
||||||
@DictFormat("${column.dictType}") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
|
|
||||||
#else
|
|
||||||
@ExcelProperty("${column.columnComment}")
|
|
||||||
#end
|
|
||||||
private ${column.javaType} ${column.javaField};
|
|
||||||
|
|
||||||
#end
|
|
||||||
#end
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;
|
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
|
||||||
import lombok.Builder;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
@Schema(description = "${sceneEnum.name} - ${table.classComment}导入 Response VO")
|
|
||||||
@Data
|
|
||||||
@Builder
|
|
||||||
public class ${sceneEnum.prefixClass}${table.className}ImportRespVO {
|
|
||||||
|
|
||||||
@Schema(description = "创建成功的数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
|
|
||||||
private Integer successCount;
|
|
||||||
|
|
||||||
@Schema(description = "导入失败的数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
|
||||||
private Integer failureCount;
|
|
||||||
|
|
||||||
@Schema(description = "导入失败的数据集合,key 为行号,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
|
|
||||||
private Map<Integer, String> failureRows;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -25,16 +25,6 @@ public interface ${table.className}Service {
|
||||||
* @return 编号
|
* @return 编号
|
||||||
*/
|
*/
|
||||||
${primaryColumn.javaType} create${simpleClassName}(@Valid ${saveReqVOClass} ${saveReqVOVar});
|
${primaryColumn.javaType} create${simpleClassName}(@Valid ${saveReqVOClass} ${saveReqVOVar});
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导入${table.classComment}
|
|
||||||
*
|
|
||||||
* @param importList 导入信息
|
|
||||||
* @return 导入结果
|
|
||||||
*/
|
|
||||||
${sceneEnum.prefixClass}${table.className}ImportRespVO import${simpleClassName}List(List<${sceneEnum.prefixClass}${table.className}ImportExcelVO> importList);
|
|
||||||
#end
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新${table.classComment}
|
* 更新${table.classComment}
|
||||||
|
|
@ -172,4 +162,4 @@ public interface ${table.className}Service {
|
||||||
|
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
@ -7,9 +7,6 @@ import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
#if ($importEnable)
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
#end
|
|
||||||
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
|
import ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;
|
||||||
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
|
import ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
|
|
@ -94,32 +91,6 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
|
||||||
// 返回
|
// 返回
|
||||||
return ${classNameVar}.getId();
|
return ${classNameVar}.getId();
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
|
|
||||||
public ${sceneEnum.prefixClass}${table.className}ImportRespVO import${simpleClassName}List(List<${sceneEnum.prefixClass}${table.className}ImportExcelVO> importList) {
|
|
||||||
if (CollUtil.isEmpty(importList)) {
|
|
||||||
return ${sceneEnum.prefixClass}${table.className}ImportRespVO.builder()
|
|
||||||
.successCount(0).failureCount(0).failureRows(new LinkedHashMap<>()).build();
|
|
||||||
}
|
|
||||||
// 遍历,逐个创建
|
|
||||||
${sceneEnum.prefixClass}${table.className}ImportRespVO respVO = ${sceneEnum.prefixClass}${table.className}ImportRespVO.builder()
|
|
||||||
.successCount(0).failureCount(0).failureRows(new LinkedHashMap<>()).build();
|
|
||||||
AtomicInteger index = new AtomicInteger(1);
|
|
||||||
importList.forEach(importItem -> {
|
|
||||||
int currentIndex = index.getAndIncrement();
|
|
||||||
try {
|
|
||||||
create${simpleClassName}(BeanUtils.toBean(importItem, ${saveReqVOClass}.class));
|
|
||||||
respVO.setSuccessCount(respVO.getSuccessCount() + 1);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
respVO.getFailureRows().put(currentIndex, ex.getMessage());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
respVO.setFailureCount(respVO.getFailureRows().size());
|
|
||||||
return respVO;
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
## 特殊:主子表专属逻辑(非 ERP 模式)
|
## 特殊:主子表专属逻辑(非 ERP 模式)
|
||||||
|
|
@ -388,9 +359,6 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
|
||||||
#else
|
#else
|
||||||
#if ( $subTable.subJoinMany)
|
#if ( $subTable.subJoinMany)
|
||||||
private void create${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) {
|
private void create${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) {
|
||||||
if (CollUtil.isEmpty(list)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
list.forEach(o -> o.set${SubJoinColumnName}(${subJoinColumn.javaField}).clean());
|
list.forEach(o -> o.set${SubJoinColumnName}(${subJoinColumn.javaField}).clean());
|
||||||
${subClassNameVars.get($index)}Mapper.insertBatch(list);
|
${subClassNameVars.get($index)}Mapper.insertBatch(list);
|
||||||
}
|
}
|
||||||
|
|
@ -448,4 +416,4 @@ public class ${table.className}ServiceImpl implements ${table.className}Service
|
||||||
#end
|
#end
|
||||||
|
|
||||||
#end
|
#end
|
||||||
}
|
}
|
||||||
|
|
@ -1,11 +1,6 @@
|
||||||
## 通用变量定义
|
## 通用变量定义
|
||||||
#if ($importEnable)
|
|
||||||
#set ($functionNames = ['查询', '创建', '更新', '删除', '导出', '导入'])
|
|
||||||
#set ($functionOps = ['query', 'create', 'update', 'delete', 'export', 'import'])
|
|
||||||
#else
|
|
||||||
#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
|
#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])
|
||||||
#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
|
#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])
|
||||||
#end
|
|
||||||
##
|
##
|
||||||
## 宏定义:生成按钮 SQL(通用部分)
|
## 宏定义:生成按钮 SQL(通用部分)
|
||||||
#macro(insertButtonSql $parentIdVar)
|
#macro(insertButtonSql $parentIdVar)
|
||||||
|
|
|
||||||
|
|
@ -73,26 +73,6 @@ export function export${simpleClassName}Excel(params) {
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
// 下载${table.classComment}导入模板
|
|
||||||
export function import${simpleClassName}Template() {
|
|
||||||
return request({
|
|
||||||
url: '${baseURL}/get-import-template',
|
|
||||||
method: 'get',
|
|
||||||
responseType: 'blob'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导入${table.classComment}
|
|
||||||
export function import${simpleClassName}(data) {
|
|
||||||
return request({
|
|
||||||
url: '${baseURL}/import',
|
|
||||||
method: 'post',
|
|
||||||
data
|
|
||||||
})
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#foreach ($subTable in $subTables)
|
#foreach ($subTable in $subTables)
|
||||||
#set ($index = $foreach.count - 1)
|
#set ($index = $foreach.count - 1)
|
||||||
|
|
@ -177,4 +157,4 @@ export function import${simpleClassName}(data) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#end
|
#end
|
||||||
#end
|
#end
|
||||||
|
|
@ -49,16 +49,6 @@
|
||||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
|
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="openForm(undefined)"
|
||||||
v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
|
v-hasPermi="['${permissionPrefix}:create']">新增</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
#if ($importEnable)
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleImport"
|
|
||||||
:loading="importLoading" v-hasPermi="['${permissionPrefix}:import']">导入</el-button>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="1.5">
|
|
||||||
<el-button type="info" plain icon="el-icon-document" size="mini" @click="handleImportTemplate"
|
|
||||||
v-hasPermi="['${permissionPrefix}:import']">导入模板</el-button>
|
|
||||||
</el-col>
|
|
||||||
#end
|
|
||||||
<el-col :span="1.5">
|
<el-col :span="1.5">
|
||||||
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
|
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" :loading="exportLoading"
|
||||||
v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
|
v-hasPermi="['${permissionPrefix}:export']">导出</el-button>
|
||||||
|
|
@ -88,9 +78,6 @@
|
||||||
#end
|
#end
|
||||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
#if ($importEnable)
|
|
||||||
<input ref="importFileRef" type="file" style="display: none" accept=".xls,.xlsx" @change="handleImportFileChange" />
|
|
||||||
#end
|
|
||||||
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
|
#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
|
||||||
|
|
@ -257,10 +244,6 @@ export default {
|
||||||
loading: true,
|
loading: true,
|
||||||
// 导出遮罩层
|
// 导出遮罩层
|
||||||
exportLoading: false,
|
exportLoading: false,
|
||||||
#if ($importEnable)
|
|
||||||
// 导入遮罩层
|
|
||||||
importLoading: false,
|
|
||||||
#end
|
|
||||||
// 显示搜索条件
|
// 显示搜索条件
|
||||||
showSearch: true,
|
showSearch: true,
|
||||||
## 特殊:树表专属逻辑(树不需要分页接口)
|
## 特殊:树表专属逻辑(树不需要分页接口)
|
||||||
|
|
@ -339,44 +322,6 @@ export default {
|
||||||
openForm(id) {
|
openForm(id) {
|
||||||
this.#[[$]]#refs["formRef"].open(id);
|
this.#[[$]]#refs["formRef"].open(id);
|
||||||
},
|
},
|
||||||
#if ($importEnable)
|
|
||||||
/** 导入按钮操作 */
|
|
||||||
handleImport() {
|
|
||||||
this.$refs.importFileRef && this.$refs.importFileRef.click();
|
|
||||||
},
|
|
||||||
/** 导入模板下载 */
|
|
||||||
async handleImportTemplate() {
|
|
||||||
const data = await ${simpleClassName}Api.import${simpleClassName}Template();
|
|
||||||
this.#[[$]]#download.excel(data, '${table.classComment}导入模板.xls');
|
|
||||||
},
|
|
||||||
/** 导入文件变更 */
|
|
||||||
async handleImportFileChange(event) {
|
|
||||||
const target = event.target;
|
|
||||||
const file = target.files && target.files[0];
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.importLoading = true;
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
const res = await ${simpleClassName}Api.import${simpleClassName}(formData);
|
|
||||||
const data = res.data || res;
|
|
||||||
let text = '导入成功数量:' + (data.successCount || 0) + ';导入失败数量:' + (data.failureCount || 0) + ';';
|
|
||||||
if (data.failureRows) {
|
|
||||||
Object.keys(data.failureRows).forEach((rowNo) => {
|
|
||||||
text += '< 第' + rowNo + '行: ' + data.failureRows[rowNo] + ' >';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
await this.$alert(text, '${table.classComment}导入结果', { dangerouslyUseHTMLString: true });
|
|
||||||
await this.getList();
|
|
||||||
} catch {
|
|
||||||
} finally {
|
|
||||||
target.value = '';
|
|
||||||
this.importLoading = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
#end
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
async handleDelete(row) {
|
async handleDelete(row) {
|
||||||
const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
|
const ${primaryColumn.javaField} = row.${primaryColumn.javaField};
|
||||||
|
|
|
||||||
|
|
@ -98,18 +98,6 @@ export const ${simpleClassName}Api = {
|
||||||
export${simpleClassName}: async (params) => {
|
export${simpleClassName}: async (params) => {
|
||||||
return await request.download({ url: `${baseURL}/export-excel`, params })
|
return await request.download({ url: `${baseURL}/export-excel`, params })
|
||||||
},
|
},
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
// 下载${table.classComment}导入模板
|
|
||||||
import${simpleClassName}Template: async () => {
|
|
||||||
return await request.download({ url: `${baseURL}/get-import-template` })
|
|
||||||
},
|
|
||||||
|
|
||||||
// 导入${table.classComment}
|
|
||||||
import${simpleClassName}: async (data: FormData) => {
|
|
||||||
return await request.upload({ url: `${baseURL}/import`, data })
|
|
||||||
},
|
|
||||||
#end
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#foreach ($subTable in $subTables)
|
#foreach ($subTable in $subTables)
|
||||||
#set ($index = $foreach.count - 1)
|
#set ($index = $foreach.count - 1)
|
||||||
|
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
<template>
|
|
||||||
<Dialog v-model="dialogVisible" title="${table.classComment}导入" width="400">
|
|
||||||
<el-upload
|
|
||||||
ref="uploadRef"
|
|
||||||
v-model:file-list="fileList"
|
|
||||||
:auto-upload="false"
|
|
||||||
:disabled="formLoading"
|
|
||||||
:limit="1"
|
|
||||||
:on-exceed="handleExceed"
|
|
||||||
accept=".xlsx, .xls"
|
|
||||||
action="none"
|
|
||||||
drag
|
|
||||||
>
|
|
||||||
<Icon icon="ep:upload" />
|
|
||||||
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
|
|
||||||
<template #tip>
|
|
||||||
<div class="el-upload__tip text-center">
|
|
||||||
<span>仅允许导入 xls、xlsx 格式文件。</span>
|
|
||||||
<el-link
|
|
||||||
:underline="false"
|
|
||||||
style="font-size: 12px; vertical-align: baseline"
|
|
||||||
type="primary"
|
|
||||||
@click="handleDownloadTemplate"
|
|
||||||
>
|
|
||||||
下载模板
|
|
||||||
</el-link>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-upload>
|
|
||||||
<template #footer>
|
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { UploadUserFile } from 'element-plus'
|
|
||||||
import download from '@/utils/download'
|
|
||||||
import { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'
|
|
||||||
|
|
||||||
/** ${table.classComment} 导入 */
|
|
||||||
defineOptions({ name: '${simpleClassName}ImportForm' })
|
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
|
||||||
const formLoading = ref(false) // 表单的加载中:上传、下载模板
|
|
||||||
const uploadRef = ref()
|
|
||||||
const fileList = ref<UploadUserFile[]>([])
|
|
||||||
|
|
||||||
/** 打开弹窗 */
|
|
||||||
const open = async () => {
|
|
||||||
dialogVisible.value = true
|
|
||||||
await resetForm()
|
|
||||||
}
|
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
|
||||||
|
|
||||||
/** 提交导入 */
|
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
|
||||||
const submitForm = async () => {
|
|
||||||
if (fileList.value.length === 0) {
|
|
||||||
message.error('请上传文件')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
formLoading.value = true
|
|
||||||
try {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', fileList.value[0].raw as Blob)
|
|
||||||
const res = await ${simpleClassName}Api.import${simpleClassName}(formData)
|
|
||||||
const data = res.data
|
|
||||||
let text = '导入成功数量:' + data.successCount + ';导入失败数量:' + data.failureCount + ';'
|
|
||||||
if (data.failureCount > 0) {
|
|
||||||
for (const rowNo in data.failureRows) {
|
|
||||||
text += '< 第' + rowNo + '行: ' + data.failureRows[rowNo] + ' >'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
message.alert(text)
|
|
||||||
dialogVisible.value = false
|
|
||||||
emit('success')
|
|
||||||
} finally {
|
|
||||||
formLoading.value = false
|
|
||||||
await resetForm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 下载导入模板 */
|
|
||||||
const handleDownloadTemplate = async () => {
|
|
||||||
const data = await ${simpleClassName}Api.import${simpleClassName}Template()
|
|
||||||
download.excel(data, '${table.classComment}导入模板.xls')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 文件超限 */
|
|
||||||
const handleExceed = (): void => {
|
|
||||||
message.error('最多只能上传一个文件!')
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 重置表单 */
|
|
||||||
const resetForm = async () => {
|
|
||||||
fileList.value = []
|
|
||||||
await nextTick()
|
|
||||||
uploadRef.value?.clearFiles()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
@ -92,16 +92,6 @@
|
||||||
>
|
>
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
</el-button>
|
</el-button>
|
||||||
#if ($importEnable)
|
|
||||||
<el-button
|
|
||||||
type="warning"
|
|
||||||
plain
|
|
||||||
@click="handleImport"
|
|
||||||
v-hasPermi="['${permissionPrefix}:import']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:upload" class="mr-5px" /> 导入
|
|
||||||
</el-button>
|
|
||||||
#end
|
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
plain
|
plain
|
||||||
|
|
@ -247,11 +237,6 @@
|
||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
<!-- 导入弹窗 -->
|
|
||||||
<${simpleClassName}ImportForm ref="importRef" @success="getList" />
|
|
||||||
#end
|
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<${simpleClassName}Form ref="formRef" @success="getList" />
|
<${simpleClassName}Form ref="formRef" @success="getList" />
|
||||||
|
|
@ -278,9 +263,6 @@
|
||||||
import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { isEmpty } from '@/utils/is'
|
import { isEmpty } from '@/utils/is'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
#if ($importEnable)
|
|
||||||
import ${simpleClassName}ImportForm from './${simpleClassName}ImportForm.vue'
|
|
||||||
#end
|
|
||||||
## 特殊:树表专属逻辑
|
## 特殊:树表专属逻辑
|
||||||
#if ( $table.templateType == 2 )
|
#if ( $table.templateType == 2 )
|
||||||
import { handleTree } from '@/utils/tree'
|
import { handleTree } from '@/utils/tree'
|
||||||
|
|
@ -326,9 +308,6 @@ const queryParams = reactive({
|
||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
#if ($importEnable)
|
|
||||||
const importRef = ref() // ${table.classComment} 导入组件的 Ref
|
|
||||||
#end
|
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
|
|
@ -365,13 +344,6 @@ const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
/** 导入按钮操作 */
|
|
||||||
const handleImport = () => {
|
|
||||||
importRef.value.open()
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
|
|
@ -449,4 +421,4 @@ const toggleExpandAll = async () => {
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
@ -30,15 +30,3 @@ export function delete${simpleClassName}(id: number) {
|
||||||
export function export${simpleClassName}(params) {
|
export function export${simpleClassName}(params) {
|
||||||
return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')
|
return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
// 下载${table.classComment}导入模板
|
|
||||||
export function import${simpleClassName}Template() {
|
|
||||||
return defHttp.download({ url: '${baseURL}/get-import-template' }, '${table.classComment}导入模板.xls')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导入${table.classComment}
|
|
||||||
export function import${simpleClassName}(data: FormData) {
|
|
||||||
return defHttp.post({ url: '${baseURL}/import', data })
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,18 @@
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
|
import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
|
||||||
import { columns, searchFormSchema } from './${classNameVar}.data'
|
import { columns, searchFormSchema } from './${classNameVar}.data'
|
||||||
import { ref } from 'vue'
|
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { useModal } from '@/components/Modal'
|
import { useModal } from '@/components/Modal'
|
||||||
import { IconEnum } from '@/enums/appEnum'
|
import { IconEnum } from '@/enums/appEnum'
|
||||||
import { BasicTable, TableAction, useTable } from '@/components/Table'
|
import { BasicTable, TableAction, useTable } from '@/components/Table'
|
||||||
import {
|
import { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${table.businessName}'
|
||||||
delete${simpleClassName},
|
|
||||||
export${simpleClassName},
|
|
||||||
get${simpleClassName}Page,
|
|
||||||
#if ($importEnable)
|
|
||||||
import${simpleClassName},
|
|
||||||
import${simpleClassName}Template,
|
|
||||||
#end
|
|
||||||
} from '@/api/${table.moduleName}/${table.businessName}'
|
|
||||||
|
|
||||||
defineOptions({ name: '${table.className}' })
|
defineOptions({ name: '${table.className}' })
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const { createConfirm, createMessage } = useMessage()
|
const { createConfirm, createMessage } = useMessage()
|
||||||
const [registerModal, { openModal }] = useModal()
|
const [registerModal, { openModal }] = useModal()
|
||||||
#if ($importEnable)
|
|
||||||
const importFileRef = ref<HTMLInputElement>()
|
|
||||||
const importLoading = ref(false)
|
|
||||||
#end
|
|
||||||
|
|
||||||
const [registerTable, { getForm, reload }] = useTable({
|
const [registerTable, { getForm, reload }] = useTable({
|
||||||
title: '${table.classComment}列表',
|
title: '${table.classComment}列表',
|
||||||
|
|
@ -61,44 +48,6 @@ async function handleExport() {
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
function handleImport() {
|
|
||||||
importFileRef.value?.click()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleImportTemplateDownload() {
|
|
||||||
await import${simpleClassName}Template()
|
|
||||||
createMessage.success('模板下载已开始')
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleImportFileChange(event: Event) {
|
|
||||||
const target = event.target as HTMLInputElement
|
|
||||||
const file = target.files?.[0]
|
|
||||||
if (!file) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
importLoading.value = true
|
|
||||||
try {
|
|
||||||
const formData = new FormData()
|
|
||||||
formData.append('file', file)
|
|
||||||
const response: any = await import${simpleClassName}(formData)
|
|
||||||
const data = response?.data ?? response
|
|
||||||
let text =
|
|
||||||
'导入成功数量:' + (data?.successCount || 0) + ';导入失败数量:' + (data?.failureCount || 0) + ';'
|
|
||||||
if (data?.failureRows) {
|
|
||||||
Object.keys(data.failureRows).forEach((rowNo) => {
|
|
||||||
text += '< 第' + rowNo + '行: ' + data.failureRows[rowNo] + ' >'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
createMessage.success(text)
|
|
||||||
await reload()
|
|
||||||
} finally {
|
|
||||||
target.value = ''
|
|
||||||
importLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
async function handleDelete(record: Recordable) {
|
async function handleDelete(record: Recordable) {
|
||||||
await delete${simpleClassName}(record.id)
|
await delete${simpleClassName}(record.id)
|
||||||
|
|
@ -113,14 +62,6 @@ async function handleDelete(record: Recordable) {
|
||||||
<a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
|
<a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
|
||||||
{{ t('action.create') }}
|
{{ t('action.create') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
#if ($importEnable)
|
|
||||||
<a-button v-auth="['${permissionPrefix}:import']" :loading="importLoading" @click="handleImport">
|
|
||||||
导入
|
|
||||||
</a-button>
|
|
||||||
<a-button v-auth="['${permissionPrefix}:import']" @click="handleImportTemplateDownload">
|
|
||||||
导入模板
|
|
||||||
</a-button>
|
|
||||||
#end
|
|
||||||
<a-button v-auth="['${permissionPrefix}:export']" :preIcon="IconEnum.EXPORT" @click="handleExport">
|
<a-button v-auth="['${permissionPrefix}:export']" :preIcon="IconEnum.EXPORT" @click="handleExport">
|
||||||
{{ t('action.export') }}
|
{{ t('action.export') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
@ -146,9 +87,6 @@ async function handleDelete(record: Recordable) {
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</BasicTable>
|
</BasicTable>
|
||||||
#if ($importEnable)
|
|
||||||
<input ref="importFileRef" type="file" accept=".xls,.xlsx" class="hidden" @change="handleImportFileChange" />
|
|
||||||
#end
|
|
||||||
<${simpleClassName}Modal @register="registerModal" @success="reload()" />
|
<${simpleClassName}Modal @register="registerModal" @success="reload()" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
||||||
|
|
@ -100,18 +100,6 @@ export function delete${simpleClassName}List(ids: number[]) {
|
||||||
export function export${simpleClassName}(params: any) {
|
export function export${simpleClassName}(params: any) {
|
||||||
return requestClient.download('${baseURL}/export-excel', { params });
|
return requestClient.download('${baseURL}/export-excel', { params });
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
/** 下载${table.classComment}导入模板 */
|
|
||||||
export function import${simpleClassName}Template() {
|
|
||||||
return requestClient.download('${baseURL}/get-import-template');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
export function import${simpleClassName}(data: FormData) {
|
|
||||||
return requestClient.post('${baseURL}/import', data);
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#foreach ($subTable in $subTables)
|
#foreach ($subTable in $subTables)
|
||||||
|
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { FileType } from 'ant-design-vue/es/upload/interface';
|
|
||||||
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
|
||||||
|
|
||||||
import { Button, message, Upload } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { import${simpleClassName}, import${simpleClassName}Template } from '#/api/${table.moduleName}/${table.businessName}';
|
|
||||||
|
|
||||||
defineOptions({ name: '${simpleClassName}Import' });
|
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
|
||||||
const fileRef = ref<File | null>(null);
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
|
||||||
async onConfirm() {
|
|
||||||
if (!fileRef.value) {
|
|
||||||
message.error('请上传文件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modalApi.lock();
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', fileRef.value);
|
|
||||||
const response: any = await import${simpleClassName}(formData);
|
|
||||||
const data = response?.data ?? response ?? {};
|
|
||||||
let text = '导入成功数量:' + (data.successCount || 0) + ';导入失败数量:' + (data.failureCount || 0) + ';';
|
|
||||||
if (data.failureRows) {
|
|
||||||
Object.keys(data.failureRows).forEach((rowNo) => {
|
|
||||||
text += '< 第' + rowNo + '行: ' + data.failureRows[rowNo] + ' >';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
message.info(text);
|
|
||||||
await modalApi.close();
|
|
||||||
emit('success');
|
|
||||||
} finally {
|
|
||||||
modalApi.unlock();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 上传前:拦截 antd Upload 的自动上传,文件存到 ref */
|
|
||||||
function beforeUpload(file: FileType) {
|
|
||||||
fileRef.value = file as unknown as File;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 下载导入模板 */
|
|
||||||
async function handleDownload() {
|
|
||||||
const data = await import${simpleClassName}Template();
|
|
||||||
downloadFileFromBlobPart({ fileName: '${table.classComment}导入模板.xls', source: data });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal title="导入${table.classComment}" class="w-1/3">
|
|
||||||
<div class="mx-4">
|
|
||||||
<Upload :max-count="1" accept=".xls,.xlsx" :before-upload="beforeUpload">
|
|
||||||
<Button type="primary"> 选择 Excel 文件 </Button>
|
|
||||||
</Upload>
|
|
||||||
</div>
|
|
||||||
<template #prepend-footer>
|
|
||||||
<div class="flex flex-auto items-center">
|
|
||||||
<Button @click="handleDownload"> 下载导入模板 </Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
@ -31,9 +31,6 @@ import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClas
|
||||||
#else## 标准表接口
|
#else## 标准表接口
|
||||||
import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
|
import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
|
||||||
import ${simpleClassName}Import from './modules/import-form.vue';
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
|
#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
|
||||||
/** 子表的列表 */
|
/** 子表的列表 */
|
||||||
|
|
@ -122,17 +119,6 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: ${simpleClassName}Form,
|
connectedComponent: ${simpleClassName}Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
#if ($importEnable)
|
|
||||||
const [ImportFormModal, importFormModalApi] = useVbenModal({
|
|
||||||
connectedComponent: ${simpleClassName}Import,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
function handleImport() {
|
|
||||||
importFormModalApi.open();
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
/** 创建${table.classComment} */
|
/** 创建${table.classComment} */
|
||||||
function handleCreate() {
|
function handleCreate() {
|
||||||
|
|
@ -204,7 +190,6 @@ try {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if (${table.templateType} == 2)
|
#if (${table.templateType} == 2)
|
||||||
/** 切换树形展开/收缩状态 */
|
/** 切换树形展开/收缩状态 */
|
||||||
const isExpanded = ref(true);
|
const isExpanded = ref(true);
|
||||||
|
|
@ -224,9 +209,6 @@ onMounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<FormModal @success="getList" />
|
<FormModal @success="getList" />
|
||||||
#if ($importEnable)
|
|
||||||
<ImportFormModal @success="getList" />
|
|
||||||
#end
|
|
||||||
|
|
||||||
<Card v-if="!hiddenSearchBar" class="mb-4">
|
<Card v-if="!hiddenSearchBar" class="mb-4">
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
|
|
@ -332,16 +314,6 @@ onMounted(() => {
|
||||||
>
|
>
|
||||||
{{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
|
{{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
|
||||||
</Button>
|
</Button>
|
||||||
#if ($importEnable)
|
|
||||||
<Button
|
|
||||||
class="ml-2"
|
|
||||||
type="primary"
|
|
||||||
@click="handleImport"
|
|
||||||
v-access:code="['${permissionPrefix}:import']"
|
|
||||||
>
|
|
||||||
导入
|
|
||||||
</Button>
|
|
||||||
#end
|
|
||||||
<Button
|
<Button
|
||||||
:icon="h(Download)"
|
:icon="h(Download)"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
||||||
|
|
@ -134,21 +134,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ($importEnable)
|
|
||||||
/** 导入的表单 */
|
|
||||||
export function useImportFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
fieldName: 'file',
|
|
||||||
label: '${table.classComment}数据',
|
|
||||||
component: 'Upload',
|
|
||||||
rules: 'required',
|
|
||||||
help: '仅允许导入 xls、xlsx 格式文件',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
#end
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { FileType } from 'ant-design-vue/es/upload/interface';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
|
||||||
|
|
||||||
import { Button, message, Upload } from 'ant-design-vue';
|
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
|
||||||
import { import${simpleClassName}, import${simpleClassName}Template } from '#/api/${table.moduleName}/${table.businessName}';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
import { useImportFormSchema } from '../data';
|
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
|
||||||
commonConfig: {
|
|
||||||
formItemClass: 'col-span-2',
|
|
||||||
labelWidth: 120,
|
|
||||||
},
|
|
||||||
layout: 'horizontal',
|
|
||||||
schema: useImportFormSchema(),
|
|
||||||
showDefaultActions: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
|
||||||
async onConfirm() {
|
|
||||||
const { valid } = await formApi.validate();
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modalApi.lock();
|
|
||||||
// 提交表单
|
|
||||||
const data = await formApi.getValues();
|
|
||||||
try {
|
|
||||||
await import${simpleClassName}(data.file);
|
|
||||||
// 关闭并提示
|
|
||||||
await modalApi.close();
|
|
||||||
emit('success');
|
|
||||||
message.success($t('ui.actionMessage.operationSuccess'));
|
|
||||||
} finally {
|
|
||||||
modalApi.unlock();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 上传前 */
|
|
||||||
function beforeUpload(file: FileType) {
|
|
||||||
formApi.setFieldValue('file', file);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 下载模板 */
|
|
||||||
async function handleDownload() {
|
|
||||||
const data = await import${simpleClassName}Template();
|
|
||||||
downloadFileFromBlobPart({ fileName: '${table.classComment}导入模板.xls', source: data });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal title="导入${table.classComment}" class="w-1/3">
|
|
||||||
<Form class="mx-4">
|
|
||||||
<template #file>
|
|
||||||
<div class="w-full">
|
|
||||||
<Upload
|
|
||||||
:max-count="1"
|
|
||||||
accept=".xls,.xlsx"
|
|
||||||
:before-upload="beforeUpload"
|
|
||||||
>
|
|
||||||
<Button type="primary"> 选择 Excel 文件 </Button>
|
|
||||||
</Upload>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Form>
|
|
||||||
<template #prepend-footer>
|
|
||||||
<div class="flex flex-auto items-center">
|
|
||||||
<Button @click="handleDownload"> 下载导入模板 </Button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
@ -26,9 +26,6 @@ import {
|
||||||
get${simpleClassName}Page,
|
get${simpleClassName}Page,
|
||||||
} from '#/api/${table.moduleName}/${table.businessName}';
|
} from '#/api/${table.moduleName}/${table.businessName}';
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
|
||||||
import ${simpleClassName}Import from './modules/import-form.vue';
|
|
||||||
#end
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
@ -53,17 +50,6 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
#if ($importEnable)
|
|
||||||
const [ImportFormModal, importFormModalApi] = useVbenModal({
|
|
||||||
connectedComponent: ${simpleClassName}Import,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
function handleImport() {
|
|
||||||
importFormModalApi.open();
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
|
#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
|
||||||
|
|
||||||
/** 切换树形展开/收缩状态 */
|
/** 切换树形展开/收缩状态 */
|
||||||
|
|
@ -149,7 +135,6 @@ async function handleExport() {
|
||||||
downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
|
|
@ -225,9 +210,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<FormModal @success="handleRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
#if ($importEnable)
|
|
||||||
<ImportFormModal @success="handleRefresh" />
|
|
||||||
#end
|
|
||||||
#if ($table.templateType == 11) ## erp情况
|
#if ($table.templateType == 11) ## erp情况
|
||||||
<div>
|
<div>
|
||||||
#end
|
#end
|
||||||
|
|
@ -265,14 +247,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
onClick: handleExpand,
|
onClick: handleExpand,
|
||||||
},
|
},
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
|
||||||
{
|
|
||||||
label: '导入',
|
|
||||||
type: 'primary',
|
|
||||||
auth: ['${table.moduleName}:${simpleClassName_strikeCase}:import'],
|
|
||||||
onClick: handleImport,
|
|
||||||
},
|
|
||||||
#end
|
|
||||||
{
|
{
|
||||||
label: $t('ui.actionTitle.export'),
|
label: $t('ui.actionTitle.export'),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
|
@ -344,4 +318,4 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
</div>
|
</div>
|
||||||
#end
|
#end
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -100,18 +100,6 @@ export function delete${simpleClassName}List(ids: number[]) {
|
||||||
export function export${simpleClassName}(params: any) {
|
export function export${simpleClassName}(params: any) {
|
||||||
return requestClient.download('${baseURL}/export-excel', { params });
|
return requestClient.download('${baseURL}/export-excel', { params });
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
/** 下载${table.classComment}导入模板 */
|
|
||||||
export function import${simpleClassName}Template() {
|
|
||||||
return requestClient.download('${baseURL}/get-import-template');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
export function import${simpleClassName}(data: FormData) {
|
|
||||||
return requestClient.post('${baseURL}/import', data);
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#foreach ($subTable in $subTables)
|
#foreach ($subTable in $subTables)
|
||||||
|
|
|
||||||
|
|
@ -1,75 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { ref } from 'vue';
|
|
||||||
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
|
||||||
|
|
||||||
import { ElButton, ElMessage, ElUpload } from 'element-plus';
|
|
||||||
|
|
||||||
import { import${simpleClassName}, import${simpleClassName}Template } from '#/api/${table.moduleName}/${table.businessName}';
|
|
||||||
|
|
||||||
defineOptions({ name: '${simpleClassName}Import' });
|
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
|
||||||
const fileRef = ref<File | null>(null);
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
|
||||||
async onConfirm() {
|
|
||||||
if (!fileRef.value) {
|
|
||||||
ElMessage.error('请上传文件');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modalApi.lock();
|
|
||||||
try {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', fileRef.value);
|
|
||||||
const response: any = await import${simpleClassName}(formData);
|
|
||||||
const data = response?.data ?? response ?? {};
|
|
||||||
let text = '导入成功数量:' + (data.successCount || 0) + ';导入失败数量:' + (data.failureCount || 0) + ';';
|
|
||||||
if (data.failureRows) {
|
|
||||||
Object.keys(data.failureRows).forEach((rowNo) => {
|
|
||||||
text += '< 第' + rowNo + '行: ' + data.failureRows[rowNo] + ' >';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ElMessage.info(text);
|
|
||||||
await modalApi.close();
|
|
||||||
emit('success');
|
|
||||||
} finally {
|
|
||||||
modalApi.unlock();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 文件改变 */
|
|
||||||
function handleChange(file: any) {
|
|
||||||
if (file.raw) {
|
|
||||||
fileRef.value = file.raw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 下载导入模板 */
|
|
||||||
async function handleDownload() {
|
|
||||||
const data = await import${simpleClassName}Template();
|
|
||||||
downloadFileFromBlobPart({ fileName: '${table.classComment}导入模板.xls', source: data });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal title="导入${table.classComment}" class="w-1/3">
|
|
||||||
<div class="mx-4">
|
|
||||||
<ElUpload
|
|
||||||
:limit="1"
|
|
||||||
accept=".xls,.xlsx"
|
|
||||||
:on-change="handleChange"
|
|
||||||
:auto-upload="false"
|
|
||||||
>
|
|
||||||
<ElButton type="primary"> 选择 Excel 文件 </ElButton>
|
|
||||||
</ElUpload>
|
|
||||||
</div>
|
|
||||||
<template #prepend-footer>
|
|
||||||
<div class="flex flex-auto items-center">
|
|
||||||
<ElButton @click="handleDownload"> 下载导入模板 </ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
@ -31,9 +31,7 @@ import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClas
|
||||||
import { isEmpty } from '@vben/utils';
|
import { isEmpty } from '@vben/utils';
|
||||||
import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
|
import { get${simpleClassName}Page, delete${simpleClassName},#if ($deleteBatchEnable) delete${simpleClassName}List,#end export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
import { downloadFileFromBlobPart } from '@vben/utils';
|
||||||
import ${simpleClassName}Import from './modules/import-form.vue';
|
|
||||||
#end
|
|
||||||
|
|
||||||
#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
|
#if ($table.templateType == 12 || $table.templateType == 11) ## 内嵌和erp情况
|
||||||
/** 子表的列表 */
|
/** 子表的列表 */
|
||||||
|
|
@ -122,17 +120,6 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: ${simpleClassName}Form,
|
connectedComponent: ${simpleClassName}Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
#if ($importEnable)
|
|
||||||
const [ImportFormModal, importFormModalApi] = useVbenModal({
|
|
||||||
connectedComponent: ${simpleClassName}Import,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
function handleImport() {
|
|
||||||
importFormModalApi.open();
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
/** 创建${table.classComment} */
|
/** 创建${table.classComment} */
|
||||||
function handleCreate() {
|
function handleCreate() {
|
||||||
|
|
@ -202,7 +189,6 @@ try {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if (${table.templateType} == 2)
|
#if (${table.templateType} == 2)
|
||||||
/** 切换树形展开/收缩状态 */
|
/** 切换树形展开/收缩状态 */
|
||||||
const isExpanded = ref(true);
|
const isExpanded = ref(true);
|
||||||
|
|
@ -222,9 +208,6 @@ onMounted(() => {
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<FormModal @success="getList" />
|
<FormModal @success="getList" />
|
||||||
#if ($importEnable)
|
|
||||||
<ImportFormModal @success="getList" />
|
|
||||||
#end
|
|
||||||
|
|
||||||
<ContentWrap v-if="!hiddenSearchBar">
|
<ContentWrap v-if="!hiddenSearchBar">
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
|
|
@ -333,16 +316,6 @@ onMounted(() => {
|
||||||
>
|
>
|
||||||
{{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
|
{{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
|
||||||
</el-button>
|
</el-button>
|
||||||
#if ($importEnable)
|
|
||||||
<el-button
|
|
||||||
class="ml-2"
|
|
||||||
type="primary"
|
|
||||||
@click="handleImport"
|
|
||||||
v-access:code="['${permissionPrefix}:import']"
|
|
||||||
>
|
|
||||||
导入
|
|
||||||
</el-button>
|
|
||||||
#end
|
|
||||||
<el-button
|
<el-button
|
||||||
:icon="h(Download)"
|
:icon="h(Download)"
|
||||||
type="primary"
|
type="primary"
|
||||||
|
|
|
||||||
|
|
@ -113,18 +113,6 @@ export function delete${simpleClassName}List(ids: number[]) {
|
||||||
export function export${simpleClassName}(params: any) {
|
export function export${simpleClassName}(params: any) {
|
||||||
return requestClient.download('${baseURL}/export-excel', { params });
|
return requestClient.download('${baseURL}/export-excel', { params });
|
||||||
}
|
}
|
||||||
#if ($importEnable)
|
|
||||||
|
|
||||||
/** 下载${table.classComment}导入模板 */
|
|
||||||
export function import${simpleClassName}Template() {
|
|
||||||
return requestClient.download('${baseURL}/get-import-template');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
export function import${simpleClassName}(file: File) {
|
|
||||||
return requestClient.upload('${baseURL}/import', { file });
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
|
|
||||||
## 特殊:主子表专属逻辑
|
## 特殊:主子表专属逻辑
|
||||||
#foreach ($subTable in $subTables)
|
#foreach ($subTable in $subTables)
|
||||||
|
|
|
||||||
|
|
@ -137,21 +137,6 @@ export function useFormSchema(): VbenFormSchema[] {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
#if ($importEnable)
|
|
||||||
/** 导入的表单 */
|
|
||||||
export function useImportFormSchema(): VbenFormSchema[] {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
fieldName: 'file',
|
|
||||||
label: '${table.classComment}数据',
|
|
||||||
component: 'Upload',
|
|
||||||
rules: 'required',
|
|
||||||
help: '仅允许导入 xls、xlsx 格式文件',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
#end
|
|
||||||
/** 列表的搜索表单 */
|
/** 列表的搜索表单 */
|
||||||
export function useGridFormSchema(): VbenFormSchema[] {
|
export function useGridFormSchema(): VbenFormSchema[] {
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
<script lang="ts" setup>
|
|
||||||
import { useVbenModal } from '@vben/common-ui';
|
|
||||||
import { downloadFileFromBlobPart } from '@vben/utils';
|
|
||||||
|
|
||||||
import { ElButton, ElMessage, ElUpload } from 'element-plus';
|
|
||||||
|
|
||||||
import { useVbenForm } from '#/adapter/form';
|
|
||||||
import { import${simpleClassName}, import${simpleClassName}Template } from '#/api/${table.moduleName}/${table.businessName}';
|
|
||||||
import { $t } from '#/locales';
|
|
||||||
|
|
||||||
import { useImportFormSchema } from '../data';
|
|
||||||
|
|
||||||
const emit = defineEmits(['success']);
|
|
||||||
|
|
||||||
const [Form, formApi] = useVbenForm({
|
|
||||||
commonConfig: {
|
|
||||||
formItemClass: 'col-span-2',
|
|
||||||
labelWidth: 120,
|
|
||||||
},
|
|
||||||
layout: 'horizontal',
|
|
||||||
schema: useImportFormSchema(),
|
|
||||||
showDefaultActions: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const [Modal, modalApi] = useVbenModal({
|
|
||||||
async onConfirm() {
|
|
||||||
const { valid } = await formApi.validate();
|
|
||||||
if (!valid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
modalApi.lock();
|
|
||||||
// 提交表单
|
|
||||||
const data = await formApi.getValues();
|
|
||||||
try {
|
|
||||||
await import${simpleClassName}(data.file);
|
|
||||||
// 关闭并提示
|
|
||||||
await modalApi.close();
|
|
||||||
emit('success');
|
|
||||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
|
||||||
} finally {
|
|
||||||
modalApi.unlock();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 文件改变时 */
|
|
||||||
function handleChange(file: any) {
|
|
||||||
if (file.raw) {
|
|
||||||
formApi.setFieldValue('file', file.raw);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 下载模板 */
|
|
||||||
async function handleDownload() {
|
|
||||||
const data = await import${simpleClassName}Template();
|
|
||||||
downloadFileFromBlobPart({ fileName: '${table.classComment}导入模板.xls', source: data });
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal title="导入${table.classComment}" class="w-1/3">
|
|
||||||
<Form class="mx-4">
|
|
||||||
<template #file>
|
|
||||||
<div class="w-full">
|
|
||||||
<ElUpload
|
|
||||||
:limit="1"
|
|
||||||
accept=".xls,.xlsx"
|
|
||||||
:on-change="handleChange"
|
|
||||||
:auto-upload="false"
|
|
||||||
>
|
|
||||||
<ElButton type="primary"> 选择 Excel 文件 </ElButton>
|
|
||||||
</ElUpload>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Form>
|
|
||||||
<template #prepend-footer>
|
|
||||||
<div class="flex flex-auto items-center">
|
|
||||||
<ElButton @click="handleDownload"> 下载导入模板 </ElButton>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
|
|
@ -26,9 +26,6 @@ import {
|
||||||
get${simpleClassName}Page,
|
get${simpleClassName}Page,
|
||||||
} from '#/api/${table.moduleName}/${table.businessName}';
|
} from '#/api/${table.moduleName}/${table.businessName}';
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
|
||||||
import ${simpleClassName}Import from './modules/import-form.vue';
|
|
||||||
#end
|
|
||||||
import { $t } from '#/locales';
|
import { $t } from '#/locales';
|
||||||
|
|
||||||
import { useGridColumns, useGridFormSchema } from './data';
|
import { useGridColumns, useGridFormSchema } from './data';
|
||||||
|
|
@ -53,17 +50,6 @@ const [FormModal, formModalApi] = useVbenModal({
|
||||||
connectedComponent: Form,
|
connectedComponent: Form,
|
||||||
destroyOnClose: true,
|
destroyOnClose: true,
|
||||||
});
|
});
|
||||||
#if ($importEnable)
|
|
||||||
const [ImportFormModal, importFormModalApi] = useVbenModal({
|
|
||||||
connectedComponent: ${simpleClassName}Import,
|
|
||||||
destroyOnClose: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
/** 导入${table.classComment} */
|
|
||||||
function handleImport() {
|
|
||||||
importFormModalApi.open();
|
|
||||||
}
|
|
||||||
#end
|
|
||||||
#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
|
#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩
|
||||||
|
|
||||||
/** 切换树形展开/收缩状态 */
|
/** 切换树形展开/收缩状态 */
|
||||||
|
|
@ -147,7 +133,6 @@ async function handleExport() {
|
||||||
downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
|
downloadFileFromBlobPart({ fileName: '${table.classComment}.xls', source: data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const [Grid, gridApi] = useVbenVxeGrid({
|
const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
formOptions: {
|
formOptions: {
|
||||||
schema: useGridFormSchema(),
|
schema: useGridFormSchema(),
|
||||||
|
|
@ -223,9 +208,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
<template>
|
<template>
|
||||||
<Page auto-content-height>
|
<Page auto-content-height>
|
||||||
<FormModal @success="handleRefresh" />
|
<FormModal @success="handleRefresh" />
|
||||||
#if ($importEnable)
|
|
||||||
<ImportFormModal @success="handleRefresh" />
|
|
||||||
#end
|
|
||||||
#if ($table.templateType == 11) ## erp情况
|
#if ($table.templateType == 11) ## erp情况
|
||||||
<div>
|
<div>
|
||||||
#end
|
#end
|
||||||
|
|
@ -263,14 +245,6 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
onClick: handleExpand,
|
onClick: handleExpand,
|
||||||
},
|
},
|
||||||
#end
|
#end
|
||||||
#if ($importEnable)
|
|
||||||
{
|
|
||||||
label: '导入',
|
|
||||||
type: 'primary',
|
|
||||||
auth: ['${table.moduleName}:${simpleClassName_strikeCase}:import'],
|
|
||||||
onClick: handleImport,
|
|
||||||
},
|
|
||||||
#end
|
|
||||||
{
|
{
|
||||||
label: $t('ui.actionTitle.export'),
|
label: $t('ui.actionTitle.export'),
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
|
|
@ -343,4 +317,4 @@ const [Grid, gridApi] = useVbenVxeGrid({
|
||||||
</div>
|
</div>
|
||||||
#end
|
#end
|
||||||
</Page>
|
</Page>
|
||||||
</template>
|
</template>
|
||||||
|
|
@ -84,19 +84,4 @@ public class CodegenBuilderTest extends BaseMockitoUnitTest {
|
||||||
assertEquals("input", column.getHtmlType());
|
assertEquals("input", column.getHtmlType());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testSanitizeComment() {
|
|
||||||
// 1. null / 空字符串:原样返回
|
|
||||||
assertNull(codegenBuilder.sanitizeComment(null));
|
|
||||||
assertEquals("", codegenBuilder.sanitizeComment(""));
|
|
||||||
// 2. 无英文引号:原样返回
|
|
||||||
assertEquals("无引号注释", codegenBuilder.sanitizeComment("无引号注释"));
|
|
||||||
// 3. 含英文双引号:替换为中文左双引号
|
|
||||||
assertEquals("含“双“引号", codegenBuilder.sanitizeComment("含\"双\"引号"));
|
|
||||||
// 4. 含英文单引号:替换为中文左单引号
|
|
||||||
assertEquals("含‘单‘引号", codegenBuilder.sanitizeComment("含'单'引号"));
|
|
||||||
// 5. 双 / 单引号混合
|
|
||||||
assertEquals("“混‘搭“‘", codegenBuilder.sanitizeComment("\"混'搭\"'"));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
|
||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
||||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenVOTypeEnum;
|
|
||||||
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
|
import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.mockito.InjectMocks;
|
import org.mockito.InjectMocks;
|
||||||
|
|
@ -20,10 +19,8 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
|
@ -44,18 +41,16 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
|
||||||
|
|
||||||
@Spy
|
@Spy
|
||||||
protected CodegenProperties codegenProperties = new CodegenProperties()
|
protected CodegenProperties codegenProperties = new CodegenProperties()
|
||||||
.setBasePackage("cn.iocoder.yudao")
|
.setBasePackage("cn.iocoder.yudao");
|
||||||
.setVoType(CodegenVOTypeEnum.VO.getType())
|
|
||||||
.setDeleteBatchEnable(true)
|
|
||||||
.setUnitTestEnable(true)
|
|
||||||
.setImportEnable(false);
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
codegenEngine.setJakartaEnable(true); // 强制使用 jakarta,保证单测可以基于 jakarta 断言
|
codegenEngine.setJakartaEnable(true); // 强制使用 jakarta,保证单测可以基于 jakarta 断言
|
||||||
codegenEngine.initGlobalBindingMap();
|
codegenEngine.initGlobalBindingMap();
|
||||||
// 获取测试文件 resources 路径,writeResult 调试用
|
// 单测强制使用
|
||||||
|
// 获取测试文件 resources 路径
|
||||||
String absolutePath = FileUtil.getAbsolutePath("application-unit-test.yaml");
|
String absolutePath = FileUtil.getAbsolutePath("application-unit-test.yaml");
|
||||||
|
// 系统不一样生成的文件也有差异,那就各自生成各自的
|
||||||
resourcesPath = absolutePath.split("/target")[0] + "/src/test/resources/codegen/";
|
resourcesPath = absolutePath.split("/target")[0] + "/src/test/resources/codegen/";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,32 +82,17 @@ public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 重新生成断言数据的开关,命令行加 {@code -Dcodegen.regenerate=true} 启用
|
|
||||||
*/
|
|
||||||
private static final boolean REGENERATE = Boolean.parseBoolean(System.getProperty("codegen.regenerate", "false"));
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
protected void assertResult(Map<String, String> result, String path) {
|
protected static void assertResult(Map<String, String> result, String path) {
|
||||||
if (REGENERATE) {
|
|
||||||
writeResult(result, resourcesPath + path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String assertContent = ResourceUtil.readUtf8Str("codegen/" + path + "/assert.json");
|
String assertContent = ResourceUtil.readUtf8Str("codegen/" + path + "/assert.json");
|
||||||
List<HashMap> asserts = JsonUtils.parseArray(assertContent, HashMap.class);
|
List<HashMap> asserts = JsonUtils.parseArray(assertContent, HashMap.class);
|
||||||
Set<String> expectedFiles = asserts.stream()
|
assertEquals(asserts.size(), result.size());
|
||||||
.map(m -> (String) m.get("filePath"))
|
// 校验每个文件
|
||||||
.collect(java.util.stream.Collectors.toCollection(LinkedHashSet::new));
|
|
||||||
assertEquals(expectedFiles, result.keySet(), "生成文件集合不匹配");
|
|
||||||
// 校验每个文件;归一化 \r\n 为 \n,让断言不依赖文件落盘的换行风格
|
|
||||||
asserts.forEach(assertMap -> {
|
asserts.forEach(assertMap -> {
|
||||||
String contentPath = (String) assertMap.get("contentPath");
|
String contentPath = (String) assertMap.get("contentPath");
|
||||||
String filePath = (String) assertMap.get("filePath");
|
String filePath = (String) assertMap.get("filePath");
|
||||||
String expected = ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath)
|
String content = ResourceUtil.readUtf8Str("codegen/" + path + "/" + contentPath);
|
||||||
.replace("\r\n", "\n");
|
assertEquals(content, result.get(filePath), filePath + ":不匹配");
|
||||||
String actual = result.get(filePath);
|
|
||||||
assertEquals(expected, actual == null ? null : actual.replace("\r\n", "\n"),
|
|
||||||
filePath + ":不匹配");
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,109 +0,0 @@
|
||||||
package cn.iocoder.yudao.module.infra.service.codegen.inner;
|
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
|
|
||||||
import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
|
|
||||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
|
|
||||||
import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
|
|
||||||
import com.baomidou.mybatisplus.annotation.DbType;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link CodegenEngine} 的 Vue3 + Vben5 + AntD + General 单元测试
|
|
||||||
*
|
|
||||||
* @author 芋道源码
|
|
||||||
*/
|
|
||||||
public class CodegenEngineVben5AntdGeneralTest extends CodegenEngineAbstractTest {
|
|
||||||
|
|
||||||
private static final Integer FRONT_TYPE = CodegenFrontTypeEnum.VUE3_VBEN5_ANTD_GENERAL.getType();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExecute_one() {
|
|
||||||
// 准备参数
|
|
||||||
CodegenTableDO table = getTable("student")
|
|
||||||
.setFrontType(FRONT_TYPE)
|
|
||||||
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
|
|
||||||
List<CodegenColumnDO> columns = getColumnList("student");
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
|
||||||
// 断言
|
|
||||||
assertResult(result, "/vben5_antd_general_one");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExecute_one_importEnable() {
|
|
||||||
// 开启 import 开关
|
|
||||||
codegenProperties.setImportEnable(true);
|
|
||||||
codegenEngine.initGlobalBindingMap();
|
|
||||||
// 准备参数
|
|
||||||
CodegenTableDO table = getTable("student")
|
|
||||||
.setFrontType(FRONT_TYPE)
|
|
||||||
.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
|
|
||||||
List<CodegenColumnDO> columns = getColumnList("student");
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
|
||||||
// 断言
|
|
||||||
assertResult(result, "/vben5_antd_general_one_importEnable");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExecute_tree() {
|
|
||||||
// 准备参数
|
|
||||||
CodegenTableDO table = getTable("category")
|
|
||||||
.setFrontType(FRONT_TYPE)
|
|
||||||
.setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
|
|
||||||
List<CodegenColumnDO> columns = getColumnList("category");
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
|
|
||||||
// 断言
|
|
||||||
assertResult(result, "/vben5_antd_general_tree");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExecute_master_normal() {
|
|
||||||
testExecute_master(CodegenTemplateTypeEnum.MASTER_NORMAL, "/vben5_antd_general_master_normal");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExecute_master_erp() {
|
|
||||||
testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vben5_antd_general_master_erp");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testExecute_master_inner() {
|
|
||||||
testExecute_master(CodegenTemplateTypeEnum.MASTER_INNER, "/vben5_antd_general_master_inner");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
|
|
||||||
// 准备参数
|
|
||||||
CodegenTableDO table = getTable("student")
|
|
||||||
.setFrontType(FRONT_TYPE)
|
|
||||||
.setTemplateType(templateType.getType());
|
|
||||||
List<CodegenColumnDO> columns = getColumnList("student");
|
|
||||||
// 准备参数(子表)
|
|
||||||
CodegenTableDO contactTable = getTable("contact")
|
|
||||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
|
||||||
.setFrontType(FRONT_TYPE)
|
|
||||||
.setSubJoinColumnId(100L).setSubJoinMany(true);
|
|
||||||
List<CodegenColumnDO> contactColumns = getColumnList("contact");
|
|
||||||
// 准备参数(班主任)
|
|
||||||
CodegenTableDO teacherTable = getTable("teacher")
|
|
||||||
.setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
|
|
||||||
.setFrontType(FRONT_TYPE)
|
|
||||||
.setSubJoinColumnId(200L).setSubJoinMany(false);
|
|
||||||
List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
|
|
||||||
|
|
||||||
// 调用
|
|
||||||
Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
|
|
||||||
Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
|
|
||||||
// 断言
|
|
||||||
assertResult(result, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||