多线程事务回滚

TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(

最近开发,有地方需要用到多线程,每个线程里面处理多个方法,过程中遇到了一个问题,我们使用平时的@Transactional注解,就是当前一个方法执行完成(比如插入操作),后一个方法是不会事务回滚的。当时觉得很不可思议,后来经过半天时间,终于挖出原因,并成功解决。
我这里先说明原因:多线程底层连接数据库的时候,时使用的线程变量(TheadLocal),所以,开多少线程理论上就会建立多少个连接,每个线程有自己的连接,事务肯定不是同一个了。
解决办法:我强制手动把每个线程的事务状态放到一个同步集合里面。然后如果有单个异常,循环回滚每个线程。
代码如下:

1、注入

     @Autowired
     private PlatformTransactionManager transactionManager;

2、多线程操作

@Override
    public String importBatch(String url,String taskId) {
        if (StringUtils.isEmpty(taskId)){
            return "请选择外呼任务";
        }

        Task task = taskService.selectById(taskId);
        if (task == null){
            return "外呼任务不存在,Id为:"+taskId;
        }

        String filePath = tmpFilePath + url;
        List<Customer> customers = ExcelTemplateExportUtil.importExcel(filePath, 0, 1, Customer.class);
        if (CollectionUtils.isEmpty(customers)){
            return "Excel数据不能为空";
        }

        int totalCustomerCnt = customers.size();
        if (totalCustomerCnt > 5000){
            return "最多支持5000条,请分批导入";
        }

        //过滤掉空属性
        List<String> phones = new ArrayList<>();
        List<Customer> customersNotNull = new ArrayList<>();
        String corpCode = ExecutionContext.getCorpCode();
        String userId = ExecutionContext.getUserId();
        for (Customer customer : customers) {
            String phone = customer.getPhone();
            if (StringUtils.isEmpty(customer.getName()) || StringUtils.isEmpty(phone)){
                continue;
            }

            customersNotNull.add(customer);
            phones.add(phone);
        }

        String result = "";
        int notNullSize = customersNotNull.size();
        int size = customers.size();
        if (notNullSize < size){
            result += "存在客户名称或手机号码为空:"+(size - notNullSize)+"条记录";
        }

        //过滤手机号重复的
        List<Customer> validCustomers = new ArrayList<>();
        List<Customer> repeatCustomers = this.selectList(new EntityWrapper<Customer>().eq("corp_code", corpCode).in("phone", phones));
        if (CollectionUtils.isNotEmpty(repeatCustomers)){
            result += "<br/>存在手机号码重复:"+(repeatCustomers.size())+"条记录";
            List<String> repeatPhones = new ArrayList<>(repeatCustomers.size());
            for (Customer repeatCustomer : repeatCustomers) {
                repeatPhones.add(repeatCustomer.getPhone());
            }

            for (Customer customer : customersNotNull) {
                if (repeatPhones.contains(customer.getPhone())){
                    continue;
                }

                validCustomers.add(customer);
            }
        }else {
            validCustomers = customersNotNull;
        }

        for (Customer validCustomer : validCustomers) {
            validCustomer.setCustomerType(UkConstant.CUSTOMER_TYPE_INDIVIDUALITY);
            validCustomer.setChannelType(UkConstant.CHANNEL_TYPE_CALL_OUT);
            validCustomer.setCalloutTaskStatus(0);
            validCustomer.setCalloutDistributeStatus(0);
            validCustomer.setCalloutTaskId(taskId);
            validCustomer.setCorpCode(corpCode);
            validCustomer.setCreater(userId);
        }

        if (CollectionUtils.isEmpty(validCustomers)){
            return "成功导入:"+ 0 +"条记录<br/>"+result;
        }


        List<List<Customer>> customersList = new ArrayList<>();
        int total = validCustomers.size();
        int threads = 5;
        int oneSize = total/5 +1;
        int start = 0;
        int end = 0;

        for (int i = 0; i <threads ; i++) {
            start = i * oneSize;
            end = (i+1)*oneSize;
            if (i<threads-1){
                customersList.add(validCustomers.subList(start,end));
            }else {
                customersList.add(validCustomers.subList(start,validCustomers.size()));
            }
        }

        //先在开启多线程外面,定义一个同步集合:
        List<TransactionStatus> transactionStatuses = Collections.synchronizedList(new ArrayList<TransactionStatus>());
        CountDownLatch latch= new CountDownLatch(threads);

        for (int i = 0; i < threads; i++) {
            int finalI = i;
            ThreadPoolUtils.fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
                    TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
                    transactionStatuses.add(status);
                    try{
//执行业务逻辑  插入或更新操作
                        improtInsertBath(corpCode,userId,customersList.get(finalI));
                    }catch(Exception e){
                        e.printStackTrace();
//异常 回滚所有事务
                        for (TransactionStatus transactionStatus:transactionStatuses) {
                            transactionStatus.setRollbackOnly();
                        }

                    }


                    latch.countDown();
                }
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }



        //更新外呼任务客户总数 customerCnt、未分配客户数 notDistributeCnt、未拨打数 not_call_cnt
        task.setCustomerCnt(task.getCustomerCnt()+total);
        task.setNotDistributeCnt(task.getNotDistributeCnt()+total);
        task.setNotCallCnt(task.getNotCallCnt()+total);
        taskService.updateById(task);
        return "成功导入:"+ total +"条记录<br/>"+result;
    }

 

