自身项目导入luckysheet server
前言
-
luckysheet server
是luckysheet
官方的一个后台。用来和luckyshee
t建立ws
连接、保存文档至数据库。 - 目前最麻烦的是不好导入本身的
spring boot
项目。直接下载官方代码,也很难直接运行。 - 如下是我琢磨出来的比较简单的在自身
spring boot
项目导入luckysheet server
的办法。如果有更好的办法,请告知我。。 - 各位可以试试模块导入方法。。。可能会更加简单。。我原本没想到
luckysheet server
的文件那么多那么复杂。
1.外部环境配置
-
mysql安装
-
redis安装
-
idea安装以及新建一个spring boot web项目。(如果已经有项目就打开原来的项目)
-
数据库初始化。注意schema名称要和配置文件的一致。
CREATE SCHEMA `collsheet` ; CREATE TABLE `luckysheet` ( `id` bigint(30) NOT NULL, `block_id` varchar(200) NOT NULL, `row_col` varchar(50) DEFAULT NULL, `index` varchar(200) NOT NULL, `list_id` varchar(200) NOT NULL, `status` int(1) DEFAULT NULL, `json_data` json DEFAULT NULL, `order` int(3) DEFAULT NULL, `is_delete` int(1) DEFAULT NULL, PRIMARY KEY (`id`), KEY `lib` (`list_id`,`index`,`block_id`), KEY `order` (`order`), KEY `status` (`status`), KEY `is_delete` (`is_delete`) ) ENGINE=InnoDB DEFAULT CHARSET=armscii8; INSERT INTO luckysheet VALUES (139400313311449087, 'fblock', '', '1', '1079500#-8803#7c45f52b7d01486d88bc53cb17dcd2c3', 1, '{"row":84,"name":"Sheet1","chart":[],"color":"","index":"1","order":0,"column":60,"config":{},"status":0,"celldata":[],"ch_width":4748,"rowsplit":[],"rh_height":1790,"scrollTop":0,"scrollLeft":0,"visibledatarow":[],"visibledatacolumn":[],"jfgird_select_save":[],"jfgrid_selection_range":{}}', 0, 0); INSERT INTO luckysheet VALUES (139400313311449088, 'fblock', '', '2', '1079500#-8803#7c45f52b7d01486d88bc53cb17dcd2c3', 0, '{"row":84,"name":"Sheet2","chart":[],"color":"","index":"2","order":1,"column":60,"config":{},"status":0,"celldata":[],"ch_width":4748,"rowsplit":[],"rh_height":1790,"scrollTop":0,"scrollLeft":0,"visibledatarow":[],"visibledatacolumn":[],"jfgird_select_save":[],"jfgrid_selection_range":{}}', 1, 0); INSERT INTO luckysheet VALUES (139400313311449089, 'fblock', '', '3', '1079500#-8803#7c45f52b7d01486d88bc53cb17dcd2c3', 0, '{"row":84,"name":"Sheet3","chart":[],"color":"","index":"3","order":2,"column":60,"config":{},"status":0,"celldata":[],"ch_width":4748,"rowsplit":[],"rh_height":1790,"scrollTop":0,"scrollLeft":0,"visibledatarow":[],"visibledatacolumn":[],"jfgird_select_save":[],"jfgrid_selection_range":{}}', 2, 0);
2.pom.xml
文件修改
-
目的是引入
luckysheet server
需要的依赖。 -
注意版本号。。个别依赖版本不一致会报错。
-
文件修改号后,需要等待idea下载好所有依赖。
-
<!--导入数据库驱动,不同数据库,驱动器不一样--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!--引入 mybatis-spring-boot-starter 的依赖--> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> <!--引入 hutool-all--> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.7.22</version> </dependency> <!--websokcet的starter--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> <!--JSON解析--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version> </dependency> <!--为类添加getter和setter和构造函数等--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <dependency> <!--google的gson工具--> <groupId>com.google.code.gson</groupId> <artifactId>gson</artifactId> <version>2.9.0</version> <scope>compile</scope> </dependency> <!--阿里的druid数据源--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.20</version> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- 给redis集群用的Lettuce的连接池 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.3</version> </dependency>
3.
application.yml
文件修改-
主要是
mysql
数据库配置、redis
配置、日志配置等 -
注意数据库配置有重复。。因为官方就是有重复的。。暂时还没有对重复进行去掉。
-
#服务器配置 server: port: 8080 #数据库配置 spring: application: name: web jackson: time-zone: GMT+8 date-format: yyyy-MM-dd HH:mm:ss http: encoding: charset: UTF-8 enabled: true datasource: druid: url: jdbc:mysql://127.0.0.1:3306/collsheet username: root password: 5587699$lgg driver-class-name: com.mysql.cj.jdbc.Driver redis: host: 127.0.0.1 port: 6379 password: timeout: 10000ms lettuce: pool: max-active: 8 max-wait: -1ms max-idle: 8 min-idle: 0 database: 0 db: mysql: druid: url: jdbc:mysql://127.0.0.1:3306/collsheet username: root password: 5587699$lgg driver-class-name: com.mysql.cj.jdbc.Driver postgre: druid: url: jdbc:mysql://127.0.0.1:3306/collsheet username: root password: 5587699$lgg driver-class-name: com.mysql.cj.jdbc.Driver #日记配置 logging: file: path: my-log pattern: console: "%clr(%d{yyyy-MM-dd HH:mm:ss}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(-){faint} %clr([%14.14t]){faint} %clr(%-40.40logger.%13.13M){cyan} %clr(%3.3L) %clr(:){faint} %msg%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}" #redis通道名称 redis.channel: luckysheet.channel #表格中块的大小,新增加时写入每一个sheet的第一个模块中 row_size: 500 col_size: 500 #使用服务器类型 servertype: tomcat
4. 复制class文件
-
-
复制如下一大丢文件至你的项目
-
我全部丢到一个文件夹了。。不太合适。。最好分好类。
-
复制过来后,idea会有报错。。都是import错误。。。删掉这些报错的import错误。。然后交给idea自动import即可。
-
BaseHandle.java BaseModel.java ConfigerService.java ConfigMergeModel.java DataSourceConfig.java DisabledTypeEnum.java GridFileRedisCacheService.java GridRecordDataModel.java GzipHandle.java IpAndPortUtil.java IRecordDataInsertHandle.java IRecordDataUpdataHandle.java IRecordDelHandle.java IRecordSelectHandle.java JdbcTempleConfig.java JfGridConfigModel.java JfGridFileController.java JfGridFileGetService.java JfGridFileUtil.java JfGridUpdateService.java JsonUtil.java LuckySheetGridModel.java MSExcelUtil.java MyStringUtil.java MyURLUtil.java MyWebSocketHandler.java MyWebSocketInterceptor.java OperationTypeEnum.java Pako_GzipUtils.java readme.txt RecordDataInsertHandle.java RecordDataUpdataHandle.java RecordDelHandle.java RecordSelectHandle.java RedisCacheService.java RedisConfig.java RedisLock.java RedisMessageListener.java RedisMessageModel.java RedisMessagePublish.java RedisQueueService.java ScheduleService.java SheetOperationEnum.java SnowFlake.java SysConstant.java TestUtil.java TimeUtil.java WebSocketConfig.java WSUserModel.java
5.使用
- 至此,如果原本的项目可以运行,我们导入的
luckysheet server
项目也可以运行了。什么都不需要做了。 - spring boot会自动导入各种config类,自动启动ws服务等
- 但是还需要如下配置才能实现前端数据保存至数据库。
5.1 使用步骤
-
后端用uuid工具生成一个uuid。
-
用该uuid向luckysheet表插入一条空白记录。插入语句参考如下。这个是初始化语句。目的是为了新建一个空白表格。一条记录表示一个空白sheet页面。
-
luckysheet server
用分块实现sheet的保存。第一个fblock
表示配置块,里面没有数据的。只有颜色等配置。 INSERT INTO luckysheet VALUES (139400313311449087, 'fblock', '', '1', '1079500#-8803#7c45f52b7d01486d88bc53cb17dcd2c3', 1, '{"row":84,"name":"Sheet1","chart":[],"color":"","index":"1","order":0,"column":60,"config":{},"status":0,"celldata":[],"ch_width":4748,"rowsplit":[],"rh_height":1790,"scrollTop":0,"scrollLeft":0,"visibledatarow":[],"visibledatacolumn":[],"jfgird_select_save":[],"jfgrid_selection_range":{}}', 0, 0);
-
传递该uuid到前端
-
前端的luckysheet配置如下
socket_url = "ws://" + window.location.host + "/websocket/luckysheet?t=" + user.name; luckysheetOptions = { container: 'luckysheet', //luckysheet为容器id lang: 'zh', // 设定表格语言 showinfobar: false, allowUpdate: true, updateUrl: socket_url, loadUrl: "/user/load", gridKey: uuid, }
-
注意loadUrl地址的controller文件。。可以根据需要使用里面的方法。里面有导入全部数据、导入sheet等。我修改为如下:(最末尾那一行)
@PostMapping("/load") public String load(HttpServletRequest request, HttpServletResponse response, @RequestParam(defaultValue = "") String gridKey) { //告诉浏览器,当前发送的是gzip格式的内容 response.setHeader("Content-Encoding", "gzip"); response.setContentType("text/html"); String resultStr=""; if(gridKey.trim().length()!=0){ try { String _checkStr=check(request,gridKey.toString(),null,OperationTypeEnum.Read); if(_checkStr.length()>0){ return null; } List<JSONObject> dbObject=null; //dbObject=jfGridFileGetService.getDefaultByGridKey(gridKey); dbObject=jfGridFileGetService.getAllSheetByGridKey(gridKey);
-
至此一切都可以正常使用了。
6 bug处理
- 目前发现这个后台有3个bug。按道理来说不应该。。。也可能是我的导入办法不对。
6.1 无法删除sheet页
-
这个是sql语句有错误。定位到RecordDelHandle.java文件
-
原来的写法
/** * 删除sheet(非物理删除) * * @param model * @return */ @Override public boolean updateDataForReDel(GridRecordDataModel model) { try{ String sql1="update "+ JfGridConfigModel.TABLENAME+" set is_delete=? where list_id=? and index=? "; log.info("updateSql1:"+sql1); luckySheetJdbcTemplate.update(sql1,new Object[]{model.getIs_delete(),model.getList_id(),model.getIndex()}); return true; }catch (Exception e){ log.error(e.getMessage()); return false; } }
-
主要原因是字段名称index在mysql是系统关键词。。直接如此引用必然报错。修改如下(增加表名用t.index代替)
-
/** * 删除sheet(非物理删除) * * @param model * @return */ @Override public boolean updateDataForReDel(GridRecordDataModel model) { try{ String sql1="update "+ JfGridConfigModel.TABLENAME+" t set is_delete=? where list_id=? and t.index=? "; log.info("updateSql1:"+sql1); luckySheetJdbcTemplate.update(sql1,new Object[]{model.getIs_delete(),model.getList_id(),model.getIndex()}); return true; }catch (Exception e){ log.error(e.getMessage()); return false; } }
6.2 无法修改sheet名称
-
定位RecordDataUpdataHandle.java文件137行
if(v instanceof JSON){ updateSql.append("CAST('"+((JSON) v).toJSONString()+"' as JSON)"); }else{ //updateSql.append("\""+v+"\""); updateSql.append(v); }
-
其中v是前端传递过来的sheet名称。按道理官方这么写没错。。但是不懂为什么v自身带有两个双引号。导致该sql语句是 ""v""。多了2个双引号。。所以数据库提示字符错误。
6.3无法修改sheet的行或列的宽度和高度。
-
这个bug事实上有很大的影响。。不仅仅是无法修改行或列。。
-
这个bug没解决6.4的bug也没办法解决。。事实上影响到了所有表格cg( Operation_cg)操作(操作config)。
-
bug没解决前,提示sql语句存在sync错误。
-
定位到RecordDataUpdataHandle.java449行。
-
注释掉的是原本的报错的写法。。更换成如下写法即可。。主要是
JSON_CONTAINS_PATH(t.json_data,'one',\"$."+key+"\")=0"
和`"update "+JfGridConfigModel.TABLENAME+" t 需要指定一个表的别名以及告诉JSON_CONTAINS_PATH函数一个正确的列。 -
同理还有这个类的497行。。一样的错误。。
//String createSql="update "+JfGridConfigModel.TABLENAME+" t set t.json_data=json_set(json_data,\"$."+key+"\",CAST('"+newObj.getString(key)+"' AS JSON)) where 1=1 " +condition+ // " and JSON_CONTAINS_PATH(jsontest,'one',\"$."+key+"\")=0"; String createSql="update "+JfGridConfigModel.TABLENAME+" t set t.json_data=json_set(json_data,\"$."+key+"\",CAST('"+newObj.getString(key)+"' AS JSON)) where 1=1 " +condition+ " and JSON_CONTAINS_PATH(t.json_data,'one',\"$."+key+"\")=0";
6.4 无法添加边框
-
这个bug事实上影响也很大。。不仅仅是无法修改边框。甚至其他涉及config的都无法保存成功。
-
解决及发现这个bug花了数天时间。最终才定位问题所在。我感觉好像是我错了。。。但是按照我这个办法修改后,可以添加边框了。
-
根本原因是因为luckysheet的config类,比如边框、单元格大小等在后台都是用前端发过来的进行替换原本的。比如如下一个正确的config。后面那个json是前端进行更改发送给后端的。
-
注意看如下的config的属性。有的是对象,有的是数组。。后台用fastJson框架进行处理,需要转换为JSONObject或JSONArray对象。。
-
但是涉及到config类的保存方法中,把config的属性的类型定死为JSONObject了。。所以就会提示JSONArray无法转换为JSONObject。。。需要进行相应的判断。。
-
解决办法更加复杂了。。因为涉及到多个方法。。这些方法的参数类型都定死了为JSONObject了。。。。。
-
所以。。最最根本的原因是前端更改接口了!!!!后端没料到会传入JSONArray。
"config": { "columnlen": { "2": 260, "3": 260 }, "borderInfo": [ { "color": "#000", "range": [ { "row": [ 0, 9 ], "top": 0, "left": 0, "width": 73, "column": [ 0, 4 ], "height": 19, "top_move": 0, "left_move": 0, "row_focus": 0, "width_move": 369, "height_move": 199, "column_focus": 0 } ], "style": "1", "rangeType": "range", "borderType": "border-all" } ] }, {"t":"cg","v":{"2":251,"3":203},"i":"1","k":"columnlen"}
-
解决步骤:
JfGridUpdateService.java文件中:1168行 //JSONObject _v = null Object _v = null;//需要替换的值
JfGridUpdateService.java文件中:1220行 //JSONObject _k = JfGridFileUtil.getObjectByObject(_config, k); Object _k = _config.get(k);
IRecordDataUpdataUhanle.java文件中利用多态增加如下一个接口: boolean updateJsonbForSetRootNull(JSONObject query, String word, JSONObject db, Integer position, String words); boolean updateJsonbForSetRootNull(JSONObject query, String word, Object db, Integer position, String words);
RecordDataUpdataUhanle.java文件中利用多态实现接口: public boolean updateJsonbForSetRootNull(JSONObject query, String word, Object db, Integer position, String words) { return updateJsonbForInsertNull(query, word, db, position, words); }
RecordDataUpdataUhanle.java文件中利用多态增加如下方法: 注意在515行处增加如下判断语句: public boolean updateJsonbForInsertNull(JSONObject query, String word, Object db, Integer position, String words) { if (db instanceof JSONObject){ updateSql.append("CAST('"+((JSONObject)db).toString(SerializerFeature.WriteMapNullValue)+"' as JSON)"); } if (db instanceof JSONArray){ updateSql.append("CAST('"+((JSONArray)db).toString(SerializerFeature.WriteMapNullValue)+"' as JSON)"); } updateSql.append(") where 1=1 "+condition); log.info("updateSql:{}",updateSql.toString()); luckySheetJdbcTemplate.update(updateSql.toString(),arr.toArray()); }
-
- 至此,如果原本的项目可以运行,我们导入的
### 6.5前端手动输入单一的单元格数值。会出现某个单元格数值不见了。
- 前端手动输入单一的单元格数值。会出现某个单元格数值不见了。这个单元格可能是之前手动输入的某个数值,也可能是之前用拖动生成的数值。
- 解决方法如下:
- ```java
定位JfGridUpdateService.java 1554行
//_result = recordDataUpdataHandle.updateJsonbForElementInsert(query, "celldata", _db, 0);
_result = recordDataUpdataHandle.updateJsonbForElementInsert(query, "celldata", _db, 999999999);
```
- 原因:这个方法访问如下方法:
- ```java
RecordDataUpdataHandle.updateJsonbForElementInsert(JSONObject query, String word, JSONObject db, Integer position)
```
- 最终都会执行这个sql语句:
- ```sql
update luckysheet t set t.json_data=json_set(t.json_data,"$.celldata[position]",CAST('{"r":0,"c":0,"v":{"ct":{"t":"n","fa":"General"},"v":1346,"m":"1346"}}' as JSON)) where 1=1 and t.list_id="78290eb1-c879-4ae3-9eb3-e11dab05da93" and t.index="1" and t.block_id="0_0"
```
- 如果传入的position为0。就会把celldata[0]的json给替换掉。如果position为999999999。就会插入新数据。
### 6.6 重复登录bug
- 这个不能说是bug。。只能说是考虑问题。。
- 2022年5月23日。。测试的时候发现。。如果一个用户可以在多个网页打开同一个项目。。经常会把该项目搞崩溃。。导致该项目无法访问。所以目前暂时定死了。。一个用户同时只能登录一个luckysheet。。
- ```java
MyWebSocketHandler.java 第52行。
修改该变量为public。
//private static final Hashtable<String, Hashtable<String, WSUserModel>> USER_SOCKET_SESSION_MAP;
public static final Hashtable<String, Hashtable<String, WSUserModel>> USER_SOCKET_SESSION_MAP;
```
- ```java
MyWebSocketInterceptor.java 第46行增加如下4行。只要该方法返回false,就不能连接ws。
如下通过判断是否有token登录。如果有token登录就拒绝登录。
String str = MyWebSocketHandler.USER_SOCKET_SESSION_MAP.toString();
if (str.indexOf(token) > -1){
return false;
}
```
### 6.7 双击表格的某个单元格,进入单元格编辑界面,然后马上退出编辑网页。。然后就不能再次进入这个项目了
- 原因:双击某个单元格的时候,前端向后端发送如下命令
```json
{
"t": "mv",
"i": "1",
"v": {
"op": "enterEdit",
"range": [
{
"left": 148,
"width": 73,
"top": 60,
"height": 19,
"left_move": 148,
"width_move": 73,
"top_move": 60,
"height_move": 19,
"row": [
3,
3
],
"column": [
2,
2
],
"row_focus": 3,
"column_focus": 2
}
]
}
}
```
- 注意,前端发送过来的是一个JSONObject对象。后端接收到这个命令后,什么都不做直接写入数据库
```json
updateSql:update luckysheet t set t.json_data=json_set(t.json_data,"$.jfgird_select_save",CAST('{"op":"enterEdit","range":[{"top_move":0,"top":0,"left":148,"column_focus":2,"width":73,"left_move":148,"column":[2,2],"width_move":73,"row":[0,0],"height_move":19,"row_focus":0,"height":19}]}' as JSON)) where 1=1 and t.list_id='e4777a8b-7d29-43d0-95d6-6b17863e3bdb' and t.index='1' and t.block_id='fblock'
```
- 注意如上sql语句。。把jfgird_select_save这个key对应的值写成了一个对象。。但是前端要求这个是一个数组。。所以前端会报错,,然后就不能进入这个文档了。。
- 简单来说就是某句代码向文档写入了脏数据。。导致前端无法识别这个文档。。然后就无法进入了。
- 解决办法:解决办法就是不写入整个收到的命令。而是提取该命令中的所需要的数据写入数据库。。真正需要的是v.range这个数组。
```java
定位JfGridUpdateService.java第1769行,更换为如下写法:
//Object db = bson.get("v");
Object db_wrap = bson.get("v");
if (!(db_wrap instanceof JSONObject)){
return;
}
Object db = ((JSONObject) db_wrap).get("range");
if (db == null){
return;
}
```
### 6.8 无法保存单元格回车信息
- 如果单元格的内容有回车(excel经常见)的情况下。会出现更新失败。
- 主要原因还是sql语句的符号错误。
- 错误提示如下:
```java
RecordDataUpdataHandle.DataListValue 188 : StatementCallback; SQL [update luckysheet t set t.json_data=json_set(t.json_data,"$.celldata[0].v",CAST('{"ct":{"s":[{"v":"nihao\r\nnihao"}]
MysqlDataTruncation: Data truncation: Invalid JSON text in argument 1 to function cast_as_json: "Invalid encoding in string." at position 23
MyWebSocketHandler.handleMessage 138 : handleUpdate--error:更新失败
```
- 把完整的sql语句复制到mysql中执行。。报的错误是一样的。由此可见,错误的原因是sql语句符号错误。
- 因为仅仅是回车才出现的错误。所以推测是回车的转义问题。
- 注意到`('{"ct":{"s":[{"v":"nihao\r\nnihao"}]`的`\r\n`仅有一个`\`。试着修改为`\\n\\r`。不在提示这个错误。
- 解决办法如下。解决思路就是用字符串替换`\r\n`为`\\r\\n`。注意顺手把"''"也替换为""。否则当单元格有‘的时候一样会报错。
```java
定位RecordDataUpdataHandle.java文件166行。
增加如下几行
//log.info("updateSql:"+updateSql.toString());
//luckySheetJdbcTemplate.update(updateSql.toString());
String v_str = v.toString()
.replace("\\r","\\\\r")
.replace("\\n","\\\\n")
.replace("'","");
v = JSON.parse(v_str);
```
### 6.9 合并单元格无法保存回车信息。
- 这个bug很少出现。
- 我在导入excel文件的时候才发现有这个bug。具体可以看导入excel文件篇。
- 某个单元格无法写入数据,更新失败。原因如上所述。当这个合并单元格有换行的时候。就会出现这个bug。
- 解决方法如下:注释为原本的写法。稍微改进了下。
```java
定位RecordDataUpdataHandle.java文件224行。
//updateSql.append("CAST('"+db.toString(SerializerFeature.WriteMapNullValue)+"' as JSON)");
String db_str = db.toString(SerializerFeature.WriteMapNullValue)
.replace("\\r","\\\\r")
.replace("\\n","\\\\n")
.replace("'","");
updateSql.append("CAST('"+db_str+"' as JSON)");
```
网友评论