软件研究之JeecgBoot

标签: 无 分类: 未分类 创建时间:2022-05-17 01:51:45 更新时间:2024-11-24 10:04:02

前言

这里我主要是采用了jeecg-boot进行体验,主要就是讲如何运行官方的代码。这里我测试和安装了jeecg-boot 3.0版本,2021年12月05日最新版吧。

1.后端启动

(1)安装MySQL
安装MySQL,创建数据库 jeecg-boot,并导入相应的数据

(2)创建数据库

1
2
3
4
5
6
7
8
9
10
## 登录数据库
mysql>create database `jeecg-boot`;

## 设置远程登录
mysql>use mysql;
mysql>update user set host = '%' where user ='root';
mysql> flush privileges;

## 退出数据库,导入数据
$>mysql -h localhost -u root -p jeecg-boot < jeecgboot-mysql-5.7.sql

(3)导入数据

1
mysql -h localhost -u root -p jeecg-boot < jeecgboot-mysql-5.7.sql 

(4)修改配置文件
打开后端工程 jeecg-boot,修改 jeecg-boot-module-system模块的application-dev.yml配置文件,找到其中的数据库配置,修改数据库的用户名和密码

(5)启动工程
启动 jeecg-boot-module-system 工程,浏览器中输入 :http://localhost:8080/jeecg-boot/ 即可访问项目

问题

(1) Table ‘jeecg-boot.QRTZ_LOCKS’ doesn’t exist
因为数据库大小写敏感了,又不允许在数据目录初始化之后,修改 lower_case_table_names 的值,只能删除数据目录,修改值,然后重新初始化。

1
2
3
4
5
6
7
8
9
10
11
12
## 停止MySQL
systemctl stop mysqld

## 删除数据目录,即删除 /var/lib/mysql 目录、
rm -rf /var/lib/mysql

## 在MySQL配置文件( /etc/my.cnf )中添加 lower_case_table_names=1
vi /etc/my.cnf

## 启动 MySQL
systemctl start mysqld
## 使用临时密码登录
参考文章:
1.JeecgBoot启动提示:QRTZ_LOCKS表不存在 这里是在mysql的配置文件/etc/my.cnf中增加 lower_case_table_names=1,关闭大小写,但是我的添加之后,直接就启动不了mysql了。
2.JeecgBoot常见问题大全
3.Mysql8.0开启忽略表大小写,无法启动,解决方案 删除数据之后,重新初始化
4.mysql8.0设置忽略大小写后无法启动
5.mysql8.0修改大小写敏感配置方法 在 MySQL 8 中,数据目录初始化之后,不再允许更改 lower_case_table_names = 1 的 值;MySQL 基于某些原因,禁止在重新启动 MySQL 服务时将 lower_case_table_names 设置 成不同于初始化 MySQL 服务时设置的 lower_case_table_names 值。也就是说启动(重启)MySQL 时,lower_case_table_names的值必须于,初始化 MySQL 时(安装 MySQL 后的首次启动)的值相同。
6.linux下mysql8,表名称设置不区分大小写

2.启动前端

~~下载源代码,打开 ant-design-vue-jeecg 文件,执行依赖安装。~~

1
2
3
4
## 安装依赖
pnpm install
## 运行
pnpm serve

刚开始我用的是pnpm安装的依赖,总是饱错,后来使用的yarn进行依赖的安装,竟然奇迹般的没有报错。

1
2
3
4
## 安装依赖
yarn install
## 启动项目
yarn serve

问题

(1) Failed to resolve loader: cache-loader
我在github仓库中下载了源码,进入了 ant-design-vue-jeecg 目录,然后执行了依赖安装 ,运行

1
2
3
4
## 依赖安装
pnpm install
## 运行
pnpm serve

结果出现了依赖不满足:Failed to resolve loader: cache-loader

【尝试】
我尝试安装了依赖,但是又跳出来了其他的依赖不满足

1
pnpm add cache-loader babel-loader file-loader url-loader vue-style-loader css-loader postcss-loader

【结果】
结果就是出现了新的问题:Syntax Error: TypeError: this.getOptions is not a function


【尝试】
我觉得可能是pnpm有问题,我换成了官网的yarn 安装,安装完成之后,直接报错了:this.getOptions is not a function

3.自定义模块

我在idea中新建了自定义的模块,命名为 disasterapi

(1) 引入 jeecg-boot-core 模块
在自定义模块中,引入jeecg-boot的核心模块。

1
2
3
4
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>jeecg-boot-base-core</artifactId>
</dependency>

(2) jeecg-system-start 模块引入自定义的模块