3、整体类

package com.ps.uzkefu.apps.crm.service.impl;

import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.ps.uzkefu.apps.callout.entity.Task;
import com.ps.uzkefu.apps.callout.service.TaskService;
import com.ps.uzkefu.apps.crm.entity.Customer;
import com.ps.uzkefu.apps.crm.entity.CustomerRecord;
import com.ps.uzkefu.apps.crm.mapper.CustomerDao;
import com.ps.uzkefu.apps.crm.service.CustomerRecordService;
import com.ps.uzkefu.apps.crm.service.CustomerService;
import com.ps.uzkefu.apps.oms.account.entity.DefinedField;
import com.ps.uzkefu.apps.oms.account.entity.DefinedFieldOption;
import com.ps.uzkefu.apps.oms.account.entity.DefinedFieldValue;
import com.ps.uzkefu.apps.oms.account.service.DefinedFieldOptionService;
import com.ps.uzkefu.apps.oms.account.service.DefinedFieldService;
import com.ps.uzkefu.apps.oms.account.service.DefinedFieldValueService;
import com.ps.uzkefu.apps.oms.account.service.UserService;
import com.ps.uzkefu.base.BaseServiceImpl;
import com.ps.uzkefu.common.ExecutionContext;
import com.ps.uzkefu.common.UkConstant;
import com.ps.uzkefu.util.DateUtil;
import com.ps.uzkefu.util.ExcelTemplateExportUtil;
import com.ps.uzkefu.util.ThreadPoolUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.BatchExecutorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.sql.BatchUpdateException;
import java.util.*;
import java.util.concurrent.CountDownLatch;

/**
 * <p>
 * 客户 服务实现类
 * </p>
 *
 * @author WuZhiWei
 * @since 2018-07-06
 */
@Transactional(rollbackFor = {Exception.class})
@Service
public class CustomerServiceImpl extends BaseServiceImpl<CustomerDao, Customer> implements CustomerService {

    @Autowired
    private DefinedFieldValueService definedFieldValueService;
    @Autowired
    private DefinedFieldService definedFieldService;
    @Autowired
    private DefinedFieldOptionService definedFieldOptionService;

    @Autowired
    private CustomerRecordService customerRecordService;
    @Autowired
    private UserService userService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private PlatformTransactionManager transactionManager;





    @Value("${tmp.file.path}")
    private String tmpFilePath;


    @Override
    public String saveOrUpdate(Customer customer) {
        String id = customer.getId();
        String phone = customer.getPhone();
        String corpCode = ExecutionContext.getCorpCode();
        customer.setCorpCode(corpCode);
        if (StringUtils.isEmpty(id)){
            Customer selectOne = this.selectOne(new EntityWrapper<Customer>().
                    eq("corp_code", corpCode).eq("phone", phone));
            if (selectOne != null){
                return "联系号码已存在";
            }
        }

        Customer selectOne = this.selectOne(new EntityWrapper<Customer>().
                eq("corp_code", corpCode).eq("phone", phone)
                .where("id !={0}",id));
        if (selectOne != null){
            return "联系号码已存在";
        }

        if (StringUtils.isEmpty(customer.getCorpName())){
            customer.setCustomerType(UkConstant.CUSTOMER_TYPE_INDIVIDUALITY);
        }else {
            customer.setCustomerType(UkConstant.CUSTOMER_TYPE_CORP);
        }

        if (StringUtils.isEmpty(customer.getUserId())){
            customer.setUserId(ExecutionContext.getUserId());
        }

        //自定义属性
        List<DefinedFieldValue> definedFieldValues = customer.getDefinedFieldValues();

        //变更记录
        String updateRecord = getUpdateRecord(customer, id, corpCode, definedFieldValues);
        customer.setAssignDate(new Date());
        this.insertOrUpdate(customer);
        if (StringUtils.isEmpty(id)){
            id = customer.getId();
        }

        if (StringUtils.isNotEmpty(updateRecord)){
            CustomerRecord customerRecord = new CustomerRecord(UkConstant.RECORD_TYPE_UPDATE,updateRecord,null,null,null,id);
            customerRecordService.insert(customerRecord);
        }

        //自定义保存或更新处理
        if (CollectionUtils.isNotEmpty(definedFieldValues)){
            for (DefinedFieldValue fieldValue : definedFieldValues) {
                fieldValue.setResourceId(id);
            }

            definedFieldValueService.delete(new EntityWrapper<DefinedFieldValue>().eq("resource_id",id));
            definedFieldValueService.insertBatch(definedFieldValues);
        }

        return "";
    }

