/* ================================================================== * Created [2016-3-5] by Jon.King * ================================================================== * TSS * ================================================================== * mailTo:[email protected] * Copyright (c) boubei.com, 2015-2018 * ================================================================== */ package com.boubei.tss.dm.record; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.codehaus.jackson.map.ObjectMapper; import org.dom4j.Document; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import com.boubei.tss.EX; import com.boubei.tss.cache.extension.CacheHelper; import com.boubei.tss.dm.DMConstants; import com.boubei.tss.dm.DMUtil; import com.boubei.tss.dm.DataExport; import com.boubei.tss.dm.ddl._Database; import com.boubei.tss.dm.ddl._Field; import com.boubei.tss.dm.dml.MultiSQLExcutor; import com.boubei.tss.dm.dml.SQLExcutor; import com.boubei.tss.dm.record.file.RecordAttach; import com.boubei.tss.dm.record.permission.RecordPermission; import com.boubei.tss.dm.record.permission.RecordResource; import com.boubei.tss.dm.record.workflow.WFService; import com.boubei.tss.dm.record.workflow.WFStatus; import com.boubei.tss.dm.record.workflow.WFUtil; import com.boubei.tss.dm.report.log.AccessLogRecorder; import com.boubei.tss.framework.Config; import com.boubei.tss.framework.SecurityUtil; import com.boubei.tss.framework.exception.BusinessException; import com.boubei.tss.framework.exception.ExceptionEncoder; import com.boubei.tss.framework.persistence.ICommonService; import com.boubei.tss.framework.persistence.pagequery.PageInfo; import com.boubei.tss.framework.sso.Environment; import com.boubei.tss.framework.web.display.grid.DefaultGridNode; import com.boubei.tss.framework.web.display.grid.GridDataEncoder; import com.boubei.tss.framework.web.display.grid.IGridNode; import com.boubei.tss.framework.web.display.tree.DefaultTreeNode; import com.boubei.tss.framework.web.display.tree.ITreeNode; import com.boubei.tss.framework.web.display.tree.TreeEncoder; import com.boubei.tss.framework.web.filter.Filter8APITokenCheck; import com.boubei.tss.framework.web.mvc.ProgressActionSupport; import com.boubei.tss.modules.HitRateManager; import com.boubei.tss.modules.log.BusinessLogger; import com.boubei.tss.um.permission.PermissionHelper; import com.boubei.tss.um.service.ILoginService; import com.boubei.tss.util.DateUtil; import com.boubei.tss.util.EasyUtils; import com.boubei.tss.util.FileHelper; @Controller @RequestMapping({ "/auth/xdata", "/xdata/api", "/xdata" }) public class _Recorder extends ProgressActionSupport { public static final int PAGE_SIZE = 50; @Autowired RecordService recordService; @Autowired WFService wfService; @Autowired ILoginService loginSerivce; @Autowired ICommonService commService; _Database getDB(Long recordId, String... permitOptions) { // 检测当前用户对当前录入表是否有指定的操作权限 boolean flag = permitOptions.length == 0; for (String permitOption : permitOptions) { flag = flag || checkPermission(recordId, permitOption); } if (!flag) { throw new BusinessException(EX.parse(EX.DM_09, recordId, Arrays.asList(permitOptions).toString())); } _Database _db = recordService.getDB(recordId); // TODO 可按 recordId + domain 进行缓存 CacheHelper.getLongCache().putObject("_db_record_" + recordId, _db); return _db; } @RequestMapping("/id") @ResponseBody public List<Long> getRecordIDs(String nameOrTables) { String[] list = nameOrTables.split(","); List<Long> idList = new ArrayList<Long>(); for (String nt : list) { Long id = recordService.getRecordID(nt, Record.TYPE1, false); idList.add(id); } return idList; } @RequestMapping("/define/{record}") @ResponseBody public Object getDefine(HttpServletRequest request, @PathVariable("record") Object record) { Long recordId = recordService.getRecordID(record, false); prepareParams(request, recordId); Record _record = recordService.getRecord(recordId); if (!_record.isActive()) { throw new BusinessException(EX.DM_10); } _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_EDATA, Record.OPERATION_VDATA); return new Object[] { _db.getFields(), _record.getCustomizeJS(), _record.getCustomizeGrid(), _record.getNeedFile(), _record.getBatchImp(), _record.getName(), _record.getCustomizePage(), _db.getVisiableFields(false), WFUtil.checkWorkFlow(_record.getWorkflow()), _db.isLogicDelete(), _record.getTable() }; } public Map<String, String> prepareParams(HttpServletRequest request, Long recordId) { Map<String, String> requestMap = DMUtil.parseRequestParams(request, false); /* 其它系统调用接口时,传入其在TSS注册的用户ID; 检查令牌,令牌有效则自动完成登陆 */ if (recordId > 0) { Record record = recordService.getRecord(recordId); Filter8APITokenCheck.checkAPIToken(request, record); } /* 当recordId < 0 时,通常是为定制的表(比如万马的员工表)虚拟一张录入表,用以保存附件 */ return requestMap; } @RequestMapping("/xml/{record}/{page}") public void showAsGrid(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("page") int page) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); boolean pointedFileds = requestMap.containsKey("fields"); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_VDATA, Record.OPERATION_EDATA); // 默认模糊查询 String strictQuery = (String) EasyUtils.checkNull(requestMap.get(_Field.STRICT_QUERY), "false"); requestMap.put(_Field.STRICT_QUERY, strictQuery); SQLExcutor ex = queryRecordData(_db, page, PAGE_SIZE, requestMap, pointedFileds, true); List<IGridNode> temp = new ArrayList<IGridNode>(); for (Map<String, Object> item : ex.result) { DefaultGridNode gridNode = new DefaultGridNode(); gridNode.getAttrs().putAll(item); temp.add(gridNode); } Document gridTemplate = pointedFileds ? ex.getGridTemplate(_db.cnm, _db.ctype, _db.cpattern) : _db.getGridTemplate(); GridDataEncoder gEncoder = new GridDataEncoder(temp, gridTemplate); PageInfo pageInfo = new PageInfo(); pageInfo.setPageSize(PAGE_SIZE); pageInfo.setTotalRows(ex.count); pageInfo.setPageNum(page); print(new String[] { "RecordData", "PageInfo" }, new Object[] { gEncoder, pageInfo }); } private SQLExcutor queryRecordData(_Database _db, int page, int pagesize, Map<String, String> requestMap, boolean pointedFileds, boolean isTssGrid) { SQLExcutor ex; boolean isWFQuery = requestMap.remove("my_wf_list") != null; if (isWFQuery) { ex = wfService.queryMyTasks(_db, requestMap, page, pagesize); } else { ex = _db.select(page, pagesize, requestMap); /* 添加工作流信息 */ wfService.fixWFStatus(_db, ex.result); } if (pointedFileds && !ex.selectFields.contains("id") ) { return ex; } List<Long> itemIDs = new ArrayList<Long>(); for (Map<String, Object> item : ex.result) { itemIDs.add( (Long) item.get("id") ); } Map<String, String> usersMap = loginSerivce.getUsersMap(); /* 读取记录的附件信息 */ Map<Object, Object> itemAttach = new HashMap<Object, Object>(); if ( _db.needFile && itemIDs.size() > 0 ) { String sql = "select itemId item, group_concat(id ORDER BY id ASC) ids from dm_record_attach " + " where recordId = ? and itemId in (" +EasyUtils.list2Str(itemIDs)+ ") " + " group by itemId"; List<Map<String, Object>> attachResult = SQLExcutor.queryL(sql, _db.recordId); for (Map<String, Object> temp : attachResult) { itemAttach.put(temp.get("item").toString(), temp.get("ids")); } } for (Map<String, Object> item : ex.result) { // 把附件字段替换为链接 int index = 0; for (String field : _db.fieldCodes) { boolean isFileField = _Field.TYPE_FILE.equals(_db.fieldTypes.get(index++)); if (isFileField) { String[] values = EasyUtils.obj2String(item.get(field)).split(","); String urls = ""; List<String> ids = new ArrayList<>(); for (String value : values) { int splitIndex = value.indexOf("#"); if (splitIndex < 0) continue; String name = value.substring(0, splitIndex); String id = value.substring(splitIndex + 1); String downloadUrl = "/tss/xdata/attach/download/" + id; urls += "<a href='" + downloadUrl + "' target='_blank'>" + name + "</a>  "; ids.add(id); } item.put(field, urls); item.put(field + "_ids", EasyUtils.list2Str(ids)); } } Object itemId = item.get("id").toString(); String fileIds = (String) itemAttach.get(itemId); int fileCount = fileIds == null ? 0 : fileIds.split(",").length; if( isTssGrid || requestMap.containsKey("showAttach") ) { String onclick = "manageAttach(" +itemId+ ", " +_db.recordId+ ")"; Object attachTag = fileCount > 0 ? fileCount : (isWFQuery ? "0" : "上传"); item.put("fileNum", "<a href='javascript:void(0)' onclick='" +onclick+ "'>" + attachTag + "</a>"); item.put("fileIds", fileIds); } item.put("_fileNum", EasyUtils.obj2Int(fileCount)); Object account = item.get("creator"); item.put( "creatorName", EasyUtils.checkNull(usersMap.get( account ), account) ); } return ex; } @RequestMapping("/json/{record}/{page}") @ResponseBody public Object showAsJSON(HttpServletRequest request, @PathVariable("record") Object record, @PathVariable("page") int page) { // 如果同时传递了录入表名,优先用之(适合中文名表名) String recordName = request.getParameter("recordName"); if (!EasyUtils.isNullOrEmpty(recordName)) { record = recordName; } Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); boolean pointedFileds = requestMap.containsKey("fields"); int _pagesize = getPageSize(requestMap, PAGE_SIZE * 20); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_VDATA, Record.OPERATION_EDATA); // 默认模糊查询(EasyUI网页上需要模糊查询) String strictQuery = (String) EasyUtils.checkNull(requestMap.get(_Field.STRICT_QUERY), "false"); requestMap.put(_Field.STRICT_QUERY, strictQuery); SQLExcutor ex = queryRecordData(_db, page, _pagesize, requestMap, pointedFileds, false); if(requestMap.containsKey("debugSQL")) { // 调试SQL解析 return ex.sql.replaceAll("\n", " "); } if (requestMap.containsKey("rows")) { // for EasyUI Map<String, Object> returlVal = new HashMap<String, Object>(); returlVal.put("total", ex.count); returlVal.put("rows", ex.result); return returlVal; } return ex.result; } /** EasyUI 翻页调用此处接口 */ @RequestMapping("/json/{record}") @ResponseBody public Object showAsJSON(HttpServletRequest request, @PathVariable("record") String record) { Object page = EasyUtils.checkNull(request.getParameter("page"), 1); return showAsJSON(request, record, EasyUtils.obj2Int(page)); } private int getPageSize(Map<String, String> m, int defaultSize) { Object pagesize = EasyUtils.checkNull(m.get("pagesize"), m.get("rows"), defaultSize); return EasyUtils.obj2Int(pagesize); } @RequestMapping("/export/{record}") public void export(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record) { Map<String, String> requestMap = DMUtil.parseRequestParams(request, true); // GET Method Request boolean pointed = requestMap.containsKey("fields"); requestMap.put("export", "true"); Long recordId = recordService.getRecordID(record, true); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_VDATA); int _page = EasyUtils.obj2Int(EasyUtils.checkNull(requestMap.get("page"), "1")); int _pagesize = getPageSize(requestMap, 10 * 10000); // 默认模糊查询 String strictQuery = (String) EasyUtils.checkNull(requestMap.get(_Field.STRICT_QUERY), "false"); requestMap.put(_Field.STRICT_QUERY, strictQuery); SQLExcutor ex; boolean isWFQuery = "true".equals(requestMap.remove("my_wf_list")); if (isWFQuery && !pointed) { ex = wfService.queryMyTasks(_db, requestMap, _page, _pagesize); } else { ex = _db.select(_page, _pagesize, requestMap); /* 添加工作流信息 */ wfService.fixWFStatus(_db, ex.result); } List<Map<String, Object>> result = ex.result; String fileName = DateUtil.format(new Date()) + "_" + recordId + Environment.getUserId() + ".csv"; for (Map<String, Object> row : result) { // 剔除 row.remove("domain"); row.remove("version"); row.remove("id"); row.remove("updatetime"); row.remove("updator"); if( !_db.showCreator ) { row.remove("createtime"); row.remove("creator"); } row.remove("lastProcessor"); } // 过滤出用户可见的表头列 List<String> visiableFieldNames = _db.getVisiableFields(true, pointed ? ex.selectFields : _db.fieldCodes); if( _db.showCreator && !pointed ) { visiableFieldNames.add("创建时间"); visiableFieldNames.add("创建人"); } if( WFUtil.checkWorkFlow(_db.wfDefine) && !pointed ) { visiableFieldNames.add("流程状态"); visiableFieldNames.add("发起人"); visiableFieldNames.add("发起时间"); visiableFieldNames.add("审批人列表"); visiableFieldNames.add("当前审批人"); visiableFieldNames.add("抄送"); } String exportPath = DataExport.exportCSV(fileName, result, visiableFieldNames); DataExport.downloadFileByHttp(response, exportPath); } @RequestMapping(value = "/{record}/{id}", method = RequestMethod.GET) @ResponseBody public Map<String, Object> get(HttpServletRequest request, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); prepareParams(request, recordId); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_VDATA, Record.OPERATION_EDATA); Map<String, Object> result = _db.get(id); if (result == null) { throw new BusinessException(EX.parse(EX.DM_13B, id)); } /* 添加附件信息 */ result.put("attachList", recordService.getAttachList(recordId, id)); /* 添加工作流信息 */ wfService.appendWFInfo(_db, result, id); result.put("id", id); return result; } @RequestMapping(value = "/{record}", method = RequestMethod.POST) public void create(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record) { Object newID = createAndReturnID(request, record); printSuccessMessage(String.valueOf(newID)); } @RequestMapping(value = "/rid/{record}", method = RequestMethod.POST) @ResponseBody public Long createAndReturnID(HttpServletRequest request, @PathVariable("record") Object record) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); Long _tempID = EasyUtils.obj2Long(requestMap.remove("_tempID")); boolean isDraft = Config.TRUE.equalsIgnoreCase( requestMap.remove("saveDraft") ); _Database _db = getDB(recordId, Record.OPERATION_CDATA); Long newID = null; try { newID = _db.insertRID(requestMap); File attachDir1 = new File(RecordAttach.getAttachDir(recordId, _tempID)); if (attachDir1.exists()) { // 将挂在临时记录ID下的附件挂到新生成的记录ID上 String fixSql = "update dm_record_attach set itemId=? where recordId=? and itemId=?"; String attachSeqNos = requestMap.remove("remainAttachs"); // 提交记录时还存在的附件序号,有些上传错的附件可能已经删除了 if (!EasyUtils.isNullOrEmpty(attachSeqNos)) { fixSql += " and seqNo in (" + attachSeqNos + ") "; // 不在attachSeqNos里的附件成为废弃记录,可写一个定时Job清理这类记录 } Map<Integer, Object> paramsMap = new HashMap<Integer, Object>(); paramsMap.put(1, newID); paramsMap.put(2, recordId); paramsMap.put(3, _tempID); SQLExcutor.excute(fixSql, paramsMap, DMConstants.LOCAL_CONN_POOL); File attachDir2 = new File(RecordAttach.getAttachDir(recordId, newID)); attachDir1.renameTo(attachDir2); } // 新增时带附件操作,使用了自定义操作来支持多表数据操作;修改、删除不带附件操作,直接用MultiSQLExcutor执行即可 exeAfterOperation(requestMap, _db, newID); // 计算并初始化流程 if( !isDraft ) { wfService.calculateWFStatus(newID, _db); } } catch (Exception e) { _db.delete(newID); // 回滚 throwEx(e, _db + " create "); } return newID; } /** * 注:_after_ data参数名不能有和requestMap里重复的,不然用不上,优先被fmParse掉了 */ protected void exeAfterOperation(Map<String, String> requestMap, _Database _db, Long itemId) throws Exception { String afterOption = requestMap.remove("_after_"); if (!EasyUtils.isNullOrEmpty(afterOption)) { Map<String, Object> data = _db.get(itemId); data.put("id", itemId); for (String key : requestMap.keySet()) { if (!data.containsKey(key)) { data.put(key, requestMap.get(key)); } } afterOption = EasyUtils.fmParse(afterOption, data); MultiSQLExcutor mex = new MultiSQLExcutor(); mex.recordService = this.recordService; mex._exeMultiSQLs(afterOption, _db.datasource, data); } } private void throwEx(Exception e, String op) { Throwable firstCause = ExceptionEncoder.getFirstCause(e); String errorMsg = op + " error: " + firstCause; errorMsg = errorMsg.replaceAll("com.boubei.tss.framework.exception.BusinessException: ", ""); log.error(errorMsg, e); throw new BusinessException(errorMsg); } @RequestMapping(value = "/{record}/{id}", method = RequestMethod.POST) public void update(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); boolean isDraft = Config.TRUE.equalsIgnoreCase( requestMap.remove("saveDraft") ); // 检查用户对当前记录是否有编辑权限,防止篡改别人创建的记录 checkRowEditable(recordId, id); _Database _db = getDB(recordId); Map<String, Object> old = _db.get(id); try { _db.update(id, requestMap); } catch (Exception e) { throwEx(e, _db + " update "); } try { exeAfterOperation(requestMap, _db, id); // 计算并初始化流程 if( !isDraft ) { wfService.calculateWFStatus(id, _db); } printSuccessMessage(); } catch (Exception e) { _db.rollback(id, old); // 回滚 throwEx(e, _db + " update after "); } } /** * 批量更新选中记录行的某个字段值,用在批量审批等场景: * $.post("/tss/xdata/batch/13", {'ids': '1,2,3,4', 'field': 'brand', 'value': '农夫'} , function(data) {}); */ @RequestMapping(value = "/batch/{record}", method = RequestMethod.POST) public void updateBatch(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, String ids, String field, String value) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); String _value = requestMap.get("value"); value = (String) EasyUtils.checkNull(_value, value); // 检查用户对当前记录是否有编辑权限,防止篡改别人创建的记录 if (!checkPermission(recordId, Record.OPERATION_EDATA) && !checkPermission(recordId, Record.OPERATION_CDATA)) { throw new BusinessException(EX.DM_12); } getDB(recordId).updateBatch(ids, field, value); printSuccessMessage(); } @RequestMapping(value = "/{record}/{id}", method = RequestMethod.PUT) public void restore(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); prepareParams(request, recordId); checkRowEditable(recordId, id); _Database db = getDB(recordId); db.restore(id); printSuccessMessage(); } @RequestMapping(value = "/{record}/{id}", method = RequestMethod.DELETE) public void delete(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); exeDelete(recordId, id, requestMap); printSuccessMessage(); } private void exeDelete(Long recordId, Long id, Map<String, String> requestMap) { // 检查用户对当前记录是否有编辑权限 checkRowEditable(recordId, id); _Database db = getDB(recordId); Map<String, Object> old = db.get(id); String domain = EasyUtils.obj2String(old.get("domain")); // 已逻辑删除的再次删除,则直接删除 boolean isRecycled = domain.endsWith(_Database.deletedTag); // 判断是逻辑删除还是物理删除(系统级、单个表、单次请求) boolean loginDel = db.isLogicDelete(); if ( loginDel && !isRecycled ) { db.logicDelete(id); } else { // 物理删除 db.delete(id); wfService.removeWFStatus(recordId, id); // 删除附件 List<?> attachs = recordService.getAttachList(recordId, id); for (Object obj : attachs) { RecordAttach attach = (RecordAttach) obj; recordService.deleteAttach(attach.getId()); FileHelper.deleteFile(new File(attach.getAttachPath())); } } } // for 不支持method=DELETE的客户端,微信小程序等 @RequestMapping(value = "/batch/{record}/del", method = RequestMethod.POST) public void deleteBatchII(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, String ids) { deleteBatch(request, response, record, ids); } @RequestMapping(value = "/batch/{record}", method = RequestMethod.DELETE) public void deleteBatch(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, String ids) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); String[] idArray = ids.split(","); for (String id : idArray) { exeDelete(recordId, EasyUtils.obj2Long(id), requestMap); } printSuccessMessage(); } /** * 批量新增、修改、删除,All in one。 TODO 事务一致性 * * @param request * @param recordId * @param csv * @return * @throws Exception */ @RequestMapping(value = "/cud/{record}", method = RequestMethod.POST) @ResponseBody public Object cudBatch(HttpServletRequest request, @PathVariable("record") Object record) throws Exception { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); String csv = requestMap.get("csv"); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_EDATA); String[] rows = EasyUtils.split(csv, "\n"); List<Map<String, String>> insertList = new ArrayList<Map<String, String>>(); int updateCount = 0, deleteCount = 0; String[] headers = rows[0].split(","); for (int index = 1; index < rows.length; index++) { // 第一行为表头,不要 String row = rows[index]; String[] values = row.split(","); Map<String, String> item = new HashMap<String, String>(); for (int j = 0; j < values.length; j++) { item.put(headers[j], values[j]); } String _itemID = item.get("id"); if ( EasyUtils.isNullOrEmpty(_itemID) ) { insertList.add(item); } else { Long itemID = EasyUtils.obj2Long(_itemID); if (row.replaceAll(",", "").trim().equals(_itemID.trim())) { // 除了ID其它都为空 exeDelete(recordId, itemID, requestMap); deleteCount++; } else { checkRowEditable(recordId, itemID); _db.update(itemID, item); updateCount++; } } } _db.insertBatch(insertList); // 所有新增是一个事务的,但删除和修改不在一个事务内 exeAfterOperation(requestMap, _db, null); Map<String, Object> rtMap = new HashMap<String, Object>(); rtMap.put("created", insertList.size()); rtMap.put("updated", updateCount); rtMap.put("deleted", deleteCount); return rtMap; } @RequestMapping(value = "/cud/json/{record}", method = RequestMethod.POST) @ResponseBody public Object cudBatchJSON(HttpServletRequest request, @PathVariable("record") Object record) throws Exception { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); String json = requestMap.get("json"); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_EDATA); @SuppressWarnings("unchecked") List<Map<String, Object>> list = new ObjectMapper().readValue(json, List.class); List<Map<String, String>> insertList = new ArrayList<Map<String, String>>(); int updateCount = 0, deleteCount = 0; for (Map<String, Object> row : list) { Map<String, String> item = new HashMap<String, String>(); List<String> codes = new ArrayList<String>(_db.fieldCodes); codes.add("_version"); for (String field : codes) { if( row.containsKey(field) ) { Object value = row.get(field); item.put(field, EasyUtils.obj2String(value)); } } if ( !row.containsKey("id") ) { insertList.add(item); } else { Long itemID = EasyUtils.obj2Long( row.get("id") ); if ( item.isEmpty() ) { // 只有ID exeDelete(recordId, itemID, requestMap); deleteCount++; } else { checkRowEditable(recordId, itemID); _db.update(itemID, item); updateCount++; } } } _db.insertBatch(insertList); // 所有新增是一个事务的,但删除和修改不在一个事务内 exeAfterOperation(requestMap, _db, null); Map<String, Object> rtMap = new HashMap<String, Object>(); rtMap.put("created", insertList.size()); rtMap.put("updated", updateCount); rtMap.put("deleted", deleteCount); return rtMap; } /************************************* record workflow **************************************/ // 批量提交草稿状态流程申请 @RequestMapping(value = "/apply/{record}/batch", method = RequestMethod.POST) public void apply(HttpServletResponse response, @PathVariable("record") Object record, String ids) { Long recordId = recordService.getRecordID(record, false); _Database _db = recordService.getDB(recordId); String[] idArray = ids.trim().split(","); int count = 0; for (String id : idArray) { Long itemId = EasyUtils.obj2Long(id); WFStatus wfStatus = wfService.getWFStatus(recordId, itemId); if(wfStatus == null) { wfService.calculateWFStatus(itemId, _db); count++; } } printJSON("成功提交" + count + "条" + _db.recordName + "申请"); } // 审核 @RequestMapping(value = "/approve/{record}/{id}", method = RequestMethod.POST) public void approve(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); String opinion = requestMap.remove("opinion"); // 审批时允许审批者补充填写 wf_ 打头的数据表字段 if( requestMap.size() > 0 ) { _Database db = recordService.getDB(recordId); db.update(id, requestMap); } String msg = "审批成功"; String wfStatus = wfService.approve(recordId, id, opinion); if( WFStatus.PASSED.equals(wfStatus) ) { msg = "审批通过"; } printJSON(msg); } // 驳回 @RequestMapping(value = "/reject/{record}/{id}", method = RequestMethod.POST) public void reject(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); wfService.reject(recordId, id, requestMap.get("opinion")); printJSON("驳回成功"); } // 重新发起 @RequestMapping(value = "/reapply/{record}/{id}", method = RequestMethod.POST) public void reApply(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); wfService.reApply(recordId, id, requestMap.get("opinion")); printJSON("重新发起成功"); } // 转审 @RequestMapping(value = "/transApprove/{record}/{id}", method = RequestMethod.POST) public void transApprove(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); wfService.transApprove(recordId, id, requestMap.get("opinion"), requestMap.get("target")); printJSON("转审成功"); } @RequestMapping(value = "/translist/{record}/{id}") @ResponseBody public List<?> transList(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); prepareParams(request, recordId); return wfService.getTransList(recordId, id); } @RequestMapping(value = "/translist/xml/{record}/{id}") public void transListXML(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { @SuppressWarnings("unchecked") List<Map<String, String>> users = (List<Map<String, String>>) transList(request, response, record, id); List<ITreeNode> nodes = new ArrayList<ITreeNode>(); for (Map<String, String> m : users) { nodes.add(new DefaultTreeNode(m.get("usercode"), m.get("username"))); } TreeEncoder treeEncoder = new TreeEncoder(nodes); treeEncoder.setNeedRootNode(false); print("UserTree", treeEncoder); } // 撤销 @RequestMapping(value = "/cancel/{record}/{id}", method = RequestMethod.POST) public void cancel(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("id") Long id) { Long recordId = recordService.getRecordID(record, false); Map<String, String> requestMap = prepareParams(request, recordId); wfService.cancel(recordId, id, requestMap.get("opinion")); printJSON("撤销成功"); } /************************************* record batch import **************************************/ /** * 将前台(一般为生成好的table数据)数据导出成CSV格式 */ @RequestMapping("/import/tl/{record}") public void getImportTL(HttpServletResponse response, @PathVariable("record") Object record) { Long recordId = recordService.getRecordID(record, false); _Database _db = getDB(recordId, Record.OPERATION_CDATA, Record.OPERATION_EDATA, Record.OPERATION_VDATA); String fileName = _db.recordName + "-tl.csv"; String exportPath = DataExport.getExportPath() + "/" + fileName; List<String> columns = new ArrayList<String>(); String fieldNames = DMUtil.getExtendAttr(_db.remark, DMConstants.IMPORT_TL_FIELDS); // 允许在录入表备注里配置导入模板的列 if (fieldNames != null) { fieldNames = fieldNames.replaceAll(",", ",").replaceAll(" ", ","); columns.addAll(Arrays.asList(fieldNames.split(","))); } else { columns.addAll(_db.fieldNames); } String fieldIgnores = DMUtil.getExtendAttr(_db.remark, DMConstants.IMPORT_TL_IGNORES); if (fieldIgnores != null) { String[] _fieldIgnores = fieldIgnores.replaceAll(",", ",").replaceAll(" ", ",").split(","); for (String ignore : _fieldIgnores) { columns.remove(ignore); } } int index = 0; for(String fname : columns) { String fcode = _db.ncm.get(fname); boolean notnull = "false".equals(_db.cnull.get(fcode)); if( notnull ) { columns.set(index, "*" +fname+ "*"); } index++; } DataExport.exportCSV(exportPath, EasyUtils.list2Str(columns)); DataExport.downloadFileByHttp(response, exportPath); } /************************************* record attach operation **************************************/ @RequestMapping("/attach/json/{record}/{itemId}") @ResponseBody public List<?> getAttachList(HttpServletRequest request, @PathVariable("record") Object record, @PathVariable("itemId") Long itemId) { Long recordId = recordService.getRecordID(record, false); prepareParams(request, recordId); // 检查用户对当前记录是否有查看权限 if (!checkRowVisible(recordId, itemId)) { throw new BusinessException(EX.DM_08); } return recordService.getAttachList(recordId, itemId); } @RequestMapping("/attach/xml/{record}/{itemId}") public void getAttachListXML(HttpServletRequest request, HttpServletResponse response, @PathVariable("record") Object record, @PathVariable("itemId") Long itemId) { Long recordId = recordService.getRecordID(record, false); prepareParams(request, recordId); // 检查用户对当前记录是否有查看权限 if (!checkRowVisible(recordId, itemId)) { throw new BusinessException(EX.DM_08); } List<?> list = recordService.getAttachList(recordId, itemId); GridDataEncoder attachGrid = new GridDataEncoder(list, DMConstants.GRID_RECORD_ATTACH); print("RecordAttach", attachGrid); } @RequestMapping(value = "/attach/{id}", method = RequestMethod.DELETE) public void deleteAttach(HttpServletRequest request, HttpServletResponse response, @PathVariable("id") Long id) { RecordAttach attach = recordService.getAttach(id); if (attach == null) { throw new BusinessException(EX.DM_06); } Long recordId = attach.getRecordId(); prepareParams(request, recordId); // 远程访问预登录 // 检查用户对当前附件所属记录是否有编辑权限 checkRowEditable(recordId, attach.getItemId()); recordService.deleteAttach(id); FileHelper.deleteFile(new File(attach.getAttachPath())); // 记录附件删除日志,关联到附件所属的录入表 BusinessLogger.log(recordService.getRecord(recordId).getName(), "删除附件, " + id, attach); printSuccessMessage(); } // for 不支持method=DELETE的客户端,微信小程序等 @RequestMapping(value = "/attach/del/{id}", method = RequestMethod.POST) public void deleteAttach4WX(HttpServletRequest request, HttpServletResponse response, @PathVariable("id") Long id) { deleteAttach(request, response, id); } @RequestMapping("/attach/download/{id}") public void downloadAttach(HttpServletRequest request, HttpServletResponse response, @PathVariable("id") Long id) throws IOException { RecordAttach attach = recordService.getAttach(id); if (attach == null) { throw new BusinessException(EX.DM_06); } Long recordId = attach.getRecordId(); prepareParams(request, recordId); // 远程访问预登录 // 检查用户对当前附件所属记录是否有查看权限 if (!checkRowVisible(recordId, attach.getItemId())) { throw new BusinessException(EX.DM_07); } FileHelper.downloadFile(response, attach.getAttachPath(), attach.getName()); HitRateManager.getInstanse("dm_record_attach").output(id); // 更新浏览次数 Record record = recordService.getRecord(recordId); String rcName = record.getName(); AccessLogRecorder.outputAccessLog("record-" + record.getId(), "下载附件", rcName + "_" + id, new HashMap<String, String>(), System.currentTimeMillis()); } @RequestMapping(value = "/attach/top/{id}", method = RequestMethod.POST) @ResponseBody public RecordAttach setTop(@PathVariable("id") Long id) { RecordAttach attach = recordService.getAttach(id); Long recordId = attach.getRecordId(); // 检查用户对当前附件所属记录是否有编辑权限 Long itemId = attach.getItemId(); checkRowEditable(recordId, itemId); attach.setSeqNo(1); commService.update(attach); List<?> list = recordService.getAttachList(recordId, itemId); int index = 2; for( Object obj : list ) { RecordAttach _attach = (RecordAttach) obj; if( !_attach.getId().equals(id) ) { _attach.setSeqNo(index++); commService.update(_attach); } } return attach; } /************************************* check permissions:安全级别 >= 6 才启用 **************************************/ public static boolean checkPermission(Long recordId, String permitOption) { if (!SecurityUtil.isHardMode() || recordId < 0) return true; PermissionHelper helper = PermissionHelper.getInstance(); String permissionTable = RecordPermission.class.getName(); return helper.checkPermission(recordId, permissionTable, RecordResource.class, permitOption); } /** * 检查用户对当前记录是否有编辑权限,防止篡改别人创建的记录 * * @param recordId * @param itemId */ private void checkRowEditable(Long recordId, Long itemId) { if (!SecurityUtil.isHardMode() || recordId < 0) return; // 先检查流程是否存在且是否已开始处理 List<String> statusList = new ArrayList<String>(); statusList.add(WFStatus.NEW); // 已提交允许继续修改 statusList.add(WFStatus.REMOVED); // 还原 statusList.add(WFStatus.AUTO_PASSED); // 自动通过的允许修改 WFStatus wfStatus = wfService.getWFStatus(recordId, itemId); String userCode = Environment.getUserCode(); if (wfStatus != null && !(statusList.contains(wfStatus.getCurrentStatus()) && userCode.equals(wfStatus.getApplier()))) { throw new BusinessException(EX.WF_1); } boolean flag = false; if (checkPermission(recordId, Record.OPERATION_EDATA)) { flag = checkRowVisible(recordId, itemId); // 如果有【维护数据】权限,则只要可见就能编辑 } if (!flag && checkPermission(recordId, Record.OPERATION_CDATA)) { // 临时ID(即新增尚未保存时上传的附件)无需校验 flag = itemId > 1510000000000L || checkRowAuthor(recordId, itemId); // 如果没有【维护数据】只有【新建】权限,则只能编辑自己创建的记录 } if (!flag) { throw new BusinessException(EX.parse(EX.DM_05, itemId)); } } /** * 因db.select方法里对数据进行了权限过滤(以及按域过滤),所以能按ID查询出来的都是有权限查看的; */ private boolean checkRowVisible(Long recordId, Long itemId) { if (!SecurityUtil.isHardMode() || recordId < 0 || itemId > 1510000000000L) { // 临时记录ID,新建时 return true; } WFStatus wf = wfService.getWFStatus(recordId, itemId); String userCode = Environment.getUserCode(); if (wf != null && (wf.processorList().contains(userCode) || userCode.equals(wf.getNextProcessor()) || wf.toCCs().contains(userCode) )) { return true; // 所有审批记录对审批人(已审批、待审批、抄送人员)可见 } // 非审批流记录 Map<String, String> params = new HashMap<String, String>(); params.put("id", EasyUtils.obj2String(itemId)); _Database _db = getDB(recordId); SQLExcutor ex = _db.select(1, 1, params); return !ex.result.isEmpty() || checkInRecycleBin(_db, params); } private boolean checkRowAuthor(Long recordId, Long itemId) { Map<String, String> params = new HashMap<String, String>(); params.put("id", EasyUtils.obj2String(itemId)); params.put("creator", Environment.getUserCode()); _Database _db = getDB(recordId); SQLExcutor ex = _db.select(1, 1, params); /* 此处用select不用get,select还会拼上domain条件,get不会;用户如果变化了域,将看不到以前域的数据 */ return !ex.result.isEmpty() || checkInRecycleBin(_db, params); } private boolean checkInRecycleBin(_Database _db, Map<String, String> params) { params.put("domain", EasyUtils.obj2String(Environment.getDomainOrign()) + _Database.deletedTag); SQLExcutor ex = _db.select(1, 1, params); return !ex.result.isEmpty(); } }