1
2
3
4
5
<dependency>
<groupId>org.jeecgframework.boot</groupId>
<artifactId>disasterapi</artifactId>
<version>${jeecgboot.version}</version>
</dependency>

(3) 修改Application路径扫描
因为我的代码没有按照官方的指导进行创建,所以需要修改 start 的启动类 JeecgSystemApplication,使bean能被扫描。

1
@ComponentScan(basePackages = {"com.openmap.disaster.*","org.jeecg.*"})

(4) 修改 mybatis-plus 的类扫描路径
修改 application-dev 中的mybatis-plus配置,增加自定义的 Mapper.xml 路径。

1
2
mybatis-plus:
mapper-locations: classpath*:org/jeecg/modules/**/xml/*Mapper.xml,classpath*:com/openmap/disaster/**/xml/*Mapper.xml

在 启动类 JeecgSystemApplication中添加 MapScan。

1
@MapperScan(basePackages = {"com.openmap.disaster.mapper"})

4.独立页面

jeecg-boot是一个后端管理框架,那么这个后端管理的页面整个的框架就是菜单栏和内容页决定好的了,如何开发一个独立的页面呢?

这部分需要修改源码实现,因为全部的页面的模版都是使用的 layouts/default/index.vue 这个模版,会有左侧菜单,右侧的内容页,要想跳转到自定义页面,需要独立修改这部分的内容。主要修改 src/router/helper/routeHelper.ts 文件里面的内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// Dynamic introduction
function asyncImportRoute(routes: AppRouteRecordRaw[] | undefined) {
if (!dynamicModules) {
dynamicModules = import.meta.glob('../../views/**/*.{vue,tsx}');
const dynamicLayoutModules = import.meta.glob('../../layouts/**/*.{vue,tsx}');
//合并online lib路由
dynamicModules = Object.assign({}, dynamicModules, dynamicLayoutModules, packageViews);
}
if (!routes) return;
routes.forEach((item) => {
//【jeecg-boot/issues/I5N2PN】左侧动态菜单怎么做国际化处理 2022-10-09
//菜单支持国际化翻译
if (item?.meta?.title) {
const { t } = useI18n();
if (item.meta.title.includes("t('") && t) {
item.meta.title = eval(item.meta.title);
//console.log('译后: ',item.meta.title)
}
}

// update-begin--author:sunjianlei---date:20210918---for:适配旧版路由选项 --------
// @ts-ignore 适配隐藏路由
if (item?.hidden) {
item.meta.hideMenu = true;
//是否隐藏面包屑
item.meta.hideBreadcrumb = true;
}
// @ts-ignore 添加忽略路由配置
if (item?.route == 0) {
item.meta.ignoreRoute = true;
}
// @ts-ignore 添加是否缓存路由配置
item.meta.ignoreKeepAlive = !item?.meta.keepAlive;
const token = getToken();
const tenantId = getTenantId();
// URL支持{{ window.xxx }}占位符变量
//update-begin---author:wangshuai ---date:20220711 for:[VUEN-1638]菜单tenantId需要动态生成------------
item.component = (item.component || '')
.replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2))
.replace('${token}', token)
.replace('${tenantId}', tenantId);
//update-end---author:wangshuai ---date:20220711 for:[VUEN-1638]菜单tenantId需要动态生成------------
// 适配 iframe
if (/^\/?http(s)?/.test(item.component as string)) {
item.component = item.component.substring(1, item.component.length);
}
if (/^http(s)?/.test(item.component as string)) {
if (item.meta?.internalOrExternal) {
// @ts-ignore 外部打开
item.path = item.component;
// update-begin--author:sunjianlei---date:20220408---for: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下
item.path = item.path.replace('#', URL_HASH_TAB);
// update-end--author:sunjianlei---date:20220408---for: 【VUEN-656】配置外部网址打不开,原因是带了#号,需要替换一下
} else {
// @ts-ignore 内部打开
item.meta.frameSrc = item.component;
}
delete item.component;
}
// update-end--author:sunjianlei---date:20210918---for:适配旧版路由选项 --------
if (!item.component && item.meta?.frameSrc) {
item.component = 'IFRAME';
}
let { component } = item;
const { name } = item;
const { children } = item;
if (component) {
const layoutFound = LayoutMap.get(component.toUpperCase());
if (layoutFound) {
item.component = layoutFound;
} else {
// update-end--author:zyf---date:20220307--for:VUEN-219兼容后台返回动态首页,目的适配跟v2版本配置一致 --------
if (component.indexOf('dashboard/') > -1) {
//当数据标sys_permission中component没有拼接index时前端需要拼接
if (component.indexOf('/index') < 0) {
component = component + '/index';
}
}

// update-end--author:zyf---date:20220307---for:VUEN-219兼容后台返回动态首页,目的适配跟v2版本配置一致 --------
item.component = dynamicImport(dynamicModules, component as string);
}
} else if (name) {
item.component = getParentLayout();
}
children && asyncImportRoute(children);
});
}