    private String getUpdateRecord(Customer customer, String id, String corpCode, List<DefinedFieldValue> definedFieldValues) {
        String recordContent = "";
        if (StringUtils.isNotEmpty(id)){
            Customer customerDb = this.selectById(id);
            Map<String, String> uidNameMap = userService.getAllUidNameMap();
            recordContent = dealUpdate("客户姓名",customer.getName(),customerDb.getName(),recordContent);
            recordContent = dealUpdate("公司名称",customer.getCorpName(),customerDb.getCorpName(),recordContent);
            recordContent =dealUpdate("联系号码",customer.getPhone(),customerDb.getPhone(),recordContent);
            recordContent =dealUpdate("联系邮箱",customer.getEmail(),customerDb.getEmail(),recordContent);
            recordContent =dealUpdate("微信",customer.getWx(),customerDb.getWx(),recordContent);
            recordContent =dealUpdate("QQ",customer.getQq(),customerDb.getQq(),recordContent);
            recordContent =dealUpdate("所在省",customer.getProvince(),customerDb.getProvince(),recordContent);
            recordContent =dealUpdate("所在市",customer.getCity(),customerDb.getCity(),recordContent);
            recordContent =dealUpdate("描述",customer.getDescription(),customerDb.getDescription(),recordContent);
            recordContent =dealUpdate("所属员工",uidNameMap.get(customer.getUserId()),uidNameMap.get(customerDb.getUserId()),recordContent);
            if (CollectionUtils.isNotEmpty(definedFieldValues)){
                Map<String,String> fieldIdValueMap = new HashMap<>();
                for (DefinedFieldValue fieldValue : definedFieldValues) {
                    fieldIdValueMap.put(fieldValue.getFieldId(),fieldValue.getFieldValue());
                }

                Map<String,String> fieldIdValueMapDb = new HashMap<>();
                List<DefinedFieldValue> definedFieldValuesDb = definedFieldValueService.selectList(new EntityWrapper<DefinedFieldValue>().
                        eq("corp_code", corpCode).eq("resource_id", id));
                if (CollectionUtils.isNotEmpty(definedFieldValuesDb)){
                    for (DefinedFieldValue fieldValue : definedFieldValuesDb) {
                        fieldIdValueMapDb.put(fieldValue.getFieldId(),fieldValue.getFieldValue());
                    }
                }

                List<DefinedField> definedFields = definedFieldService.selectList(new EntityWrapper<DefinedField>().
                        eq("corp_code", corpCode).eq("biz_type", UkConstant.DEFINED_BIZ_TYPE_CRM));

                List<DefinedFieldOption> fieldOptions = definedFieldOptionService.selectList(new EntityWrapper<DefinedFieldOption>().eq("corp_code", corpCode));
                Map<String,String> optionIdNameMap;
                if (CollectionUtils.isEmpty(fieldOptions)){
                    optionIdNameMap = MapUtils.EMPTY_MAP;
                }else {
                    optionIdNameMap = new HashMap<>(fieldOptions.size());
                }

                for (DefinedFieldOption option : fieldOptions) {
                    optionIdNameMap.put(option.getId(),option.getOptionName());
                }

                if (CollectionUtils.isNotEmpty(definedFields)){
                    for (DefinedField field : definedFields) {
                        String fieldName = field.getFieldName();
                        String fieldType = field.getFieldType();
                        String fieldId = field.getId();
                        String pre = fieldIdValueMapDb.get(fieldId);
                        if (pre == null){
                            pre = "";
                        }

                        String now = fieldIdValueMap.get(fieldId);
                        if (now == null){
                            now = "";
                        }

                        if (UkConstant.DEFINED_FIELD_TYPE_SELECT.equals(fieldType) ||
                                UkConstant.DEFINED_FIELD_TYPE_RADIO.equals(fieldType) ||
                                UkConstant.DEFINED_FIELD_TYPE_CHECKBOX.equals(fieldType)){
                            pre = optionIdNameMap.get(pre);
                            now = optionIdNameMap.get(now);
                        }

                        recordContent = dealUpdate(fieldName,now,pre,recordContent);
                    }
                }
            }
        }else {
            recordContent = "创建了客户";
        }

        return recordContent;
    }

    private String dealUpdate(String label,String now,String pre,String result){
        String preStr = pre;
        if (StringUtils.isEmpty(pre)){
            pre = "";
            preStr = "<空>";
        }

        String nowStr = now;
        if (StringUtils.isEmpty(now)){
            now = "";
            nowStr = "<空>";
        }

        if (now.equals(pre)){
            return result;
        }

        result += label + ":" + preStr + "-->" + nowStr+"</br>";
        return result;
    }


