@ -1,63 +1,65 @@
@@ -1,63 +1,65 @@
package apelet.common.online.service.impl ;
import apelet.common.core.annotation.MultiDatabaseWriteMethod ;
import apelet.common.core.annotation.MyDataSourceResolver ;
import apelet.common.core.cache.CacheConfig ;
import apelet.common.core.constant.* ;
import apelet.common.core.exception.MyRuntimeException ;
import apelet.common.core.exception.NoDataPermException ;
import apelet.common.core.object.* ;
import apelet.common.core.util.* ;
import apelet.common.datafilter.config.DataFilterProperties ;
import apelet.common.dbutil.constant.DblinkType ;
import apelet.common.dbutil.provider.DataSourceProvider ;
import apelet.common.dict.model.TenantGlobalDict ;
import apelet.common.dict.service.GlobalDictService ;
import apelet.common.dict.service.TenantGlobalDictService ;
import apelet.common.generator.model.OnlFormField ;
import apelet.common.generator.model.OnlFormHead ;
import apelet.common.generator.service.IOnlFormFieldService ;
import apelet.common.generator.service.IOnlFormHeadService ;
import apelet.common.generator.utils.OrmGenDataSourceUtil ;
import apelet.common.online.config.OnlineProperties ;
import apelet.common.online.dao.ConvertRelationFOnLineMapper ;
import apelet.common.online.dao.OnlineOperationMapper ;
import apelet.common.online.dto.OnlineFilterDto ;
import apelet.common.online.dto.OnlinePluginExecuteDto ;
import apelet.common.online.dto.OnlinePluginExecuteSortDto ;
import apelet.common.online.exception.OnlineRuntimeException ;
import apelet.common.online.model.* ;
import apelet.common.online.model.constant.FieldFilterType ;
import apelet.common.online.model.constant.FieldKind ;
import apelet.common.online.model.constant.RelationType ;
import apelet.common.online.model.constant.VirtualType ;
import apelet.common.online.object.ColumnData ;
import apelet.common.online.object.ConstDictInfo ;
import apelet.common.online.object.JoinTableInfo ;
import apelet.common.online.object.TransactionalBusinessData ;
import apelet.common.online.plugin.OperationResult ;
import apelet.common.online.plugin.OperationServicePlugIn ;
import apelet.common.online.plugin.PlugInArgs ;
import apelet.common.online.plugin.pre.DeleteOperationServicePlugIn ;
import apelet.common.online.plugin.pre.SaveOperationServicePlugIn ;
import apelet.common.online.service.* ;
import apelet.common.online.util.* ;
import apelet.common.orm.impl.* ;
import apelet.common.redis.util.CommonRedisUtil ;
import apelet.common.sequence.wrapper.IdGeneratorWrapper ;
import cn.hutool.core.collection.CollUtil ;
import cn.hutool.core.io.FileUtil ;
import cn.hutool.core.lang.Assert ;
import cn.hutool.core.map.MapUtil ;
import cn.hutool.core.text.StrFormatter ;
import cn.hutool.core.util.BooleanUtil ;
import cn.hutool.core.util.EnumUtil ;
import cn.hutool.core.util.ObjectUtil ;
import cn.hutool.core.util.StrUtil ;
import cn.hutool.core.map.MapUtil ;
import cn.hutool.http.HttpUtil ;
import com.alibaba.fastjson.JSON ;
import com.alibaba.fastjson.JSONObject ;
import com.alibaba.fastjson.JSONArray ;
import com.alibaba.fastjson.JSONObject ;
import com.alibaba.fastjson.TypeReference ;
import apelet.common.core.annotation.MultiDatabaseWriteMethod ;
import apelet.common.core.annotation.MyDataSourceResolver ;
import apelet.common.core.cache.CacheConfig ;
import apelet.common.core.constant.* ;
import apelet.common.core.exception.MyRuntimeException ;
import apelet.common.core.exception.NoDataPermException ;
import apelet.common.core.object.* ;
import apelet.common.datafilter.config.DataFilterProperties ;
import apelet.common.dbutil.constant.DblinkType ;
import apelet.common.dbutil.provider.DataSourceProvider ;
import apelet.common.dict.model.TenantGlobalDict ;
import apelet.common.dict.service.GlobalDictService ;
import apelet.common.dict.service.TenantGlobalDictService ;
import apelet.common.online.config.OnlineProperties ;
import apelet.common.online.dao.OnlineOperationMapper ;
import apelet.common.online.dto.OnlineFilterDto ;
import apelet.common.online.exception.OnlineRuntimeException ;
import apelet.common.online.model.* ;
import apelet.common.online.model.constant.FieldFilterType ;
import apelet.common.online.model.constant.* ;
import apelet.common.online.object.ConstDictInfo ;
import apelet.common.online.object.* ;
import apelet.common.online.service.* ;
import apelet.common.online.util.* ;
import apelet.common.orm.impl.EntityColumn ;
import apelet.common.orm.impl.EntityTable ;
import apelet.common.orm.impl.LinkEntityColumn ;
import apelet.common.redis.util.CommonRedisUtil ;
import apelet.common.sequence.wrapper.IdGeneratorWrapper ;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper ;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper ;
import com.google.common.collect.LinkedHashMultimap ;
import com.google.common.collect.Multimap ;
import lombok.Data ;
@ -73,12 +75,9 @@ import org.springframework.transaction.annotation.Transactional;
@@ -73,12 +75,9 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource ;
import java.io.File ;
import java.io.IOException ;
import java.io.Serializable ;
import java.nio.file.Files ;
import java.sql.Connection ;
import java.time.LocalDateTime ;
import java.time.temporal.ValueRange ;
import java.util.* ;
import java.util.stream.Collectors ;
@ -135,6 +134,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -135,6 +134,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
private OnlineOperationService onlineOperationService ;
@Autowired
private IOnlFormFieldService onlFormFieldService ;
@Autowired
private OnlineFileService onlineFileService ;
private static final String DICT_MAP_SUFFIX = "DictMap" ;
private static final String DICT_MAP_LIST_SUFFIX = "DictMapList" ;
@ -197,6 +198,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -197,6 +198,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
}
}
this . saveFileColumn ( table , id , data ) ;
this . nullifyFileColumnValues ( columnDataList , columnValueList ) ;
this . doInsert ( table , columnNames , columnValueList ) ;
// List<String> list = Arrays.asList(columnNames.split(","));
// ObjectValue objectValue = new ObjectValue(table.getTableName());
@ -245,6 +248,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -245,6 +248,8 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
}
// this.doInsert(table, columnNames, columnValueList);
this . saveFileColumn ( table , id , data ) ;
this . nullifyFileColumnValues ( columnDataList , columnValueList ) ;
List < String > column = Arrays . asList ( columnNames . split ( "," ) ) ;
for ( int i = 0 ; i < columnValueList . size ( ) ; i + + ) {
if ( columnValueList . get ( i ) ! = null ) {
@ -371,6 +376,10 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -371,6 +376,10 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
continue ;
}
if ( FieldKind . isFileKind ( columnData . getColumn ( ) . getFieldKind ( ) ) ) {
// 文件字段值已写入 zz_online_file,业务列统一置NULL。
columnData . setColumnValue ( null ) ;
}
if ( ! MyCommonUtil . equalsAny ( columnData . getColumn ( ) . getFieldKind ( ) ,
FieldKind . CREATE_TIME , FieldKind . CREATE_USER_ID , FieldKind . CREATE_DEPT_ID , FieldKind . TENANT_FILTER ) ) {
updateColumnList . add ( columnData ) ;
@ -380,6 +389,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -380,6 +389,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
}
if ( CollUtil . isEmpty ( updateColumnList ) ) {
this . saveFileColumn ( table , this . convertToLongId ( dataId ) , data ) ;
return true ;
}
if ( CollUtil . isNotEmpty ( maskFieldDataList ) ) {
@ -388,7 +398,9 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -388,7 +398,9 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
this . compareAndSetMaskFieldData ( table , maskFieldDataList , originalData ) ;
}
String dataPermFilter = this . buildDataPermFilter ( table ) ;
return this . doUpdate ( table , updateColumnList , filterList , dataPermFilter ) ;
boolean updated = this . doUpdate ( table , updateColumnList , filterList , dataPermFilter ) ;
this . saveFileColumn ( table , this . convertToLongId ( dataId ) , data ) ;
return updated ;
}
@MultiDatabaseWriteMethod
@ -476,6 +488,11 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -476,6 +488,11 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
} else {
this . doLogicDelete ( table , table . getPrimaryKeyColumn ( ) , dataId , dataPermFilter ) ;
}
Long businessId = this . convertToLongId ( dataId ) ;
if ( businessId ! = null ) {
// 级联清理该业务记录对应的附件数据。
onlineFileService . removeByBusinessId ( table . getTableId ( ) , businessId ) ;
}
if ( CollUtil . isEmpty ( relationList ) ) {
return true ;
}
@ -494,11 +511,27 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -494,11 +511,27 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
List < OnlineFilterDto > slaveFilterList =
this . makeDefaultFilter ( relation . getSlaveTable ( ) , slaveColumn , columnValue ) ;
// 先查出从表受影响记录的主键,用于附件清理。
List < Long > slaveBusinessIdList = new LinkedList < > ( ) ;
OnlineColumn slavePkColumn = slaveTable . getPrimaryKeyColumn ( ) ;
if ( slavePkColumn ! = null ) {
List < Map < String , Object > > slaveRows = onlineOperationMapper . getList ( slaveTable . getTableName ( ) , null , slavePkColumn . getColumnName ( ) , slaveFilterList , null , null ) ;
slaveBusinessIdList = slaveRows . stream ( )
. map ( r - > r . get ( slavePkColumn . getColumnName ( ) ) )
. filter ( ObjectUtil : : isNotEmpty )
. map ( v - > this . convertToLongId ( v . toString ( ) ) )
. filter ( Objects : : nonNull )
. collect ( Collectors . toList ( ) ) ;
}
if ( slaveTable . getLogicDeleteColumn ( ) = = null ) {
this . doDelete ( slaveTable , slaveFilterList , null ) ;
} else {
this . doLogicDelete ( slaveTable , slaveColumn , columnValue , null ) ;
}
// 级联清理从表业务记录对应的附件数据。
for ( Long slaveBusinessId : slaveBusinessIdList ) {
onlineFileService . removeByBusinessId ( slaveTable . getTableId ( ) , slaveBusinessId ) ;
}
}
return true ;
}
@ -560,6 +593,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -560,6 +593,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
this . buildVirtualColumn ( resultList , table , toManyRelationList ) ;
}
this . reformatResultListWithOneToOneRelation ( resultList , oneToOneRelationList ) ;
this . fillFileColumn ( resultList , table ) ;
return resultList . get ( 0 ) ;
}
@ -575,6 +609,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -575,6 +609,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
slaveTable , null , selectFields , filterList , dataPermFilter , null , null ) ;
List < Map < String , Object > > resultList = pageData . getDataList ( ) ;
this . buildDataListWithDict ( resultList , slaveTable ) ;
this . fillFileColumn ( resultList , slaveTable ) ;
return CollUtil . isEmpty ( resultList ) ? null : resultList . get ( 0 ) ;
}
@ -608,6 +643,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -608,6 +643,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
this . buildVirtualColumn ( resultList , table , toManyRelationList ) ;
}
this . reformatResultListWithOneToOneRelation ( resultList , oneToOneRelationList ) ;
this . fillFileColumn ( resultList , table ) ;
return pageData ;
}
@ -694,6 +730,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -694,6 +730,7 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
this . buildDataListWithDict ( resultList , slaveTable ) ;
this . reformatResultListWithOneToOneRelation ( resultList , oneToOneRelationList ) ;
reformatResultListWithOneToOneRelation2 ( resultList , slaveTable . getTableName ( ) ) ;
this . fillFileColumn ( resultList , slaveTable ) ;
return pageData ;
}
@ -1021,7 +1058,12 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -1021,7 +1058,12 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
}
OnlineTable onlineTable = onlineTableService . getOnlineTableFromCache ( onlineForm . getMasterTableId ( ) ) ;
OnlFormHead onlFormHead = onlFormHeadService . getHeadTableNameCache ( onlineTable . getTableName ( ) ) ;
// executePlugin 是所有按钮的统一入口,附件的写入/清理必须按插件类型分别设闸,
// 否则「审核」这类只提交部分字段的按钮会把附件全量覆盖掉,「删除」则不会清理附件。
boolean saveOperation = this . containsPlugin ( sortDtoList , SaveOperationServicePlugIn . class ) ;
boolean deleteOperation = this . containsPlugin ( sortDtoList , DeleteOperationServicePlugIn . class ) ;
ObjectValue finalObjectValue = null ;
List < PendingFileColumn > pendingFileList = new LinkedList < > ( ) ;
if ( onlFormHead . getIsomerismMark ( ) ! = null ) {
finalObjectValue = this . cntEasValue ( onlinePluginExecuteDto ,
masterData ,
@ -1031,12 +1073,294 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -1031,12 +1073,294 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
} else {
if ( masterData ! = null ) {
finalObjectValue = onlineFormService . getObjectValue ( masterData , slaveData , onlineTable . getTableName ( ) ) ;
if ( saveOperation ) {
// 附件上传/图片上传字段:先摘出入参值并把业务列置NULL,等ORM保存回写主键后再落 zz_online_file。
pendingFileList = this . extractFileColumn ( finalObjectValue , onlineTable , slaveData ) ;
}
}
}
List < OnlinePluginExecuteSortDto > list
= sortDtoList . stream ( ) . sorted ( Comparator . comparing ( OnlinePluginExecuteSortDto : : getOrder ) ) . collect ( Collectors . toList ( ) ) ;
return exePlugin ( list , onlinePluginExecuteDto . getBillId ( ) , onlineTable . getTableName ( ) , finalObjectValue , onlineForm ) ;
// 删除会被ORM级联删掉分录行,附件归属的分录主键必须在删除前查出来,删完就查不到了。
Map < Long , List < Long > > removeFileBusinessIdMap = new LinkedHashMap < > ( ) ;
if ( deleteOperation & & onlFormHead . getIsomerismMark ( ) = = null ) {
removeFileBusinessIdMap = this . collectFileBusinessIdForRemove ( onlineTable , onlinePluginExecuteDto . getBillId ( ) ) ;
}
OperationResult operationResult = exePlugin ( list , onlinePluginExecuteDto . getBillId ( ) , onlineTable . getTableName ( ) , finalObjectValue , onlineForm ) ;
// ORM保存会把生成的主键回写到入参的ObjectValue实例上,此时才能确定附件归属的业务主键。
this . flushPendingFileColumn ( pendingFileList ) ;
this . removeFileByBusinessIdMap ( removeFileBusinessIdMap ) ;
return operationResult ;
}
/ * *
* 判断当前按钮绑定的插件中是否包含指定插件 。
* < p > 按钮与插件的绑定是 widgetJson 中 pluginList [ ] . class 的全限定类名 ( 见 QuickFormWidgetUtil # setSaveButtonEnabled ) ,
* 用类名判断比用中文 buttonName 可靠 。 < / p >
* /
private boolean containsPlugin ( List < OnlinePluginExecuteSortDto > sortDtoList , Class < ? > pluginClass ) {
if ( CollUtil . isEmpty ( sortDtoList ) ) {
return false ;
}
return sortDtoList . stream ( ) . anyMatch ( dto - > pluginClass . getName ( ) . equals ( dto . getClassPath ( ) ) ) ;
}
/ * *
* 删除前收集待清理附件的业务主键 。
* < p > 表头主键取自billId ; 分录主键必须在删除前查出 , 因为ORM删除表头时会级联删除分录行 ,
* 删除后再查就拿不到 , 附件会在 zz_online_file 里变成孤儿记录 。 < / p >
*
* @param masterTable 表头对应的在线数据表 。
* @param billId 表头主键 , 批量删除时为逗号分隔的多个主键 。
* @return 在线表tableId - > 该表下待清理附件的业务主键列表 。
* /
private Map < Long , List < Long > > collectFileBusinessIdForRemove ( OnlineTable masterTable , String billId ) {
Map < Long , List < Long > > businessIdMap = new LinkedHashMap < > ( ) ;
if ( masterTable = = null | | StrUtil . isBlank ( billId ) ) {
return businessIdMap ;
}
List < Long > masterIdList = new LinkedList < > ( ) ;
for ( String idStr : StrUtil . split ( billId , ',' ) ) {
Long businessId = this . convertToLongId ( StrUtil . trim ( idStr ) ) ;
if ( businessId ! = null ) {
masterIdList . add ( businessId ) ;
}
}
if ( CollUtil . isEmpty ( masterIdList ) ) {
return businessIdMap ;
}
businessIdMap . put ( masterTable . getTableId ( ) , masterIdList ) ;
try {
this . collectEntryBusinessIdForRemove ( masterTable , masterIdList , businessIdMap ) ;
} catch ( Exception e ) {
// 分录主键查询失败不影响表头附件清理,仅记录日志。
log . error ( "删除前查询分录主键失败,分录附件可能残留,table={}, billId={}" , masterTable . getTableName ( ) , billId , e ) ;
}
return businessIdMap ;
}
/ * *
* 从ORM实体元数据中找出分录表 ( relationType = 1 ) , 按表头主键查出各分录行主键 。
* /
private void collectEntryBusinessIdForRemove ( OnlineTable masterTable , List < Long > masterIdList , Map < Long , List < Long > > businessIdMap ) {
EntityTable entityTable = ormGenDataSourceUtil . getEntityTable ( masterTable . getTableName ( ) ) ;
if ( entityTable = = null | | CollUtil . isEmpty ( entityTable . getEntityColumnList ( ) ) ) {
return ;
}
// 分录表名 -> 在线表tableId,用于把查询结果里的分录行归到对应的在线表。
Map < String , Long > entryTableIdMap = new LinkedHashMap < > ( ) ;
Selector selector = new Selector ( ) ;
selector . getList ( ) . add ( new SelectorItem ( "id" ) ) ;
for ( EntityColumn entityColumn : entityTable . getEntityColumnList ( ) ) {
if ( ! ( entityColumn instanceof LinkEntityColumn ) ) {
continue ;
}
EntityLinkRelation relation = ( ( LinkEntityColumn ) entityColumn ) . getEntityLinkRelation ( ) ;
if ( relation = = null | | relation . getRelationType ( ) = = null | | ! relation . getRelationType ( ) . equals ( 1 ) ) {
continue ;
}
String entryTableName = relation . getSuperTable ( ) . getTableName ( ) ;
OnlineTable entryTable = onlineTableService . getOne ( new LambdaQueryWrapper < OnlineTable > ( ) . eq ( OnlineTable : : getTableName , entryTableName ) ) ;
if ( entryTable = = null ) {
continue ;
}
entryTableIdMap . put ( entryTableName , entryTable . getTableId ( ) ) ;
// 只取分录主键,不需要其它字段。
selector . getList ( ) . add ( new SelectorItem ( relation . getRelationName ( ) + ".id" ) ) ;
}
if ( MapUtil . isEmpty ( entryTableIdMap ) ) {
return ;
}
Filter filter = new Filter ( ) ;
filter . add ( new FilterItem ( "id" , FilterItem . INNER ,
masterIdList . stream ( ) . map ( id - > "'" + id + "'" ) . collect ( Collectors . joining ( "," ) ) ) ) ;
ObjectCollection collection = ormGenDataSourceUtil . query ( masterTable . getTableName ( ) , filter , selector ) ;
for ( int i = 0 ; i < collection . size ( ) ; i + + ) {
Map < String , Object > rowValues = collection . getObject ( i ) . getValues ( ) ;
if ( MapUtil . isEmpty ( rowValues ) ) {
continue ;
}
for ( Object value : rowValues . values ( ) ) {
if ( ! ( value instanceof ObjectCollection ) ) {
continue ;
}
ObjectCollection entryCollection = ( ObjectCollection ) value ;
for ( int j = 0 ; j < entryCollection . size ( ) ; j + + ) {
ObjectValue entryRow = entryCollection . getObject ( j ) ;
// 分录行自带表名,按表名归类比猜ObjectValue上挂载的key名可靠。
Long entryTableId = entryTableIdMap . get ( entryRow . getTableName ( ) ) ;
if ( entryTableId = = null ) {
continue ;
}
Object entryIdValue = entryRow . get ( "id" ) ;
Long entryId = entryIdValue = = null ? null : this . convertToLongId ( entryIdValue . toString ( ) ) ;
if ( entryId ! = null ) {
businessIdMap . computeIfAbsent ( entryTableId , k - > new LinkedList < > ( ) ) . add ( entryId ) ;
}
}
}
}
}
/ * *
* 按收集到的业务主键清理 zz_online_file 中的附件数据 。
* /
private void removeFileByBusinessIdMap ( Map < Long , List < Long > > businessIdMap ) {
if ( MapUtil . isEmpty ( businessIdMap ) ) {
return ;
}
businessIdMap . forEach ( ( onlineTableId , businessIdList ) - > {
for ( Long businessId : businessIdList ) {
onlineFileService . removeByBusinessId ( onlineTableId , businessId ) ;
}
} ) ;
}
/ * *
* 附件字段暂存对象 。
* < p > 持有表头 / 分录行的ObjectValue实例引用 : 新增时主键由ORM在保存过程中生成并回写到该实例上 ,
* 保存前拿不到主键 , 只能保存后按实例读回 。 < / p >
* /
private static class PendingFileColumn {
/** 附件所属的行对象,ORM保存后主键会回写到该实例上。 */
private final ObjectValue row ;
/** 行所属的在线数据表。 */
private final OnlineTable table ;
/** 附件字段的列名。 */
private final String columnName ;
/** 入参中该字段的文件JSON数组字符串,为空表示清空该字段附件。 */
private final String fileJson ;
private PendingFileColumn ( ObjectValue row , OnlineTable table , String columnName , String fileJson ) {
this . row = row ;
this . table = table ;
this . columnName = columnName ;
this . fileJson = fileJson ;
}
}
/ * *
* 摘出表头及各分录中附件上传 / 图片上传字段的入参值 , 并把业务列统一置NULL 。
* < p > 按全量覆盖语义 : 表中所有 field_kind 为 1 / 2 的字段一律以入参为准 , 入参未包含该字段视为清空 。 < / p >
*
* @param objectValue 待保存的表头对象 , 分录以分录表名为key挂在其上 。
* @param masterTable 表头对应的在线数据表 。
* @param slaveData 分录入参 , key为分录表的tableId 。
* @return 待落库的附件字段列表 。
* /
private List < PendingFileColumn > extractFileColumn ( ObjectValue objectValue , OnlineTable masterTable , JSONObject slaveData ) {
List < PendingFileColumn > pendingList = new LinkedList < > ( ) ;
if ( objectValue = = null ) {
return pendingList ;
}
this . extractFileColumnFromRow ( objectValue , masterTable , pendingList ) ;
if ( slaveData = = null ) {
return pendingList ;
}
// 分录:slaveData的key为分录表tableId,ObjectValue上以分录表名挂ObjectCollection(见OnlineFormServiceImpl#getObjectValue)。
for ( String tableIdStr : slaveData . keySet ( ) ) {
Long entryTableId = this . convertToLongId ( tableIdStr ) ;
if ( entryTableId = = null ) {
continue ;
}
OnlineTable entryTable = onlineTableService . getOnlineTableFromCache ( entryTableId ) ;
if ( entryTable = = null ) {
continue ;
}
Object collectionObj = objectValue . get ( entryTable . getTableName ( ) ) ;
if ( ! ( collectionObj instanceof ObjectCollection ) ) {
continue ;
}
ObjectCollection collection = ( ObjectCollection ) collectionObj ;
for ( int i = 0 ; i < collection . size ( ) ; i + + ) {
this . extractFileColumnFromRow ( collection . getObject ( i ) , entryTable , pendingList ) ;
}
}
return pendingList ;
}
/ * *
* 摘出单行中所有附件字段的值 , 并把该行对应列置NULL 。
* /
private void extractFileColumnFromRow ( ObjectValue row , OnlineTable table , List < PendingFileColumn > pendingList ) {
if ( row = = null | | table = = null | | MapUtil . isEmpty ( table . getColumnMap ( ) ) ) {
return ;
}
for ( OnlineColumn column : table . getColumnMap ( ) . values ( ) ) {
if ( ! FieldKind . isFileKind ( column . getFieldKind ( ) ) ) {
continue ;
}
String columnName = column . getColumnName ( ) ;
Object value = row . get ( columnName ) ;
pendingList . add ( new PendingFileColumn ( row , table , columnName , value = = null ? null : value . toString ( ) ) ) ;
// 业务列仅作查询回填载体,不落库,统一置NULL。
row . put ( columnName , null ) ;
}
}
/ * *
* 保存完成后把暂存的附件数据落 zz_online_file , 业务主键从ORM回写后的行对象上读取 。
* /
private void flushPendingFileColumn ( List < PendingFileColumn > pendingList ) {
if ( CollUtil . isEmpty ( pendingList ) ) {
return ;
}
for ( PendingFileColumn pending : pendingList ) {
OnlineColumn primaryKeyColumn = pending . table . getPrimaryKeyColumn ( ) ;
if ( primaryKeyColumn = = null ) {
log . warn ( "附件落库失败,表 [{}] 未配置主键字段" , pending . table . getTableName ( ) ) ;
continue ;
}
Object idValue = pending . row . get ( primaryKeyColumn . getColumnName ( ) ) ;
Long businessId = idValue = = null ? null : this . convertToLongId ( idValue . toString ( ) ) ;
if ( businessId = = null ) {
log . warn ( "附件落库失败,保存后未取到业务主键,table={}, column={}" , pending . table . getTableName ( ) , pending . columnName ) ;
continue ;
}
List < OnlineFile > fileList = this . parseOnlineFileList ( pending . fileJson , pending . columnName ) ;
onlineFileService . replaceFileList ( pending . table . getTableId ( ) , pending . columnName , businessId , fileList ) ;
}
}
/ * *
* 把文件字段的JSON数组字符串解析为 OnlineFile 列表 。
* < p > 值为空或解析失败时返回空列表 , 即按清空该字段附件处理 。 < / p >
* /
private List < OnlineFile > parseOnlineFileList ( String fileJson , String columnName ) {
List < OnlineFile > fileList = new LinkedList < > ( ) ;
if ( StrUtil . isBlank ( fileJson ) ) {
return fileList ;
}
JSONArray fileArray ;
try {
fileArray = JSON . parseArray ( fileJson ) ;
} catch ( Exception e ) {
log . warn ( "解析文件字段 [{}] 数据失败,按清空处理,原值={}" , columnName , fileJson ) ;
return fileList ;
}
if ( fileArray = = null ) {
return fileList ;
}
TokenData tokenData = TokenData . takeFromRequest ( ) ;
Long operatorUserId = tokenData = = null ? null : tokenData . getUserId ( ) ;
for ( int i = 0 ; i < fileArray . size ( ) ; i + + ) {
JSONObject fileItem = fileArray . getJSONObject ( i ) ;
if ( fileItem = = null ) {
continue ;
}
OnlineFile onlineFile = new OnlineFile ( ) ;
onlineFile . setBaseUrl ( fileItem . getString ( "baseUrl" ) ) ;
onlineFile . setDownloadUri ( fileItem . getString ( "downloadUri" ) ) ;
onlineFile . setFileName ( fileItem . getString ( "filename" ) ) ;
onlineFile . setUploadPath ( fileItem . getString ( "uploadPath" ) ) ;
onlineFile . setName ( fileItem . getString ( "name" ) ) ;
onlineFile . setUrl ( fileItem . getString ( "url" ) ) ;
onlineFile . setCreateUserId ( operatorUserId ) ;
onlineFile . setUpdateUserId ( operatorUserId ) ;
fileList . add ( onlineFile ) ;
}
return fileList ;
}
@ -2203,6 +2527,152 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
@@ -2203,6 +2527,152 @@ public class OnlineOperationServiceImpl implements OnlineOperationService {
return sb . substring ( 0 , sb . length ( ) - 1 ) ;
}
/ * *
* 处理附件上传 / 图片上传字段 : 将入参中的文件JSON数组解析后存入 zz_online_file , 并把业务字段置空 。
* < p > 约定 : 入参未包含该字段或值为null时保留原附件数据 ; 空数组时清空该字段的全部附件 。 < / p >
*
* @param table 在线数据表对象 。
* @param id 业务记录主键值 。
* @param data 入参数据 。
* /
private void saveFileColumn ( OnlineTable table , Object id , JSONObject data ) {
if ( id = = null ) {
return ;
}
Long businessId = this . convertToLongId ( id . toString ( ) ) ;
if ( businessId = = null ) {
return ;
}
for ( OnlineColumn column : table . getColumnMap ( ) . values ( ) ) {
if ( ! FieldKind . isFileKind ( column . getFieldKind ( ) ) ) {
continue ;
}
String columnName = column . getColumnName ( ) ;
// 入参没有该字段时不做处理,保留原有附件。
if ( ! data . containsKey ( columnName ) | | data . get ( columnName ) = = null ) {
continue ;
}
JSONArray fileArray ;
try {
fileArray = JSON . parseArray ( data . getString ( columnName ) ) ;
} catch ( Exception e ) {
// 解析失败说明入参格式异常,保留原有附件,不做清理。
log . warn ( "解析文件字段 [{}] 数据失败,保留原有附件" , columnName ) ;
continue ;
}
List < OnlineFile > fileList = new LinkedList < > ( ) ;
if ( fileArray ! = null ) {
for ( int i = 0 ; i < fileArray . size ( ) ; i + + ) {
OnlineFile onlineFile = new OnlineFile ( ) ;
JSONObject fileJson = fileArray . getJSONObject ( i ) ;
onlineFile . setBaseUrl ( fileJson . getString ( "baseUrl" ) ) ;
onlineFile . setDownloadUri ( fileJson . getString ( "downloadUri" ) ) ;
onlineFile . setFileName ( fileJson . getString ( "filename" ) ) ;
onlineFile . setUploadPath ( fileJson . getString ( "uploadPath" ) ) ;
onlineFile . setName ( fileJson . getString ( "name" ) ) ;
onlineFile . setUrl ( fileJson . getString ( "url" ) ) ;
onlineFile . setCreateUserId ( TokenData . takeFromRequest ( ) . getUserId ( ) ) ;
onlineFile . setUpdateUserId ( TokenData . takeFromRequest ( ) . getUserId ( ) ) ;
fileList . add ( onlineFile ) ;
}
}
// 全量替换:先删旧再插入,空数组即清空。
onlineFileService . replaceFileList ( table . getTableId ( ) , columnName , businessId , fileList ) ;
// 业务表字段仅作为查询回填载体,不落库,统一置NULL。
data . put ( columnName , null ) ;
}
}
/ * *
* 查询结果回填 : 将附件上传 / 图片上传字段对应的 zz_online_file 数据组装成文件JSON数组回填到字段上 。
*
* @param resultList 查询结果列表 。
* @param table 在线数据表对象 。
* /
@Override
public void fillFileColumn ( List < Map < String , Object > > resultList , OnlineTable table ) {
if ( CollUtil . isEmpty ( resultList ) ) {
return ;
}
List < OnlineColumn > fileColumnList = table . getColumnMap ( ) . values ( ) . stream ( )
. filter ( c - > FieldKind . isFileKind ( c . getFieldKind ( ) ) )
. collect ( Collectors . toList ( ) ) ;
if ( CollUtil . isEmpty ( fileColumnList ) ) {
return ;
}
OnlineColumn primaryKeyColumn = table . getPrimaryKeyColumn ( ) ;
if ( primaryKeyColumn = = null ) {
return ;
}
List < Long > businessIdList = resultList . stream ( )
. map ( r - > r . get ( primaryKeyColumn . getColumnName ( ) ) )
. filter ( ObjectUtil : : isNotEmpty )
. map ( v - > this . convertToLongId ( v . toString ( ) ) )
. filter ( Objects : : nonNull )
. distinct ( )
. collect ( Collectors . toList ( ) ) ;
for ( OnlineColumn column : fileColumnList ) {
Map < Long , List < OnlineFile > > fileMap =
onlineFileService . getOnlineFileMapByBusinessIds ( table . getTableId ( ) , column . getColumnName ( ) , businessIdList ) ;
if ( MapUtil . isEmpty ( fileMap ) ) {
continue ;
}
for ( Map < String , Object > result : resultList ) {
Object idValue = result . get ( primaryKeyColumn . getColumnName ( ) ) ;
if ( ObjectUtil . isEmpty ( idValue ) ) {
continue ;
}
Long businessId = this . convertToLongId ( idValue . toString ( ) ) ;
List < OnlineFile > fileList = fileMap . get ( businessId ) ;
if ( CollUtil . isEmpty ( fileList ) ) {
continue ;
}
JSONArray fileArray = new JSONArray ( ) ;
for ( OnlineFile onlineFile : fileList ) {
JSONObject fileJson = new JSONObject ( ) ;
fileJson . put ( "baseUrl" , onlineFile . getBaseUrl ( ) ) ;
fileJson . put ( "downloadUri" , onlineFile . getDownloadUri ( ) ) ;
fileJson . put ( "filename" , onlineFile . getFileName ( ) ) ;
fileJson . put ( "uploadPath" , onlineFile . getUploadPath ( ) ) ;
fileJson . put ( "name" , onlineFile . getName ( ) ) ;
fileJson . put ( "url" , onlineFile . getUrl ( ) ) ;
fileJson . put ( "uploadFailed" , false ) ;
fileArray . add ( fileJson ) ;
}
result . put ( column . getColumnName ( ) , fileArray ) ;
}
}
}
/ * *
* 将文件字段的值在insert / update的值列表中统一置NULL , 避免文件JSON写入业务表 。
* < p > columnValueList与columnDataList对齐 ( 均跳过自增列 ) 。 < / p >
* /
private void nullifyFileColumnValues ( List < ColumnData > columnDataList , List < Object > columnValueList ) {
int valueIndex = 0 ;
for ( ColumnData columnData : columnDataList ) {
if ( BooleanUtil . isTrue ( columnData . getColumn ( ) . getAutoIncrement ( ) ) ) {
continue ;
}
if ( FieldKind . isFileKind ( columnData . getColumn ( ) . getFieldKind ( ) ) ) {
columnData . setColumnValue ( null ) ;
columnValueList . set ( valueIndex , null ) ;
}
valueIndex + + ;
}
}
private Long convertToLongId ( String dataId ) {
if ( StrUtil . isBlank ( dataId ) ) {
return null ;
}
try {
return Long . valueOf ( dataId ) ;
} catch ( NumberFormatException e ) {
return null ;
}
}
private void makeupColumnValue ( ColumnData columnData ) {
if ( BooleanUtil . isTrue ( columnData . getColumn ( ) . getAutoIncrement ( ) ) ) {
return ;