function dynamicImport(dynamicModules: Record<string, () => Promise<Recordable>>, component: string) {
const keys = Object.keys(dynamicModules);
const matchKeys = keys.filter((key) => {
// 替换全部的空路径
const k = key.replace('../../views', '').replace('../..', '');
// console.log(k);
const startFlag = component.startsWith('/');
const endFlag = component.endsWith('.vue') || component.endsWith('.tsx');
const startIndex = startFlag ? 0 : 1;
const lastIndex = endFlag ? k.length : k.lastIndexOf('.');
return k.substring(startIndex, lastIndex) === component;
});
if (matchKeys?.length === 1) {
const matchKey = matchKeys[0];
return dynamicModules[matchKey];
} else if (matchKeys?.length > 1) {
warn(
'Please do not create `.vue` and `.TSX` files with the same file name in the same hierarchical directory under the views folder. This will cause dynamic introduction failure'
);
return;
}
}
/**
*
* @param routeList
* @returns
*/
// Turn background objects into routing objects
export function transformObjToRoute<T = AppRouteModule>(routeList: AppRouteModule[]): T[] {
/**
* 动态加载layout
*/
if (!dynamicModules) {
dynamicModules = import.meta.glob('../../views/**/*.{vue,tsx}');
// 将其他模板纳入动态创建中
const dynamicLayoutModules = import.meta.glob('../../layouts/**/*.{vue,tsx}');
//合并online lib路由
dynamicModules = Object.assign({}, dynamicModules, dynamicLayoutModules, packageViews);
}

routeList.forEach((route) => {
let component = route.component as string;
if (component) {
component = component.toUpperCase();
if (component === 'LAYOUT') {
route.component = LayoutMap.get(component);
} else {
component = component.toLowerCase();
route.children = [cloneDeep(route)];

// 动态创建
route.component = dynamicImport(dynamicModules, component as string);

// route.component = LAYOUT;
route.name = `${route.name}Parent`;
route.path = '';
const meta = route.meta || {};
meta.single = true;
meta.affix = false;
route.meta = meta;
}
} else {
warn('请正确配置路由:' + route?.name + '的component属性');
}
route.children && asyncImportRoute(route.children);
});
return routeList as unknown as T[];
}

参考文章:
1.vue-roter 4报错: Error: Invalid route component/Uncaught (in promise) Error: Invalid route component解决
2.关于jeecg登录后跳转单独页面记录贴 这里通过了静态的路由配置,并且修改了权限,实现了独立跳转这个方法。

5.Postgresql

为了特殊的原因,我需要使用 postgresql 数据库,于是我将 mysql 转换为postgresql数据库。
(1) 数据库脚本
我原先将mysql脚本转换成了postgresql,后来再次遇到这个问题的时候,我竟然忘记当初到底是怎么搞的了。

参考文章:
1.增加postgresql数据库脚本 这里建议使用navicat进行转换

(2) 修改pom.xml,加入postgresql的依赖。这部分其实不用写,在 jeecg-boot-base-core 模块中,已经写了多个驱动的依赖。

1
2
3
4
5
6
7
<!-- postgresql驱动 -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
<scope>runtime</scope>
</dependency>

(3) 修改 jeecg-module-system/jeecg-system-start 的配置文件,增加下面的配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
spring:
#postgresql 报错问题
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
properties:
hibernate:
temp:
use_jdbc_metadata_defaults: false


## 修改druid配置
validationQuery: SELECT 1


url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: root
driver-class-name: org.postgresql.Driver


## 修改quartz配置
spring
quartz
properties
org
quartz
jobStore
driverDelegateClass: org.quartz.impl.jdbcjobstore.PostgreSQLDelegate

(4) 执行类型转换
将mysql数据转到postgres,原tinyint类型会转成int2类型,这时实体属性是布尔类型 ,会导致插入数据失败。比如出现了:column “hidden” is of type integer but expression is of type boolean(字段 “hidden” 的类型为 integer, 但表达式的类型为 boolean)。下面提示的是:You will need to rewrite or cast the expression。