    @Override
    public String importBatch(String url,String taskId) {
        if (StringUtils.isEmpty(taskId)){
            return "请选择外呼任务";
        }

        Task task = taskService.selectById(taskId);
        if (task == null){
            return "外呼任务不存在,Id为:"+taskId;
        }

        String filePath = tmpFilePath + url;
        List<Customer> customers = ExcelTemplateExportUtil.importExcel(filePath, 0, 1, Customer.class);
        if (CollectionUtils.isEmpty(customers)){
            return "Excel数据不能为空";
        }

        int totalCustomerCnt = customers.size();
        if (totalCustomerCnt > 5000){
            return "最多支持5000条,请分批导入";
        }

        //过滤掉空属性
        List<String> phones = new ArrayList<>();
        List<Customer> customersNotNull = new ArrayList<>();
        String corpCode = ExecutionContext.getCorpCode();
        String userId = ExecutionContext.getUserId();
        for (Customer customer : customers) {
            String phone = customer.getPhone();
            if (StringUtils.isEmpty(customer.getName()) || StringUtils.isEmpty(phone)){
                continue;
            }

            customersNotNull.add(customer);
            phones.add(phone);
        }

        String result = "";
        int notNullSize = customersNotNull.size();
        int size = customers.size();
        if (notNullSize < size){
            result += "存在客户名称或手机号码为空:"+(size - notNullSize)+"条记录";
        }

        //过滤手机号重复的
        List<Customer> validCustomers = new ArrayList<>();
        List<Customer> repeatCustomers = this.selectList(new EntityWrapper<Customer>().eq("corp_code", corpCode).in("phone", phones));
        if (CollectionUtils.isNotEmpty(repeatCustomers)){
            result += "<br/>存在手机号码重复:"+(repeatCustomers.size())+"条记录";
            List<String> repeatPhones = new ArrayList<>(repeatCustomers.size());
            for (Customer repeatCustomer : repeatCustomers) {
                repeatPhones.add(repeatCustomer.getPhone());
            }

            for (Customer customer : customersNotNull) {
                if (repeatPhones.contains(customer.getPhone())){
                    continue;
                }

                validCustomers.add(customer);
            }
        }else {
            validCustomers = customersNotNull;
        }

        for (Customer validCustomer : validCustomers) {
            validCustomer.setCustomerType(UkConstant.CUSTOMER_TYPE_INDIVIDUALITY);
            validCustomer.setChannelType(UkConstant.CHANNEL_TYPE_CALL_OUT);
            validCustomer.setCalloutTaskStatus(0);
            validCustomer.setCalloutDistributeStatus(0);
            validCustomer.setCalloutTaskId(taskId);
            validCustomer.setCorpCode(corpCode);
            validCustomer.setCreater(userId);
        }

        if (CollectionUtils.isEmpty(validCustomers)){
            return "成功导入:"+ 0 +"条记录<br/>"+result;
        }


        List<List<Customer>> customersList = new ArrayList<>();
        int total = validCustomers.size();
        int threads = 5;
        int oneSize = total/5 +1;
        int start = 0;
        int end = 0;

        for (int i = 0; i <threads ; i++) {
            start = i * oneSize;
            end = (i+1)*oneSize;
            if (i<threads-1){
                customersList.add(validCustomers.subList(start,end));
            }else {
                customersList.add(validCustomers.subList(start,validCustomers.size()));
            }
        }

        //先在开启多线程外面,定义一个同步集合:
        List<TransactionStatus> transactionStatuses = Collections.synchronizedList(new ArrayList<TransactionStatus>());
        CountDownLatch latch= new CountDownLatch(threads);

        for (int i = 0; i < threads; i++) {
            int finalI = i;
            ThreadPoolUtils.fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
                    TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
                    transactionStatuses.add(status);
                    try{
                        improtInsertBath(corpCode,userId,customersList.get(finalI));
                    }catch(Exception e){
                        e.printStackTrace();
                        for (TransactionStatus transactionStatus:transactionStatuses) {
                            transactionStatus.setRollbackOnly();
                        }

                    }


                    latch.countDown();
                }
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }



