|
@@ -4,14 +4,12 @@ import com.mongodb.client.MongoCursor;
|
|
|
import ieven.server.webapp.domain.ModelIdInput;
|
|
|
import ieven.server.webapp.domain.data.DataMap;
|
|
|
import ieven.server.webapp.domain.data.Fields;
|
|
|
-import ieven.server.webapp.domain.data.FieldsService;
|
|
|
import ieven.server.webapp.domain.file.FileStatus;
|
|
|
import ieven.server.webapp.domain.file.LogicalFile;
|
|
|
import ieven.server.webapp.infrastructure.wrapper.Mapped;
|
|
|
import ieven.server.webapp.util.DataUtils;
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
import org.apache.commons.collections4.CollectionUtils;
|
|
|
-import org.apache.commons.lang3.ObjectUtils;
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
|
import org.bson.Document;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
@@ -30,137 +28,300 @@ public class AlipayService {
|
|
|
@Autowired
|
|
|
private MongoTemplate mongoTemplate;
|
|
|
@Autowired
|
|
|
- private FieldsService fieldsService;
|
|
|
- @Autowired
|
|
|
@Lazy
|
|
|
private AlipayService alipayService;
|
|
|
- private final static String FNAME_PERSONINFO = "提取的人员信息表";
|
|
|
- private final static String FIELDNAME_BANKCARD = "绑定银行卡";
|
|
|
- private final static String EXTRACT_BANKCARDTYPE = "提取银行卡类型";
|
|
|
- private final static String EXTRACT_BANKCARDCODE = "提取银行卡开户行";
|
|
|
- private final static String EXTRACT_BANKCARD = "提取银行卡号";
|
|
|
- private final static List<String> PERSONINFO_HEADERNAMES = Arrays.asList("用户ID", "登录邮箱", "登录手机", "账户名称", "证件类型", "证件号", "绑定手机", "绑定银行卡", "提取银行卡类型", "提取银行卡开户行", "提取银行卡号");
|
|
|
-
|
|
|
- public Mapped testCursor() {
|
|
|
- Query query = new Query(Criteria.where("fileId").is("618ce033a14aa6502ed010ba"));
|
|
|
- MongoCursor<Document> cursor = mongoTemplate.getCollection("data")
|
|
|
- .find(query.getQueryObject())
|
|
|
- .batchSize(1000)
|
|
|
- .noCursorTimeout(true)
|
|
|
- .cursor();
|
|
|
- Document doc;
|
|
|
- while (cursor.hasNext()) {
|
|
|
- //写法1(建议)
|
|
|
- doc = cursor.next();
|
|
|
- log.info(doc.getString("f4"));
|
|
|
+
|
|
|
+ public Mapped extractAll(String modelId) {
|
|
|
+ extractPersonInfo(modelId);
|
|
|
+ //登录日志
|
|
|
+ Fields outputLogin = AlipayHeaderOps.createFileAndFields(AlipayType.ALIPAY_GENERATED_LOGIN, modelId);
|
|
|
+ List<Fields> loginFieldsList = checkFields(modelId, AlipayType.ALIPAY_ORIGIN_LOGIN);
|
|
|
+ for (Fields origin : loginFieldsList) {
|
|
|
+ alipayService.extractLogin(getCursor(origin.getFileId()), origin, outputLogin);
|
|
|
+ }
|
|
|
+ //交易记录
|
|
|
+ Fields outputTrade = AlipayHeaderOps.createFileAndFields(AlipayType.ALIPAY_GENERATED_TRADE, modelId);
|
|
|
+ List<Fields> tradeFieldsList = checkFields(modelId, AlipayType.ALIPAY_ORIGIN_TRADE);
|
|
|
+ for (Fields origin : tradeFieldsList) {
|
|
|
+ alipayService.extractTrade(getCursor(origin.getFileId()), origin, outputTrade);
|
|
|
+ }
|
|
|
+ //账户明细
|
|
|
+ Fields outputAccount = AlipayHeaderOps.createFileAndFields(AlipayType.ALIPAY_GENERATED_ACCOUNT, modelId);
|
|
|
+ List<Fields> accountFieldsList = checkFields(modelId, AlipayType.ALIPAY_ORIGIN_ACCOUNT);
|
|
|
+ for (Fields origin : accountFieldsList) {
|
|
|
+ alipayService.extractAccount(getCursor(origin.getFileId()), origin, outputAccount);
|
|
|
+ }
|
|
|
+ //转账明细
|
|
|
+ Fields outputTransfer = AlipayHeaderOps.createFileAndFields(AlipayType.ALIPAY_GENERATED_TRANSFER, modelId);
|
|
|
+ List<Fields> transferFieldsList = checkFields(modelId, AlipayType.ALIPAY_ORIGIN_TRANSFER);
|
|
|
+ for (Fields origin : transferFieldsList) {
|
|
|
+ alipayService.extractTransfer(getCursor(origin.getFileId()), origin, outputTransfer);
|
|
|
}
|
|
|
- return null;
|
|
|
+ return Mapped.OK();
|
|
|
}
|
|
|
|
|
|
- public Mapped extractPersonInfo(ModelIdInput modelIdInput) {
|
|
|
- String modelId = modelIdInput.getModelId();
|
|
|
- if (modelId == null) {
|
|
|
- return Mapped.ERROR("没有选择模型!");
|
|
|
- }
|
|
|
- //插入文件
|
|
|
- LogicalFile logicalFile = createNewLogicalFile(modelId, FNAME_PERSONINFO);
|
|
|
- String fileId = logicalFile.getId();
|
|
|
- //创建并存储表头
|
|
|
- Fields fields = fieldsService.createHeaderAndSave(PERSONINFO_HEADERNAMES, fileId);
|
|
|
- //根据模型id查询所有的文件
|
|
|
- String key1 = "fieldsReverse." + FIELDNAME_BANKCARD;
|
|
|
- Query queryExist = new Query(Criteria.where(key1).exists(true).and(""));
|
|
|
- List<Fields> fieldsList = mongoTemplate.find(queryExist, Fields.class);
|
|
|
- for (Fields fields1 : fieldsList) {
|
|
|
- //再次过滤出是原始非修改过的文件的
|
|
|
- Query queryIsGenerated = new Query(Criteria.where("id").is(fields1.getFileId()).and("generated").is(Boolean.FALSE));
|
|
|
- LogicalFile checked = mongoTemplate.findOne(queryIsGenerated, LogicalFile.class);
|
|
|
- if (checked != null && checked.getId() != null && checked.getId().equals(fields1.getFileId())) {
|
|
|
- alipayService.extractPersonInfoByFileId(fields1, fields);
|
|
|
+ public void extractPersonInfo(String modelId) {
|
|
|
+ //创建并存储文件和表头
|
|
|
+ Fields output = AlipayHeaderOps.createFileAndFields(AlipayType.ALIPAY_GENERATED_REGISTER, modelId);
|
|
|
+ List<Fields> fieldsList = checkFields(modelId, AlipayType.ALIPAY_ORIGIN_REGISTER);
|
|
|
+ //可能有多个文件,对多个文件并行解析
|
|
|
+ for (Fields origin : fieldsList) {
|
|
|
+ alipayService.extractRegister(origin, output);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<Fields> checkFields(String modelId, int requireType) {
|
|
|
+ Query query = new Query(Criteria.where("modelId").is(modelId).and("generated").is(Boolean.FALSE));
|
|
|
+ List<LogicalFile> fileList = mongoTemplate.find(query, LogicalFile.class);
|
|
|
+ List<String> fileIdList = new ArrayList<>();
|
|
|
+ for (LogicalFile file : fileList) {
|
|
|
+ String fileId = file.getId();
|
|
|
+ fileIdList.add(fileId);
|
|
|
+ }
|
|
|
+ Query query1 = new Query(Criteria.where("fileId").in(fileIdList));
|
|
|
+ List<Fields> fieldsList = mongoTemplate.find(query1, Fields.class);
|
|
|
+ List<String> requireHeaders = AlipayHeaders.getHeaders(requireType);
|
|
|
+ List<Fields> checked = new ArrayList<>();
|
|
|
+ for (Fields fields : fieldsList) {
|
|
|
+ List<String> headers = new ArrayList<>(fields.getFieldsReverse().keySet());
|
|
|
+ //字段完全一样就说明是需要查找的表
|
|
|
+ if (headers.containsAll(requireHeaders) && requireHeaders.containsAll(headers)) {
|
|
|
+ checked.add(fields);
|
|
|
}
|
|
|
}
|
|
|
- Mapped mapped = Mapped.OK();
|
|
|
- mapped.put("result", logicalFile);
|
|
|
- return mapped;
|
|
|
+ return checked;
|
|
|
+ }
|
|
|
+
|
|
|
+ private MongoCursor<Document> getCursor(String inputFileId) {
|
|
|
+ Query query = new Query(Criteria.where("fileId").is(inputFileId));
|
|
|
+ //查询游标
|
|
|
+ return mongoTemplate.getCollection("data")
|
|
|
+ .find(query.getQueryObject())
|
|
|
+ .batchSize(1000)
|
|
|
+ .noCursorTimeout(true)
|
|
|
+ .cursor();
|
|
|
}
|
|
|
|
|
|
- //遍历文件里面的所有内容
|
|
|
+ //注册信息
|
|
|
@Async
|
|
|
- public void extractPersonInfoByFileId(Fields input, Fields output) {
|
|
|
- LinkedHashMap<String, String> inputReversed = input.getFieldsReverse();
|
|
|
+ public void extractRegister(Fields origin, Fields output) {
|
|
|
+ LinkedHashMap<String, String> inputReversed = origin.getFieldsReverse();
|
|
|
LinkedHashMap<String, String> outputReversed = output.getFieldsReverse();
|
|
|
- String inputFileId = input.getFileId();
|
|
|
+ String inputFileId = origin.getFileId();
|
|
|
String outputFileId = output.getFileId();
|
|
|
- String in1 = inputReversed.get("绑定银行卡");
|
|
|
- String out1 = inputReversed.get("绑定银行卡");
|
|
|
- String out2 = outputReversed.get("提取银行卡类型");
|
|
|
- String out3 = outputReversed.get("提取银行卡开户行");
|
|
|
- String out4 = outputReversed.get("提取银行卡号");
|
|
|
Query query = new Query(Criteria.where("fileId").is(inputFileId));
|
|
|
query.fields().include();
|
|
|
+ //查询游标
|
|
|
MongoCursor<Document> cursor = mongoTemplate.getCollection("data")
|
|
|
.find(query.getQueryObject())
|
|
|
.batchSize(1000)
|
|
|
.noCursorTimeout(true)
|
|
|
.cursor();
|
|
|
- Document doc;
|
|
|
+ Document originDoc;
|
|
|
List<DataMap> needToSave = new ArrayList<>(1000);
|
|
|
while (cursor.hasNext()) {
|
|
|
//写法1(建议)
|
|
|
- doc = cursor.next();
|
|
|
- DataMap newDoc = new DataMap();
|
|
|
- newDoc.put("fileId", outputFileId);
|
|
|
- //其他字段
|
|
|
- for (String headerName : PERSONINFO_HEADERNAMES) {
|
|
|
- String inputAlias = inputReversed.get(headerName);
|
|
|
- if (StringUtils.isNotBlank(inputAlias)) {
|
|
|
- String outputAlias = outputReversed.get(headerName);
|
|
|
- if (StringUtils.isNotBlank(outputAlias)) {
|
|
|
- newDoc.put(outputAlias, doc.get(inputAlias));
|
|
|
+ originDoc = cursor.next();
|
|
|
+ DataMap newDoc = coryOriginToOutput(originDoc, outputFileId, origin, output);
|
|
|
+ //绑定银行卡字段
|
|
|
+ String aO1 = inputReversed.get("绑定银行卡");
|
|
|
+ String vO1 = originDoc.getString(aO1);
|
|
|
+ String[] sp1 = vO1.split(";");
|
|
|
+ for (String s1 : sp1) {
|
|
|
+ if (StringUtils.isNotBlank(s1)) {
|
|
|
+ String[] sp2 = s1.split(":");
|
|
|
+ if (sp2.length == 3) {
|
|
|
+ //拷贝一行新的
|
|
|
+ DataMap newLine = new DataMap(newDoc);
|
|
|
+ newLine.put(outputReversed.getOrDefault(AlipayHeaders.REGISTER_1, "-1"), sp2[0]);
|
|
|
+ newLine.put(outputReversed.getOrDefault(AlipayHeaders.REGISTER_2, "-1"), sp2[1]);
|
|
|
+ newLine.put(outputReversed.getOrDefault(AlipayHeaders.REGISTER_3, "-1"), sp2[2]);
|
|
|
+ needToSave.add(newLine);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+ if (needToSave.size() >= 1000) {
|
|
|
+ saveLines(new ArrayList<>(needToSave));
|
|
|
+ needToSave.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isNotEmpty(needToSave)) {
|
|
|
+ saveLines(needToSave);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Async
|
|
|
+ public void extractLogin(MongoCursor<Document> cursor, Fields origin, Fields output) {
|
|
|
+ LinkedHashMap<String, String> inputReversed = origin.getFieldsReverse();
|
|
|
+ LinkedHashMap<String, String> outputReversed = output.getFieldsReverse();
|
|
|
+ Document originDoc;
|
|
|
+ List<DataMap> needToSave = new ArrayList<>(1000);
|
|
|
+ while (cursor.hasNext()) {
|
|
|
+ //写法1(建议)
|
|
|
+ originDoc = cursor.next();
|
|
|
+ DataMap newDoc = coryOriginToOutput(originDoc, output.getFileId(), origin, output);
|
|
|
//绑定银行卡字段
|
|
|
- String value1 = doc.getString(in1);
|
|
|
- String[] splited1 = value1.split(";");
|
|
|
- for (String s1 : splited1) {
|
|
|
- if (StringUtils.isNotBlank(s1)) {
|
|
|
- String[] splited2 = s1.split(":");
|
|
|
- if (splited2.length == 3) {
|
|
|
- newDoc.put(out2, splited2[0]);
|
|
|
- newDoc.put(out3, splited2[1]);
|
|
|
- newDoc.put(out4, splited2[2]);
|
|
|
- needToSave.add(newDoc);
|
|
|
+ String aO1 = inputReversed.get("操作发生时间");
|
|
|
+ String vO1 = originDoc.getString(aO1);
|
|
|
+ newDoc.put(outputReversed.getOrDefault(AlipayHeaders.LOGIN_1, "-1"), DataUtils.getDateFront(vO1));
|
|
|
+ newDoc.put(outputReversed.getOrDefault(AlipayHeaders.LOGIN_2, "-1"), DataUtils.getDateEnd(vO1));
|
|
|
+ needToSave.add(newDoc);
|
|
|
+ if (needToSave.size() >= 1000) {
|
|
|
+ saveLines(new ArrayList<>(needToSave));
|
|
|
+ needToSave.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isNotEmpty(needToSave)) {
|
|
|
+ saveLines(needToSave);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Async
|
|
|
+ public void extractTrade(MongoCursor<Document> cursor, Fields origin, Fields output) {
|
|
|
+ LinkedHashMap<String, String> inputReversed = origin.getFieldsReverse();
|
|
|
+ LinkedHashMap<String, String> outputReversed = output.getFieldsReverse();
|
|
|
+ Document originDoc;
|
|
|
+ List<DataMap> needToSave = new ArrayList<>(1000);
|
|
|
+ while (cursor.hasNext()) {
|
|
|
+ //写法1(建议)
|
|
|
+ originDoc = cursor.next();
|
|
|
+ DataMap newDoc = coryOriginToOutput(originDoc, output.getFileId(), origin, output);
|
|
|
+ //绑定银行卡字段
|
|
|
+ String i1 = inputReversed.get("买家信息");
|
|
|
+ String v1 = originDoc.getString(i1);
|
|
|
+ String i2 = inputReversed.get("卖家信息");
|
|
|
+ String v2 = originDoc.getString(i2);
|
|
|
+ if (StringUtils.isNotBlank(v1)) {
|
|
|
+ int firstIndex = 0;
|
|
|
+ int secondIndex = 0;
|
|
|
+ String alias1 = outputReversed.getOrDefault(AlipayHeaders.TRADE_1, "-1");
|
|
|
+ String alias2 = outputReversed.getOrDefault(AlipayHeaders.TRADE_2, "-1");
|
|
|
+ String alias3 = outputReversed.getOrDefault(AlipayHeaders.TRADE_3, "-1");
|
|
|
+ StringBuilder o1 = new StringBuilder();
|
|
|
+ StringBuilder o2 = new StringBuilder();
|
|
|
+ StringBuilder o3 = new StringBuilder();
|
|
|
+ for (int i = 0; i < v1.length(); i++) {
|
|
|
+ char current = v1.charAt(i);
|
|
|
+ if (current != '(' && current != ')') {
|
|
|
+ if (firstIndex == 0 && secondIndex == 0) {
|
|
|
+ o1.append(current);
|
|
|
+ } else if (firstIndex == 1 && secondIndex == 0) {
|
|
|
+ o2.append(current);
|
|
|
+ } else if (firstIndex == 1 && secondIndex == 1) {
|
|
|
+ o3.append(current);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (firstIndex == 0 && current == '(') {
|
|
|
+ firstIndex = 1;
|
|
|
+ }else if(firstIndex == 1 && secondIndex == 0 && current == '('){
|
|
|
+ secondIndex = 1;
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
+ newDoc.put(alias1, o1.toString());
|
|
|
+ newDoc.put(alias2, o2.toString());
|
|
|
+ newDoc.put(alias3, o3.toString());
|
|
|
}
|
|
|
+ if (StringUtils.isNotBlank(v2)) {
|
|
|
+ int firstIndex = 0;
|
|
|
+ int secondIndex = 0;
|
|
|
+ String alias1 = outputReversed.getOrDefault(AlipayHeaders.TRADE_4, "-1");
|
|
|
+ String alias2 = outputReversed.getOrDefault(AlipayHeaders.TRADE_5, "-1");
|
|
|
+ String alias3 = outputReversed.getOrDefault(AlipayHeaders.TRADE_6, "-1");
|
|
|
+ StringBuilder o1 = new StringBuilder();
|
|
|
+ StringBuilder o2 = new StringBuilder();
|
|
|
+ StringBuilder o3 = new StringBuilder();
|
|
|
+ for (int i = 0; i < v2.length(); i++) {
|
|
|
+ char current = v2.charAt(i);
|
|
|
+ if (current != '(' && current != ')') {
|
|
|
+ if (firstIndex == 0 && secondIndex == 0) {
|
|
|
+ o1.append(current);
|
|
|
+ } else if (firstIndex == 1 && secondIndex == 0) {
|
|
|
+ o2.append(current);
|
|
|
+ } else if (firstIndex == 1 && secondIndex == 1) {
|
|
|
+ o3.append(current);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (firstIndex == 0 && current == '(') {
|
|
|
+ firstIndex = 1;
|
|
|
+ }else if(firstIndex == 1 && secondIndex == 0 && current == '('){
|
|
|
+
|
|
|
+ secondIndex = 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ newDoc.put(alias1, o1.toString());
|
|
|
+ newDoc.put(alias2, o2.toString());
|
|
|
+ newDoc.put(alias3, o3.toString());
|
|
|
+ }
|
|
|
+ needToSave.add(newDoc);
|
|
|
if (needToSave.size() >= 1000) {
|
|
|
saveLines(new ArrayList<>(needToSave));
|
|
|
needToSave.clear();
|
|
|
}
|
|
|
}
|
|
|
if (CollectionUtils.isNotEmpty(needToSave)) {
|
|
|
-// saveLines(new ArrayList<>(needToSave));
|
|
|
saveLines(needToSave);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private void readFieldBankCard() {
|
|
|
+ @Async
|
|
|
+ public void extractAccount(MongoCursor<Document> cursor, Fields origin, Fields output) {
|
|
|
+ LinkedHashMap<String, String> inputReversed = origin.getFieldsReverse();
|
|
|
+ LinkedHashMap<String, String> outputReversed = output.getFieldsReverse();
|
|
|
+ Document originDoc;
|
|
|
+ List<DataMap> needToSave = new ArrayList<>(1000);
|
|
|
+ while (cursor.hasNext()) {
|
|
|
+ //写法1(建议)
|
|
|
+ originDoc = cursor.next();
|
|
|
+ DataMap newDoc = coryOriginToOutput(originDoc, output.getFileId(), origin, output);
|
|
|
+ needToSave.add(newDoc);
|
|
|
+ if (needToSave.size() >= 1000) {
|
|
|
+ saveLines(new ArrayList<>(needToSave));
|
|
|
+ needToSave.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isNotEmpty(needToSave)) {
|
|
|
+ saveLines(needToSave);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
+ @Async
|
|
|
+ public void extractTransfer(MongoCursor<Document> cursor, Fields origin, Fields output) {
|
|
|
+ LinkedHashMap<String, String> inputReversed = origin.getFieldsReverse();
|
|
|
+ LinkedHashMap<String, String> outputReversed = output.getFieldsReverse();
|
|
|
+ Document originDoc;
|
|
|
+ List<DataMap> needToSave = new ArrayList<>(1000);
|
|
|
+ while (cursor.hasNext()) {
|
|
|
+ //写法1(建议)
|
|
|
+ originDoc = cursor.next();
|
|
|
+ DataMap newDoc = coryOriginToOutput(originDoc, output.getFileId(), origin, output);
|
|
|
+ //绑定银行卡字段
|
|
|
+ needToSave.add(newDoc);
|
|
|
+ if (needToSave.size() >= 1000) {
|
|
|
+ saveLines(new ArrayList<>(needToSave));
|
|
|
+ needToSave.clear();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (CollectionUtils.isNotEmpty(needToSave)) {
|
|
|
+ saveLines(needToSave);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 创建新的逻辑表,只包含表头
|
|
|
- */
|
|
|
- public LogicalFile createNewLogicalFile(String modelId, String filename) {
|
|
|
- LogicalFile logicalFile = new LogicalFile();
|
|
|
- logicalFile.setFilename(filename);
|
|
|
- logicalFile.setStatus(FileStatus.STATUS_LOADING);
|
|
|
- String current = DataUtils.currentDate();
|
|
|
- logicalFile.setUploadDate(current);
|
|
|
- logicalFile.setModelId(modelId);
|
|
|
- logicalFile.setGenerated(Boolean.TRUE);
|
|
|
- return mongoTemplate.insert(logicalFile);
|
|
|
+ //复制原始数据到新的一行
|
|
|
+ private DataMap coryOriginToOutput(Document originData, String newFileId, Fields originFields, Fields outputFields) {
|
|
|
+ DataMap newData = new DataMap();
|
|
|
+ newData.put("fileId", newFileId);
|
|
|
+ //遍历新的表的每一个字段
|
|
|
+ LinkedHashMap<String, String> originHeaderReversed = originFields.getFieldsReverse();
|
|
|
+ LinkedHashMap<String, String> newHeaderReversed = outputFields.getFieldsReverse();
|
|
|
+ for (Map.Entry<String, String> entry : newHeaderReversed.entrySet()) {
|
|
|
+ String headerName = entry.getKey();
|
|
|
+ String alias = entry.getValue();
|
|
|
+ String originAlias = originHeaderReversed.getOrDefault(headerName, "");
|
|
|
+ newData.put(alias, originData.getOrDefault(originAlias, ""));
|
|
|
+ }
|
|
|
+ return newData;
|
|
|
}
|
|
|
|
|
|
@Async
|