【尝试方案】
我开始的时候,尝试在Dbeaver中使用sql查询进行下面的数据转换,结果出现 $$ 符号不存在的错误。于是我只能通过 psql 命令行进行命令的操作。使用 psql 命令连接数据库,然后使用 \c 切换数据库,再次执行下面的代码。

1
2
3
4
5
6
7
--- 每一个分号分开执行
create or replace function bool_to_int(boolean) returns int2 as $$
select CAST($1::int as int2);

$$ language sql strict;

create cast (bool as int2) with function bool_to_int(boolean) as implicit;

我尝试多次执行上面的代码,也执行了 drop function,drop cast (bool as int2),最后还是报错。

【解决方案】
经过不懈的努力,尝试了使用下面的更新语句进行更新,结果竟然有效果了,再插入的时候,不再报错了。

1
2
3
4
5
6
7
--- 每一个分号分开执行
create or replace function bool_to_int(boolean) returns int2 as $$
select CAST($1::int as int2);
$$ language sql strict;
create cast (bool as int2) with function bool_to_int(boolean) as implicit;
--- 多加了下面这句
update pg_cast set castcontext='a' where castsource ='boolean'::regtype and casttarget='integer'::regtype;
参考文章:
1.PostgreSQL 整型int与布尔boolean的自动转换设置(含自定义cast与cast规则介绍) 如果数据库已经内置了转换规则,那么可以通过更新系统表的方式,修改自动转换规则。update pg_cast set castcontext=’a’ where castsource =’integer’::regtype and casttarget=’boolean’::regtype;
2.PostgreSQL 自定义自动类型转换(CAST) 自定义CAST的语法如下。AS IMPLICIT,表示在表达式中,或者在赋值操作中,都对类型进行自动转换。
3.PostgreSQL 自动(隐式)类型转换,解决类型不匹配报错问题 DROP CAST (bigint as varchar);
4.postgresql 自动类型转换 字段 “is_leaf” 的类型为 integer, 但表达式的类型为 boolean 建议:你需要重写或转换表达式 位置:126 (在jeecgboot平台的菜单管理中会出现该问题)

(5) 解决大小写问题。
修改了配置文件,倒入数据后,还是会报错:ERROR: column “trigger_name” does not exist。查了资料,说是因为 postgresql 是识别大小写的,要讲 表里面的字段改为小写才行。

  • 查询整个数据库中有大写字段的表

    1
    2
    3
    select * from information_schema.columns 
    where table_schema='public'
    and column_name <> lower(column_name);
  • 创建函数 exec 如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    CREATE OR REPLACE FUNCTION "public"."exec"("sqlstring" varchar)
    RETURNS "pg_catalog"."varchar" AS $BODY$
    declare
    res varchar(50);
    BEGIN
    EXECUTE sqlstring;
    RETURN 'ok';
    END
    $BODY$
    LANGUAGE plpgsql VOLATILE
    COST 100
  • 执行函数 exec,将表 qrtz_triggers 中的全部大写改为小写。

    1
    2
    3
    4
    5
    6
    7
    8
    SELECT
    exec('alter table "' || table_name || '" rename column "' || column_name || '" to ' || lower( column_name ) || ';')
    FROM
    information_schema.COLUMNS
    WHERE
    table_schema = 'public'
    AND column_name <> lower(column_name)
    AND table_name = 'qrtz_triggers';

将全部的 “qrtz” 开头的表,全部的大写转为小些,就不会报错了。

参考文章:
1.postgresql数据库兼容问题 这里提到的函数转化,需要在数据从mysql倒入之后进行。
2.切换其他数据库 Oracle数据、SQL server数据、postgresql数据库。
3.Jeecg-boot入门学习搭建开发环境(数据库改用PostgreSQL) 查了很多方法都不行,还试了Navicat全转小写,但是导致更多坑,所以我解决的方法是数据库的字段名人工改成小写,启动就不会报错了。这里提供了方法,但是没有提供具体的操作,人工其实还是很费时间的。
4.PostgreSQL批量修改列名大小写 这里提供了一个查询那些表里有大写字段的方法
5.PostgreSQL 大小写问题 一键修改表名、字段名为小写 阅读模式 这里针对某一个表进行批量字段大写改为小些的方法,非常的有用。
6.JEECG-Boot使用(一)PostgreSQL替换Mysql数据库 这里用了navcat进行了转换。

(6)增量脚本的问题
升级的时候,官方提供的也是mysql的增量脚本,如果使用了 postgresql 数据库,那么在使用增量脚本的时候,可能就会出现问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
----MySQL-----
--- 新增部门默认是叶子节点,即没有子节点
ALTER TABLE sys_depart
MODIFY COLUMN iz_leaf tinyint(1) NULL DEFAULT 1 COMMENT '是否有叶子节点: 1是0否' AFTER tenant_id;