        //更新外呼任务客户总数 customerCnt、未分配客户数 notDistributeCnt、未拨打数 not_call_cnt
        task.setCustomerCnt(task.getCustomerCnt()+total);
        task.setNotDistributeCnt(task.getNotDistributeCnt()+total);
        task.setNotCallCnt(task.getNotCallCnt()+total);
        taskService.updateById(task);
        return "成功导入:"+ total +"条记录<br/>"+result;
    }

    @Override
    public Page<Customer> callOutTask(Page page, Customer customer) {

        if (StringUtils.isNotBlank(customer.getAssignTimeStart())){
            String start = customer.getAssignTimeStart().substring(0,10)+" 00:00:00";
            String end = customer.getAssignTimeStart().substring(13)+" 23:59:59";
           customer.setAssignTimeStart(start);
           customer.setAssignTimeEnd(end);
        }else {
            customer.setAssignTimeEnd(DateUtil.getConvertDateFormat("yyyy-MM-dd HH:mm:ss",new Date()));
        }


        if (StringUtils.isNotBlank(customer.getCalloutTimeStart())){
            String start = customer.getCalloutTimeStart().substring(0,10)+" 00:00:00";
            String end = customer.getCalloutTimeStart().substring(13)+" 23:59:59";
            customer.setCalloutTimeStart(start);
            customer.setCalloutTimeEnd(end);
        }
        List<String> ids = new ArrayList<>();
        if (StringUtils.isNotBlank(customer.getHasLabel())){
            String[] ss = customer.getHasLabel().substring(1).split("\\,");
            for (String id:ss) {
                ids.add(id);
            }
            customer.setLabelIds(ids);
        }
        customer.setCorpCode(ExecutionContext.getCorpCode());
        return  page.setRecords(this.baseMapper.callOutTask(page, customer));
    }

    @Override
    public Map<String, Integer> getCallOutTaskStatistics(String userId,List<String> taskIds) {
        List<Customer> customers = customerDao.getCallOutTaskStatistics(ExecutionContext.getCorpCode(), userId, taskIds);
        if (CollectionUtils.isEmpty(customers)){
            return MapUtils.EMPTY_MAP;
        }

        Map<String,Integer> taskIdStatusCntMap = new HashMap<>(customers.size());
        for (Customer customer : customers) {
            taskIdStatusCntMap.put(customer.getCalloutTaskId()+"-"+customer.getCalloutTaskStatus(),customer.getCalloutTaskStatusCnt());
        }

        return taskIdStatusCntMap;
    }

    @Override
    public Page<Customer> findPageForProcess(Page page, Customer customer) {
        List<Customer> records = customerDao.findPageForProcess(page, customer);
        return page.setRecords(records);
    }
    @Transactional(rollbackFor = {MybatisPlusException.class})
    @Override
    public void  improtInsertBath(String corpCode,String userId,List<Customer> list){

        insertBatch(list);//TODO 检查validCustomers,一个线程1K条数据导入
        //处理客户轨迹记录

        int validCustomerSize = list.size();
        List<CustomerRecord> customerRecords = new ArrayList<>(validCustomerSize);
        for (Customer validCustomer : list) {
            CustomerRecord customerRecord = new CustomerRecord(UkConstant.RECORD_TYPE_UPDATE,"导入了客户",null,null,null,validCustomer.getId());
            customerRecord.setCreater(userId);
            customerRecord.setCorpCode(corpCode);
            customerRecords.add(customerRecord);
        }
        boolean result = customerRecordService.insertBatch(customerRecords);
    }
}

以上在测试环境,数据插入ok,上生产

数据无法插入数据库,可能原因 事务未提交 手动提交

 

代码如下

package com.ps.uzkefu.apps.crm.service.impl;

import com.baomidou.mybatisplus.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.ps.uzkefu.apps.callout.entity.Task;
import com.ps.uzkefu.apps.callout.service.TaskService;
import com.ps.uzkefu.apps.crm.entity.Customer;
import com.ps.uzkefu.apps.crm.entity.CustomerRecord;
import com.ps.uzkefu.apps.crm.mapper.CustomerDao;
import com.ps.uzkefu.apps.crm.service.CustomerRecordService;
import com.ps.uzkefu.apps.crm.service.CustomerService;
import com.ps.uzkefu.apps.oms.account.entity.DefinedField;
import com.ps.uzkefu.apps.oms.account.entity.DefinedFieldOption;
import com.ps.uzkefu.apps.oms.account.entity.DefinedFieldValue;
import com.ps.uzkefu.apps.oms.account.service.DefinedFieldOptionService;
import com.ps.uzkefu.apps.oms.account.service.DefinedFieldService;
import com.ps.uzkefu.apps.oms.account.service.DefinedFieldValueService;
import com.ps.uzkefu.apps.oms.account.service.UserService;
import com.ps.uzkefu.base.BaseServiceImpl;
import com.ps.uzkefu.common.ExecutionContext;
import com.ps.uzkefu.common.UkConstant;
import com.ps.uzkefu.util.DateUtil;
import com.ps.uzkefu.util.ExcelTemplateExportUtil;
import com.ps.uzkefu.util.ThreadPoolUtils;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.ibatis.executor.BatchExecutorException;
import org.apache.ibatis.transaction.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import java.sql.BatchUpdateException;
import java.util.*;
import java.util.concurrent.CountDownLatch;

/**
 * <p>
 * 客户 服务实现类
 * </p>
 *
 * @author WuZhiWei
 * @since 2018-07-06
 */
@Transactional(rollbackFor = {Exception.class})
@Service
public class CustomerServiceImpl extends BaseServiceImpl<CustomerDao, Customer> implements CustomerService {

    @Autowired
    private DefinedFieldValueService definedFieldValueService;
    @Autowired
    private DefinedFieldService definedFieldService;
    @Autowired
    private DefinedFieldOptionService definedFieldOptionService;

    @Autowired
    private CustomerRecordService customerRecordService;
    @Autowired
    private UserService userService;
    @Autowired
    private TaskService taskService;
    @Autowired
    private CustomerDao customerDao;

    @Autowired
    private PlatformTransactionManager transactionManager;





    @Value("${tmp.file.path}")
    private String tmpFilePath;


