- 需求: 需要把指定目录的txt文件进行数据解析处理
涉及代码
yml配置
treeGorgesBank:
fileSend:
dirPath: E:\tree-sourece
backupDirPath: E:\tree-back
jobCron: 0/5 * * * * ?
线程池定义
@Configuration
public class FileThreadPoolConfig {
@Bean(name = "fileSendThreadPool")
public ThreadPoolExecutor fileThreadPool() {
return new ThreadPoolExecutor(5, 10, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(),
ThreadUtil.threadFactory("fileSendJobThreadPool"),
new ThreadPoolExecutor.AbortPolicy());
}
}
import com.google.common.collect.Lists;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiFunction;
import java.util.function.Function;
public class ThreadUtil {
private final static Logger logger = LoggerFactory.getLogger(ThreadUtil.class);
public static void sleepByWait(long timeout) throws InterruptedException {
byte[] lock = new byte[0];
synchronized (lock){
lock.wait(timeout);
}
lock = null;
}
public static RejectedExecutionHandler blockExecuteRejectHandle(String name){
return new BlockExecuteRejectHandle(name);
}
public static ThreadFactory threadFactory(String name){
return new TFactory(name);
}
static class TFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public TFactory(String name) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = name.concat("-");
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
static class BlockExecuteRejectHandle implements RejectedExecutionHandler {
final String name;
public BlockExecuteRejectHandle(String name) {
this.name = name.concat("RejectHandle");
}
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
try {
logger.warn("{} 阻塞加入 | pool:{}",name,e);
e.getQueue().put(r);
} catch (Exception ex) {
logger.error("{} 阻塞加入异常",e,ex);
}
}
}
}
public static int avgCapacity(int dataSize, int maxTaskNum) {
int _c = dataSize / maxTaskNum;
if (_c == 0){
return 0;
}
return dataSize % maxTaskNum == 0 ? _c : _c + 1;
}
public static <T> void concurrentExecuteAndBlockResult(ThreadPoolExecutor executor, int maxTaskNum,Function<List<T>, Void> fun,List<T> ls){
if (ls.isEmpty()){
return;
}
int avgCapacity = ThreadUtil.avgCapacity(ls.size(), maxTaskNum);
if (avgCapacity <= 1){
fun.apply(ls);
}else {
List<List<T>> lists = Lists.partition(ls, avgCapacity);
CompletableFuture[] all = new CompletableFuture[lists.size()];
for (int i = 0; i < lists.size(); i++) {
List<T> tmp = lists.get(i);
if (tmp.isEmpty()){
continue;
}
all[i] = CompletableFuture.runAsync(() -> fun.apply(tmp), executor);
}
CompletableFuture.allOf(all).join();
}
}
public static <T> void concurrentExecuteAndBlockResultVo(ThreadPoolExecutor executor, int oneTaskDataSize,Function<List<T>, Void> fun,List<T> ls){
if (ls.isEmpty()){
return;
}
if (ls.size() <= oneTaskDataSize){
fun.apply(ls);
}else {
List<List<T>> lists = Lists.partition(ls, oneTaskDataSize);
CompletableFuture[] all = new CompletableFuture[lists.size()];
for (int i = 0; i < lists.size(); i++) {
List<T> tmp = lists.get(i);
if (tmp.isEmpty()){
continue;
}
all[i] = CompletableFuture.runAsync(() -> fun.apply(tmp), executor);
}
CompletableFuture.allOf(all).join();
}
}
public static <T> void concurrentExecuteAndBlockResultVoForAbortPolicyReject(ThreadPoolExecutor executor, int oneTaskDataSize, Function<List<T>, Void> fun, List<T> ls){
if (ls.isEmpty()){
return;
}
if (ls.size() <= oneTaskDataSize){
fun.apply(ls);
}else {
List<List<T>> lists = Lists.partition(ls, oneTaskDataSize);
CompletableFuture[] all = new CompletableFuture[lists.size()];
for (int i = 0; i < lists.size(); i++) {
List<T> tmp = lists.get(i);
if (tmp.isEmpty()){
continue;
}
int reNum = 0;
while (all[i] == null){
try {
all[i] = CompletableFuture.runAsync(() -> fun.apply(tmp), executor);
} catch (RejectedExecutionException e) {
if (reNum == 0){
logger.warn("线程池处理任务繁忙:{}",e.getMessage());
}
reNum++;
try {Thread.sleep(3);} catch (Exception e1) {}
}catch (Exception e){
logger.error("线程池处理任务异常",e);
break;
}
}
if (reNum>0){
logger.warn("线程池处理任务繁忙 重试次数:{}",reNum);
}
}
CompletableFuture.allOf(all).join();
}
}
public static <T> void concurrentExecuteAndBlockResult(ThreadPoolExecutor executor, int maxTaskNum,BiFunction<List<T>, Object, Void> fun, List<T> ls,Object p0){
if (ls.isEmpty()){
return;
}
int avgCapacity = ThreadUtil.avgCapacity(ls.size(), maxTaskNum);
if (avgCapacity <= 1){
fun.apply(ls,p0);
}else {
List<List<T>> lists = Lists.partition(ls, avgCapacity);
CompletableFuture[] all = new CompletableFuture[lists.size()];
for (int i = 0; i < lists.size(); i++) {
List<T> tmp = lists.get(i);
if (tmp.isEmpty()){
continue;
}
all[i] = CompletableFuture.runAsync(() -> fun.apply(tmp,p0), executor);
}
CompletableFuture.allOf(all).join();
}
}
public static <T> void concurrentExecuteAndBlockResult(ThreadPoolExecutor executor, int maxTaskNum,BiFunction<List<T>, Object[], Void> fun, List<T> ls,Object... oArr){
if (ls.isEmpty()){
return;
}
int avgCapacity = ThreadUtil.avgCapacity(ls.size(), maxTaskNum);
if (avgCapacity <= 1){
fun.apply(ls,oArr);
}else {
List<List<T>> lists = Lists.partition(ls, avgCapacity);
CompletableFuture[] all = new CompletableFuture[lists.size()];
for (int i = 0; i < lists.size(); i++) {
List<T> tmp = lists.get(i);
if (tmp.isEmpty()){
continue;
}
all[i] = CompletableFuture.runAsync(() -> fun.apply(tmp,oArr), executor);
}
CompletableFuture.allOf(all).join();
}
}
public static <T,R> List<R> exec(ThreadPoolExecutor executor, int maxTaskNum,
Function<List<T>, List<R>> fun,
List<T> dataLs){
if (dataLs.isEmpty()){
return new ArrayList<>();
}
int avgCapacity = avgCapacity(dataLs.size(), maxTaskNum);
if (avgCapacity <= 1){
return fun.apply(dataLs);
}else {
List<R> ret = new CopyOnWriteArrayList<>();
List<List<T>> lists = Lists.partition(dataLs, avgCapacity);
CompletableFuture<? extends List<? extends R>>[] all = new CompletableFuture[lists.size()];
for (int i = 0; i < lists.size(); i++) {
List<T> tmp = lists.get(i);
if (tmp.isEmpty()){
continue;
}
all[i] = CompletableFuture.supplyAsync(() -> fun.apply(tmp), executor).whenCompleteAsync((rv, ex) -> {
if (ex != null) {
ex.printStackTrace();
}
if (rv != null) {
ret.addAll(rv);
}
});
}
CompletableFuture.allOf(all).join();
return ret;
}
}
}
定时器
import com.xyc.sms.api.threeGorgesBank.fileSend.FileSendFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.text.ParseException;
@Component
@EnableScheduling
public class FileSendJob {
@Autowired
private FileSendFactory fileSendFactory;
@Scheduled(cron = "${treeGorgesBank.jobCron:0 20 * * * ?}")
public void fileSend() throws ParseException {
fileSendFactory.moveFileToSend();
}
}
定时任务跑业务(重点)
- 主线程跑定时扫码指定目录是否有符合要求的文件,若有则进行转移到备用目录,并把转移后的文件的地址放进线程池中让线程解析该文件数据
转移文件
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.xyc.sms.api.service.SmsTypeService;
import com.xyc.sms.api.service.UserService;
import com.xyc.sms.api.service.impl.SmsTypeServiceImpl;
import com.xyc.sms.api.service.impl.UserServiceImpl;
import com.xyc.sms.boss.entity.User;
import com.xyc.sms.common.base.utils.DateUtil;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.File;
import java.text.ParseException;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Objects;
import java.util.concurrent.ThreadPoolExecutor;
@Component
public class FileSendFactory {
private static final Log logger = LogFactory.get();
@Value("${treeGorgesBank.fileSend.dirPath}")
private String dirPath;
@Value("${treeGorgesBank.fileSend.backupDirPath}")
private String backupDirPath;
@Autowired
@Qualifier("fileSendThreadPool")
private ThreadPoolExecutor executor;
public void moveFileToSend() throws ParseException {
try {
File dir = new File(dirPath);
if (dir.exists()) {
File[] files = dir.listFiles();
if (ArrayUtil.isEmpty(files)) {
return;
}
for (File txt : files) {
if (!txt.isFile() || !StrUtil.endWithIgnoreCase(txt.getName(), ".txt")) {
continue;
}
if (!StringUtils.equals(txt.getName().substring(0, 3), "mas") && !StringUtils.equals(txt.getName().substring(0, 3), "sms")) {
continue;
}
String fileAccount = txt.getName().substring(0, 3);
UserService userService = SpringUtil.getBean(UserServiceImpl.class);
User user = userService.loginByAccount(fileAccount);
if (user == null) {
logger.error("[moveFileToSend]用户不存在,文件名:{}",txt.getName());
continue;
}
String smsTypeIdById = userService.getSmsTypeIdById(user.getId());
if (StringUtils.isBlank(smsTypeIdById)){
logger.error("[moveFileToSend]用户短信类型配置不存在,文件名:{}",txt.getName());
continue;
}
SmsTypeService smsTypeService = SpringUtil.getBean(SmsTypeServiceImpl.class);
Integer smsTypeId = Integer.valueOf(smsTypeIdById.split(",")[0]);
Integer priority = smsTypeService.getPriority(smsTypeId);
if (Objects.isNull(priority)){
logger.error("[moveFileToSend]用户的短信类型未配置优先级,文件名:{}",txt.getName());
continue;
}
String dirResult = "";
File oldFile = new File(txt.getParent() + "/" + txt.getName());
String toBackStr = "";
String[] split = txt.getName().split("\\.");
String timeStr = split[4];
String fileTime = timeStr.substring(0, 2) + ":" + timeStr.substring(2, 4) + ":" + timeStr.substring(4, 6);
if (timeStr.length() != 6) {
logger.error("[moveFileToSend]文件时间格式命名有误:{}", txt.getName());
}
GregorianCalendar calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String hour_str = String.valueOf(hour);
String minute_str = String.valueOf(minute);
String second_str = String.valueOf(second);
String nowTime = hour_str + ":" + minute_str + ":" + second_str;
boolean startT = compTime(nowTime, fileTime, txt.getName());
if (!startT) {
return;
}
toBackStr = backupDirPath + "/" + DateUtil.getCurrentDate().replace("-", "");
File newFile = new File(toBackStr + "/" + txt.getName());
if (!new File(toBackStr).exists()) {
(new File(toBackStr)).mkdirs();
}
if (oldFile.renameTo(newFile)) {
logger.info("[moveFileToSend]文件移动成功至:" + newFile.getParent() + "/" + newFile.getName());
dirResult = newFile.getParent() + "/" + newFile.getName();
} else {
logger.error("[moveFileToSend]文件移动失败:" + txt.getParent() + "/" + txt.getName());
}
if (StringUtils.isNotBlank(dirResult)) {
executor.execute(new FileSendRunnable(dirResult));
}
}
}
} catch (Exception e) {
logger.error(e);
}
}
public static boolean compTime(String s1, String s2, String fileName) {
try {
if (s1.indexOf(":") < 0 || s1.indexOf(":") < 0) {
logger.error("文件命名格式有误,文件名{}:,当前时间为:{},文件格式时间数据为:{}", fileName, s1, s2);
} else {
String[] array1 = s1.split(":");
int total1 = Integer.valueOf(array1[0]) * 3600 + Integer.valueOf(array1[1]) * 60 + Integer.valueOf(array1[2]);
String[] array2 = s2.split(":");
int total2 = Integer.valueOf(array2[0]) * 3600 + Integer.valueOf(array2[1]) * 60 + Integer.valueOf(array2[2]);
return total1 - total2 > 0 ? true : false;
}
} catch (NumberFormatException e) {
logger.error("[moveFileToSend]解析文件命名有误,文件名:{},参数一{},参数二{},报错{}" + fileName, s1, s2, e);
}
return false;
}
}
解析文件数据
public class FileSendRunnable implements Runnable {
private static final Logger logger = LoggerFactory.getLogger(FileSendRunnable.class);
public static final EncodingDetect ENCODING_DETECT = new EncodingDetect();
private String filePath;
public FileSendRunnable(String filePath) {
this.filePath = filePath;
}
@Override
public void run() {
BufferedReader reader = null;
try {
int failCount = 0;
File file = new File(filePath);
if (Objects.isNull(file)) {
logger.error("[fileSendRunnable]寻找不到该文件" + filePath);
return;
}
String fileRealName = file.getName().contains("--") ? file.getName().split("--")[1] : file.getName();
blockCheckFtpFileComplete(file, 20 * 1000);
String[] splitSys = fileRealName.split("\\.");
String sysId = "";
try {
sysId = splitSys[5];
} catch (Exception e) {
}
String charset = EncodingDetect.javaname[ENCODING_DETECT.detectEncoding(file)];
if (StringUtils.isNotEmpty(charset) && charset.toUpperCase().startsWith("GB")) {
charset = "GBK";
}
reader = IoUtil.getReader(IoUtil.toStream(file), charset);
logger.info("[fileSendRunnable] - 地址 : {}, 大小 :{} ", file.getCanonicalPath(), FileUtil.size(file));
String str = null;
String fileAccount = fileRealName.substring(0, 3);
UserService userService = SpringUtil.getBean(UserServiceImpl.class);
User user = userService.loginByAccount(fileAccount);
if (user == null) {
logger.error("[fileSendRunnable]用户不存在,文件名:{}", filePath);
return;
}
SmsTypeEntity type = null;
ApiSender u = ApiSymbol.API_SENDER_MAP_STA0.get(user.getUserAccount());
String typeId;
if (StringUtils.isNotEmpty(u.getuExt1()) && StringUtils.isNotEmpty((typeId = u.getuExt1().split(",")[0]))) {
type = ApiSymbol.SMS_TYPE_ENTITY_MAP.get(Integer.valueOf(typeId));
if (type == null) {
type = ApiSymbol.SMS_TYPE_ENTITY_MAP.putIfAbsent(Integer.valueOf(typeId), userService.findByTypeId(Integer.valueOf(typeId)));
if (type == null) {
type = ApiSymbol.SMS_TYPE_ENTITY_MAP.get(Integer.valueOf(typeId));
}
}
}
if (type == null) {
logger.error("[fileSendRunnable]用户短信类型为空,文件名:{}", filePath);
return;
}
SmsConfig smsConfig = userService.getSmsConfigByUserId(user.getId());
if (smsConfig == null) {
logger.error("[fileSendRunnable]使用userId:{} 查询b_sms_config中的短信配置找不到,文件名:{}", user.getId(), filePath);
return;
}
SmsTypeService smsTypeService = SpringUtil.getBean(SmsTypeServiceImpl.class);
String smsTypeIdById = userService.getSmsTypeIdById(user.getId());
Integer smsTypeId = Integer.valueOf(smsTypeIdById.split(",")[0]);
Integer priority = smsTypeService.getPriority(smsTypeId);
HashMap<Integer, Approval> approvalMap = new HashMap<>();
while ((str = reader.readLine()) != null) {
String[] split = str.split("\\|");
int count = 0;
String strCopy = str;
while (strCopy.contains("|")) {
strCopy = strCopy.substring(strCopy.indexOf("|") + 1);
++count;
}
if (count != 4 && count != 10) {
logger.warn("[fileSendRunnable] 参数数量不符 | {} | {}", file.getCanonicalPath(), str);
failCount++;
continue;
}
String customerNo = "";
String account = "";
String mobile = "";
String content = "";
String serialNo = "";
String typeStr = "";
String keyCode = "";
String serialNum = "";
String organization = "";
String templatecode = "";
String remark = "";
try {
customerNo = split[0];
account = split[1];
mobile = split[2];
content = split[3];
serialNo = split[4];
typeStr = split[5];
keyCode = split[6];
serialNum = split[7];
organization = split[8];
templatecode = split[9];
remark = split[10];
} catch (Exception e) {
}
if (StrUtil.isBlank(content)) {
logger.warn("[fileSendRunnable] 短信内容为空 | {} | {}", file.getCanonicalPath(), str);
failCount++;
continue;
}
int mobileType = MobileUtilNew.getMobileType(mobile);
if (mobileType == -1) {
logger.warn("[fileSendRunnable] 非法号码 | {} | {}", file.getCanonicalPath(), str);
failCount++;
continue;
}
Integer typeInteger = null;
if (StringUtils.isNotBlank(typeStr)) {
String userType = ApiSymbol.SMS_TYPE_USER_MAP.get(fileAccount);
typeInteger = ApiSymbol.SMS_TYPE_STR_ENTITY_MAP.get(typeStr).getTypeId();
if (Objects.isNull(typeInteger) || StringUtils.isBlank(userType) || !Arrays.asList(userType.split(",")).contains(String.valueOf(typeInteger))) {
logger.warn("[fileSendRunnable] 短信类型不存在 | {} | {}", file.getCanonicalPath(), str);
failCount++;
continue;
}
} else {
typeInteger = type.getTypeId();
}
Approval resultApproval = approvalMap.get(typeInteger);
if (Objects.isNull(resultApproval)) {
resultApproval = new Approval();
String batchNo = BatchNoUtil.nextForOpenApi(FromTypeEnum.FILE_LOAD,IdMangerConfig.serverNodeId);
resultApproval.setBatchNo(batchNo);
String[] arr = SignTextUtil.separateSignAndContent(content, u.getSignType(),u.getSignLocation(),u.getZhSign(),u.getEnSign(), Symbols.defaultGnSign);
resultApproval.setContent(arr[1]);
resultApproval.setSmsSign(arr[0]);
resultApproval.setSmsCreateType(0);
resultApproval.setSmsTypeId(typeInteger);
resultApproval.setPriority(priority);
resultApproval.setUserId(user.getId());
resultApproval.setUserAccount(user.getUserAccount());
resultApproval.setSendType(0);
resultApproval.setSendTime(new Date());
resultApproval.setMsgFromType(FromTypeEnum.FILE_LOAD.getType());
resultApproval.setFileName(fileRealName);
resultApproval.setCreateTime(new Date());
resultApproval.setUpdateTime(new Date());
resultApproval.setTitle(fileRealName);
resultApproval.setSendNum(0);
approvalMap.put(typeInteger, resultApproval);
}
resultApproval.setSendNum(resultApproval.getSendNum() + 1);
if (StrUtil.isBlank(serialNo)) {
serialNo = String.valueOf(ApiSymbol.SMS_ID_Worker.nextId());
}
ChongqingThreeGorgesBankOptField f = new ChongqingThreeGorgesBankOptField();
f.setOptCustno(customerNo);
f.setOptSrvaccno(account);
f.setOptSerialnumber(serialNo);
f.setOptSysid(sysId);
f.setOptSrvid(sysId);
f.setOptTypeStr(typeStr);
f.setOptCoreSummaryCode(keyCode);
f.setOptGlobalSeq(serialNum);
f.setOptOrganization(organization);
f.setOptTemplateCode(templatecode);
f.setOptRemark(remark);
List<ApprovalDetail> details = new ArrayList<>();
ApprovalDetail detail = new ApprovalDetail();
detail.setBatchNo(resultApproval.getBatchNo());
detail.setMobile(mobile);
detail.setMobileType(mobileType);
String[] arr = SignTextUtil.separateSignAndContent(content, u.getSignType(),u.getSignLocation(),u.getZhSign(),u.getEnSign(), Symbols.defaultGnSign);
detail.setContent(arr[2]);
detail.setAccountManagerId("");
detail.setStatus(0);
detail.setCreateTime(new Date());
detail.setUpdateTime(new Date());
detail.setJsonStr(f.optFieldToJSONString());
details.add(detail);
if (CollectionUtil.isEmpty(resultApproval.getDetails())) {
resultApproval.setDetails(details);
} else {
resultApproval.getDetails().addAll(details);
}
}
final int[] successCount = {0};
for (Map.Entry<Integer, Approval> entry : approvalMap.entrySet()) {
QueueMgr.instance.putApprovalCacheQueue(new ApprovalQueue() {{
successCount[0] = successCount[0] + entry.getValue().getDetails().size();
setQueue(entry.getValue());
setQueueDetails(entry.getValue().getDetails());
}});
}
logger.info("[fileSendRunnable] - " + file.getCanonicalPath() + ",解析失败:" + failCount + "条" + " , 解析成功: " + successCount[0] + "条");
} catch (Exception e) {
logger.error("[fileSendRunnable]文件处理有误", e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
logger.error("[fileSendRunnable]", e);
}
}
}
}
private void blockCheckFtpFileComplete(File file, int timeOutMillis) {
logger.info("[fileSendRunnable] 检测文件移动是否完整 | START | path:{}", filePath);
long firstCheckTime = System.currentTimeMillis();
String lastHex = "";
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (Exception ignored) {
}
try (FileInputStream fis = new FileInputStream(file)) {
String tmpHex = DigestUtils.md5DigestAsHex(fis);
if (lastHex.equals(tmpHex)) {
logger.info("[fileSendRunnable] 检测文件移动是否完整 | END | 【sta:正常完毕】path:{} | currentLen:{} | hex:{}", filePath, file.length(), lastHex);
break;
}
if (System.currentTimeMillis() - firstCheckTime >= timeOutMillis) {
logger.info("[fileSendRunnable] 检测文件移动是否完整 | END | 【sta:超时完毕】path:{} | currentLen:{} | hex:{}", filePath, file.length(), lastHex);
break;
}
lastHex = tmpHex;
} catch (Exception e) {
logger.info("[fileSendRunnable] 检测文件移动是否完整 | END | 【sta:异常完毕】path:{} | currentLen:{} | hex:{}", filePath, file.length(), lastHex);
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (Exception ignored) {
}
break;
}
}
}
}