-- 字段长度不够
ALTER TABLE sys_data_log
MODIFY COLUMN `data_table` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '表名' AFTER `update_time`;

--- 索引
ALTER TABLE `sys_announcement_send` ADD INDEX `idx_sacm_annt_id`(`annt_id`)


----Postgresql----
--- 新增部门默认是叶子节点,即没有子节点
ALTER TABLE public.sys_depart ALTER COLUMN iz_leaf SET DEFAULT 1;

--- 字段长度不够
alter table sys_data_log alter column data_table type varchar(200)
-- 索引
CREATE INDEX idx_sacm_annt_id ON sys_announcement_send (annt_id)
参考文章:
1.PostgreSQL 索引 这里是postgresql创建索引的方法
2.mysql 中添加索引的三种方法
3.PostgreSQL修改表(ALTER TABLE语句)
4.PostgreSQL ALTER TABLE 命令
5.postgresql 修改字段长度 LTER TABLE your_table_name alter COLUMN your_column_name type character varying(3000);
6.Postgresql修改字段的长度 alter table table_name alter column column_name type varchar(200)

(7)操作符不存在: smallint = boolean
和 bool_to_int 方式类似,执行如下的 sql 语句,定义转换方式。

1
2
3
4
5
6
7
CREATE OR REPLACE FUNCTION boolean_to_smallint(b boolean) RETURNS smallint AS $$
BEGIN
RETURN (b::boolean)::bool::int;
END;
$$LANGUAGE plpgsql;

CREATE CAST (boolean AS smallint) WITH FUNCTION boolean_to_smallint(boolean) AS implicit;

注意
每一次重新插入数据的时候,需要重新调用 CREATE CAST 语句

参考文章:
1.PostgreSQL错误: 操作符不存在: smallint = boolean 这里的方法可以尝试一下。

3.代码生成

(1) 导入表
第一次的时候,我在低代码开发里面找到了这个表,简简单单的就可以。 低代码开发 -> 导入数据库表,选择自己需要导入的数据库表,直接导入,但是后来我把其中一个表删除之后,就再也无法新增新的表了,全部还是原先的25个表。

(2) 然后在Online开发上直接生成代码就可以了。

参考文章:
1.Jeecg-Boot 代码生成和添加菜单操作 这个讲了关于如何将生成的代码放到自己的工程中
2.Online功能如何配置成菜单 jeecg-boot开发文档
3.代码生成器配置 jeecg-boot-module-system/src/main/resources/jeecg/jeecg_config.properties 中配置路径。
小额赞助
本人提供免费与付费咨询服务,感谢您的支持!赞助请发邮件通知,方便公布您的善意!
**光 3.01 元
Sun 3.00 元
bibichuan 3.00 元
微信公众号
广告位
诚心邀请广大金主爸爸洽谈合作
每日一省
isNaN 和 Number.isNaN 函数的区别?

1.函数 isNaN 接收参数后,会尝试将这个参数转换为数值,任何不能被转换为数值的的值都会返回 true,因此非数字值传入也会返回 true ,会影响 NaN 的判断。

2.函数 Number.isNaN 会首先判断传入参数是否为数字,如果是数字再继续判断是否为 NaN ,不会进行数据类型的转换,这种方法对于 NaN 的判断更为准确。

每日二省
为什么0.1+0.2 ! == 0.3,如何让其相等?

一个直接的解决方法就是设置一个误差范围,通常称为“机器精度”。对JavaScript来说,这个值通常为2-52,在ES6中,提供了Number.EPSILON属性,而它的值就是2-52,只要判断0.1+0.2-0.3是否小于Number.EPSILON,如果小于,就可以判断为0.1+0.2 ===0.3。

每日三省
== 操作符的强制类型转换规则?

1.首先会判断两者类型是否**相同,**相同的话就比较两者的大小。

2.类型不相同的话,就会进行类型转换。

3.会先判断是否在对比 null 和 undefined,是的话就会返回 true。

4.判断两者类型是否为 string 和 number,是的话就会将字符串转换为 number。

5.判断其中一方是否为 boolean,是的话就会把 boolean 转为 number 再进行判断。

6.判断其中一方是否为 object 且另一方为 string、number 或者 symbol,是的话就会把 object 转为原始类型再进行判断。

每日英语
Happiness is time precipitation, smile is the lonely sad.
幸福是年华的沉淀,微笑是寂寞的悲伤。