    @Override
    public String saveOrUpdate(Customer customer) {
        String id = customer.getId();
        String phone = customer.getPhone();
        String corpCode = ExecutionContext.getCorpCode();
        customer.setCorpCode(corpCode);
        if (StringUtils.isEmpty(id)){
            Customer selectOne = this.selectOne(new EntityWrapper<Customer>().
                    eq("corp_code", corpCode).eq("phone", phone));
            if (selectOne != null){
                return "联系号码已存在";
            }
        }

        Customer selectOne = this.selectOne(new EntityWrapper<Customer>().
                eq("corp_code", corpCode).eq("phone", phone)
                .where("id !={0}",id));
        if (selectOne != null){
            return "联系号码已存在";
        }

        if (StringUtils.isEmpty(customer.getCorpName())){
            customer.setCustomerType(UkConstant.CUSTOMER_TYPE_INDIVIDUALITY);
        }else {
            customer.setCustomerType(UkConstant.CUSTOMER_TYPE_CORP);
        }

        if (StringUtils.isEmpty(customer.getUserId())){
            customer.setUserId(ExecutionContext.getUserId());
        }

        //自定义属性
        List<DefinedFieldValue> definedFieldValues = customer.getDefinedFieldValues();

        //变更记录
        String updateRecord = getUpdateRecord(customer, id, corpCode, definedFieldValues);
        customer.setAssignDate(new Date());
        this.insertOrUpdate(customer);
        if (StringUtils.isEmpty(id)){
            id = customer.getId();
        }

        if (StringUtils.isNotEmpty(updateRecord)){
            CustomerRecord customerRecord = new CustomerRecord(UkConstant.RECORD_TYPE_UPDATE,updateRecord,null,null,null,id);
            customerRecordService.insert(customerRecord);
        }

        //自定义保存或更新处理
        if (CollectionUtils.isNotEmpty(definedFieldValues)){
            for (DefinedFieldValue fieldValue : definedFieldValues) {
                fieldValue.setResourceId(id);
            }

            definedFieldValueService.delete(new EntityWrapper<DefinedFieldValue>().eq("resource_id",id));
            definedFieldValueService.insertBatch(definedFieldValues);
        }

        return "";
    }

    private String getUpdateRecord(Customer customer, String id, String corpCode, List<DefinedFieldValue> definedFieldValues) {
        String recordContent = "";
        if (StringUtils.isNotEmpty(id)){
            Customer customerDb = this.selectById(id);
            Map<String, String> uidNameMap = userService.getAllUidNameMap();
            recordContent = dealUpdate("客户姓名",customer.getName(),customerDb.getName(),recordContent);
            recordContent = dealUpdate("公司名称",customer.getCorpName(),customerDb.getCorpName(),recordContent);
            if (StringUtils.isNotEmpty(customer.getPhone())){
                recordContent =dealUpdate("联系号码",customer.getPhone(),customerDb.getPhone(),recordContent);
            }

            recordContent =dealUpdate("联系邮箱",customer.getEmail(),customerDb.getEmail(),recordContent);
            recordContent =dealUpdate("微信",customer.getWx(),customerDb.getWx(),recordContent);
            recordContent =dealUpdate("QQ",customer.getQq(),customerDb.getQq(),recordContent);
            recordContent =dealUpdate("所在省",customer.getProvince(),customerDb.getProvince(),recordContent);
            recordContent =dealUpdate("所在市",customer.getCity(),customerDb.getCity(),recordContent);
            recordContent =dealUpdate("描述",customer.getDescription(),customerDb.getDescription(),recordContent);
            recordContent =dealUpdate("所属员工",uidNameMap.get(customer.getUserId()),uidNameMap.get(customerDb.getUserId()),recordContent);
            if (CollectionUtils.isNotEmpty(definedFieldValues)){
                Map<String,String> fieldIdValueMap = new HashMap<>();
                for (DefinedFieldValue fieldValue : definedFieldValues) {
                    fieldIdValueMap.put(fieldValue.getFieldId(),fieldValue.getFieldValue());
                }

                Map<String,String> fieldIdValueMapDb = new HashMap<>();
                List<DefinedFieldValue> definedFieldValuesDb = definedFieldValueService.selectList(new EntityWrapper<DefinedFieldValue>().
                        eq("corp_code", corpCode).eq("resource_id", id));
                if (CollectionUtils.isNotEmpty(definedFieldValuesDb)){
                    for (DefinedFieldValue fieldValue : definedFieldValuesDb) {
                        fieldIdValueMapDb.put(fieldValue.getFieldId(),fieldValue.getFieldValue());
                    }
                }

                List<DefinedField> definedFields = definedFieldService.selectList(new EntityWrapper<DefinedField>().
                        eq("corp_code", corpCode).eq("biz_type", UkConstant.DEFINED_BIZ_TYPE_CRM));

                List<DefinedFieldOption> fieldOptions = definedFieldOptionService.selectList(new EntityWrapper<DefinedFieldOption>().eq("corp_code", corpCode));
                Map<String,String> optionIdNameMap;
                if (CollectionUtils.isEmpty(fieldOptions)){
                    optionIdNameMap = MapUtils.EMPTY_MAP;
                }else {
                    optionIdNameMap = new HashMap<>(fieldOptions.size());
                }

                for (DefinedFieldOption option : fieldOptions) {
                    optionIdNameMap.put(option.getId(),option.getOptionName());
                }

                if (CollectionUtils.isNotEmpty(definedFields)){
                    for (DefinedField field : definedFields) {
                        String fieldName = field.getFieldName();
                        String fieldType = field.getFieldType();
                        String fieldId = field.getId();
                        String pre = fieldIdValueMapDb.get(fieldId);
                        if (pre == null){
                            pre = "";
                        }

                        String now = fieldIdValueMap.get(fieldId);
                        if (now == null){
                            now = "";
                        }

                        if (UkConstant.DEFINED_FIELD_TYPE_SELECT.equals(fieldType) ||
                                UkConstant.DEFINED_FIELD_TYPE_RADIO.equals(fieldType) ||
                                UkConstant.DEFINED_FIELD_TYPE_CHECKBOX.equals(fieldType)){
                            pre = optionIdNameMap.get(pre);
                            now = optionIdNameMap.get(now);
                        }

                        recordContent = dealUpdate(fieldName,now,pre,recordContent);
                    }
                }
            }
        }else {
            recordContent = "创建了客户";
        }

        return recordContent;
    }

    private String dealUpdate(String label,String now,String pre,String result){
        String preStr = pre;
        if (StringUtils.isEmpty(pre)){
            pre = "";
            preStr = "<空>";
        }

        String nowStr = now;
        if (StringUtils.isEmpty(now)){
            now = "";
            nowStr = "<空>";
        }

        if (now.equals(pre)){
            return result;
        }

        result += label + ":" + preStr + "-->" + nowStr+"</br>";
        return result;
    }


    @Override
    public String importBatch(String url,String taskId) {
        if (StringUtils.isEmpty(taskId)){
            return "请选择外呼任务";
        }

        Task task = taskService.selectById(taskId);
        if (task == null){
            return "外呼任务不存在,Id为:"+taskId;
        }

        String filePath = tmpFilePath + url;
        List<Customer> customers = ExcelTemplateExportUtil.importExcel(filePath, 0, 1, Customer.class);
        if (CollectionUtils.isEmpty(customers)){
            return "Excel数据不能为空";
        }

        int totalCustomerCnt = customers.size();
        if (totalCustomerCnt > 5000){
            return "最多支持5000条,请分批导入";
        }

        //过滤掉空属性
        List<String> phones = new ArrayList<>();
        List<Customer> customersNotNull = new ArrayList<>();
        String corpCode = ExecutionContext.getCorpCode();
        String userId = ExecutionContext.getUserId();
        for (Customer customer : customers) {
            String phone = customer.getPhone();
            if (StringUtils.isEmpty(customer.getName()) || StringUtils.isEmpty(phone)){
                continue;
            }

            customersNotNull.add(customer);
            phones.add(phone);
        }

        String result = "";
        int notNullSize = customersNotNull.size();
        int size = customers.size();
        if (notNullSize < size){
            result += "存在客户名称或手机号码为空:"+(size - notNullSize)+"条记录";
        }

        //过滤手机号重复的
        List<Customer> validCustomers = new ArrayList<>();
        List<Customer> repeatCustomers = this.selectList(new EntityWrapper<Customer>().eq("corp_code", corpCode).in("phone", phones));
        if (CollectionUtils.isNotEmpty(repeatCustomers)){
            result += "<br/>存在手机号码重复:"+(repeatCustomers.size())+"条记录";
            List<String> repeatPhones = new ArrayList<>(repeatCustomers.size());
            for (Customer repeatCustomer : repeatCustomers) {
                repeatPhones.add(repeatCustomer.getPhone());
            }

            for (Customer customer : customersNotNull) {
                if (repeatPhones.contains(customer.getPhone())){
                    continue;
                }

                validCustomers.add(customer);
            }
        }else {
            validCustomers = customersNotNull;
        }

        for (Customer validCustomer : validCustomers) {
            validCustomer.setCustomerType(UkConstant.CUSTOMER_TYPE_INDIVIDUALITY);
            validCustomer.setChannelType(UkConstant.CHANNEL_TYPE_CALL_OUT);
            validCustomer.setCalloutTaskStatus(0);
            validCustomer.setCalloutDistributeStatus(0);
            validCustomer.setCalloutTaskId(taskId);
            validCustomer.setCorpCode(corpCode);
            validCustomer.setCreater(userId);
        }

        if (CollectionUtils.isEmpty(validCustomers)){
            return "成功导入:"+ 0 +"条记录<br/>"+result;
        }


        List<List<Customer>> customersList = new ArrayList<>();
        int total = validCustomers.size();
        int threads = 1;
        if (total >=1000){
            threads = 5;
        }
        int oneSize = total/threads +1;
        int start = 0;
        int end = 0;

        for (int i = 0; i <threads ; i++) {
            start = i * oneSize;
            end = (i+1)*oneSize;
            if (i<threads-1){
                customersList.add(validCustomers.subList(start,end));
            }else {
                customersList.add(validCustomers.subList(start,validCustomers.size()));
            }
        }

        //先在开启多线程外面,定义一个同步集合:
        List<TransactionStatus> transactionStatuses = Collections.synchronizedList(new ArrayList<TransactionStatus>());
        CountDownLatch latch= new CountDownLatch(threads);
        Map<String,Integer> totalMap = new HashMap();


        for (int i = 0; i < threads; i++) {
            int finalI = i;
            ThreadPoolUtils.fixedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
                    TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
                    transactionStatuses.add(status);
                    try{
                        improtInsertBath(corpCode,userId,customersList.get(finalI));
                        totalMap.put("total"+finalI,customersList.get(finalI).size());
                        //提交事务
                        transactionManager.commit(status);
                    }catch(Exception e){
                        e.printStackTrace();
                        //回滚 事务  这样做一个线程出问题 只能回滚出问题的线程
                        transactionManager.rollback(status);
                    }

                    latch.countDown();
                }
            });
        }
        try {
//            for (TransactionStatus transactionStatus:transactionStatuses) {
//                transactionManager.commit(transactionStatus);
//            }
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int countTotal = 0;
        for (Integer count: totalMap.values()) {
            countTotal = countTotal+count;
        }
        //更新外呼任务客户总数 customerCnt、未分配客户数 notDistributeCnt、未拨打数 not_call_cnt
        task.setCustomerCnt(task.getCustomerCnt()+countTotal);
        task.setNotDistributeCnt(task.getNotDistributeCnt()+countTotal);
        task.setNotCallCnt(task.getNotCallCnt()+countTotal);

        taskService.updateById(task);

        return "成功导入:"+ countTotal +"条记录<br/>"+result;
    }

    @Override
    public Page<Customer> callOutTask(Page page, Customer customer) {

        if (StringUtils.isNotBlank(customer.getAssignTimeStart())){
            String start = customer.getAssignTimeStart().substring(0,10)+" 00:00:00";
            String end = customer.getAssignTimeStart().substring(13)+" 23:59:59";
           customer.setAssignTimeStart(start);
           customer.setAssignTimeEnd(end);
        }else {
            customer.setAssignTimeEnd(DateUtil.getConvertDateFormat("yyyy-MM-dd HH:mm:ss",new Date()));
        }


        if (StringUtils.isNotBlank(customer.getCalloutTimeStart())){
            String start = customer.getCalloutTimeStart().substring(0,10)+" 00:00:00";
            String end = customer.getCalloutTimeStart().substring(13)+" 23:59:59";
            customer.setCalloutTimeStart(start);
            customer.setCalloutTimeEnd(end);
        }
        List<String> ids = new ArrayList<>();
        if (StringUtils.isNotBlank(customer.getHasLabel())){
            String[] ss = customer.getHasLabel().substring(1).split("\\,");
            for (String id:ss) {
                ids.add(id);
            }
            customer.setLabelIds(ids);
        }
        customer.setCorpCode(ExecutionContext.getCorpCode());
        return  page.setRecords(this.baseMapper.callOutTask(page, customer));
    }

    @Override
    public Map<String, Integer> getCallOutTaskStatistics(String userId,List<String> taskIds) {
        List<Customer> customers = customerDao.getCallOutTaskStatistics(ExecutionContext.getCorpCode(), userId, taskIds);
        if (CollectionUtils.isEmpty(customers)){
            return MapUtils.EMPTY_MAP;
        }

        Map<String,Integer> taskIdStatusCntMap = new HashMap<>(customers.size());
        for (Customer customer : customers) {
            taskIdStatusCntMap.put(customer.getCalloutTaskId()+"-"+customer.getCalloutTaskStatus(),customer.getCalloutTaskStatusCnt());
        }

        return taskIdStatusCntMap;
    }

    @Override
    public Page<Customer> findPageForProcess(Page page, Customer customer) {
        List<Customer> records = customerDao.findPageForProcess(page, customer);
        return page.setRecords(records);
    }
    @Transactional(rollbackFor = {MybatisPlusException.class})
    @Override
    public void  improtInsertBath(String corpCode,String userId,List<Customer> list){

        insertBatch(list);//TODO 检查validCustomers,一个线程1K条数据导入
        //处理客户轨迹记录

        int validCustomerSize = list.size();
        List<CustomerRecord> customerRecords = new ArrayList<>(validCustomerSize);
        for (Customer validCustomer : list) {
            CustomerRecord customerRecord = new CustomerRecord(UkConstant.RECORD_TYPE_UPDATE,"导入了客户",null,null,null,validCustomer.getId());
            customerRecord.setCreater(userId);
            customerRecord.setCorpCode(corpCode);
            customerRecords.add(customerRecord);
        }
        boolean result = customerRecordService.insertBatch(customerRecords);
    }
}

 

展开阅读全文

没有更多推荐了,返回首页