# NightAuditLogServiceImpl.java

    package cloud.wconnect.frontoffice.service.nightauditlog.impl;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseConstant;
    import cloud.wconnect.commonlib.pojo.dto.ListPagedDTO;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.redis.PropertyI18nRedisService;
    import cloud.wconnect.commonlib.service.UserInternalService;
    import cloud.wconnect.commonlib.util.*;
    import cloud.wconnect.frontoffice.cons.S3BucketFunctionCons;
    import cloud.wconnect.frontoffice.cons.nightauditlog.NightAuditLogEnum;
    import cloud.wconnect.frontoffice.entity.nightauditlog.NightAuditLog;
    import cloud.wconnect.frontoffice.mapper.nightauditlog.NightAuditLogMapper;
    import cloud.wconnect.frontoffice.pojo.dto.nigntauditlog.NightAuditLogDTO;
    import cloud.wconnect.frontoffice.pojo.query.NightAuditLogQuery;
    import cloud.wconnect.frontoffice.service.nightauditlog.INightAuditLogService;
    import com.baomidou.dynamic.datasource.annotation.DS;
    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;

    import java.time.Duration;
    import java.time.ZonedDateTime;
    import java.util.List;
    import java.util.Map;
    import java.util.Objects;
    import java.util.concurrent.Executor;
    import java.util.stream.Stream;

    import static java.util.stream.Collectors.toList;

    /**
     * <p>
     * 服务实现类
     * </p>
     *
     * @author lichen
     * @since 2023-03-16
     */
    @Service
    @DS("logbook")
    public class NightAuditLogServiceImpl extends ServiceImpl<NightAuditLogMapper, NightAuditLog> implements INightAuditLogService {

        @Value("${aws-s3.region}")
        private String region;
        @Value("#{${aws-s3.function-bucket}}")
        private Map<String, Map<String, Object>> bucketMap;
        @Autowired
        private Executor executor;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private UserInternalService userInternalService;
        @Autowired
        private PropertyI18nRedisService propertyI18nRedisService;
        @Autowired
        private BeanMapper beanMapper;

        /**
         * 修改
         *
         * @param oriLog 修改前的数据
         * @param log    修改后的数据
         * @return void
         * @author lichen
         * @date 2023/11/6
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void update(NightAuditLog oriLog, NightAuditLog log, int loginUserId) {
            log.setId(oriLog.getId());
            log.setUpdatedBy(loginUserId);
            log.setUpdatedTime(DateTimeUtils.nowTimestamp());
            updateById(log);

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //修改前的s3Key集合
                List<String> updateBeforeS3Keys = AttachmentUtil.getKeysByAttachmentArrayJson(oriLog.getAttachments(), objectMapper);
                //修改后的s3Key集合
                List<String> updateAfterS3Keys = AttachmentUtil.getKeysByAttachmentArrayJson(log.getAttachments(), objectMapper);
                //删除的附件 = 修改前的附件集合 - 修改后的附件集合
                List<String> delS3Keys = Lists.newArrayList(updateBeforeS3Keys);
                delS3Keys.removeAll(updateAfterS3Keys);
                S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.NIGHT_AUDIT), delS3Keys);
            });
        }

        /**
         * 删除
         *
         * @param log Night Audit Log
         * @return void
         * @author lichen
         * @date 2023/11/6
         */
        @Override
        @Transactional(rollbackFor = Exception.class)
        public void remove(NightAuditLog log) {
            removeById(log.getId());

            TransactionUtils.afterCommitAsyncExecute(executor, () -> {
                //修改前的s3Key集合
                List<String> delS3Keys = AttachmentUtil.getKeysByAttachmentArrayJson(log.getAttachments(), objectMapper);
                S3Utils.batchDeleteFile(region, bucketMap.get(S3BucketFunctionCons.NIGHT_AUDIT), delS3Keys);
            });
        }

        /**
         * 列表
         *
         * @param tenantId   租户id
         * @param propertyId 资产id
         * @param logQuery   查询参数
         * @return ListPagedDTO<NightAuditLogDTO>
         * @author lichen
         * @date 2025/12/29
         */
        @Override
        public ListPagedDTO<NightAuditLogDTO> pageList(Integer tenantId, int propertyId, NightAuditLogQuery logQuery) {
            PropertyDTO propertyDTO = propertyI18nRedisService.get(propertyId);

            // 构建查询条件
            LambdaQueryWrapper<NightAuditLog> lambdaQueryWrapper = new LambdaQueryWrapper<NightAuditLog>()
                    .eq(NightAuditLog::getPropertyId, propertyId)
                    .eq(Objects.nonNull(logQuery.getStatus()) && logQuery.getStatus() == NightAuditLogEnum.STATUS_PENDING.getStatus(),
                            NightAuditLog::getResolvedTime, BaseConstant.ZERO)
                    .gt(Objects.nonNull(logQuery.getStatus()) && logQuery.getStatus() == NightAuditLogEnum.STATUS_RESOLVED.getStatus(),
                            NightAuditLog::getResolvedTime, BaseConstant.ZERO)
                    .between(Objects.nonNull(logQuery.getEmailDateFrom()) && Objects.nonNull(logQuery.getEmailDateTo()),
                            NightAuditLog::getEmailDate, logQuery.getEmailDateFrom(), logQuery.getEmailDateTo())
                    .like(StringUtils.isNotBlank(logQuery.getEmailSubject()), NightAuditLog::getEmailSubject, logQuery.getEmailSubject())
                    .like(StringUtils.isNotBlank(logQuery.getAddressTo()), NightAuditLog::getAddressTo, logQuery.getAddressTo())
                    .like(StringUtils.isNotBlank(logQuery.getRvNumber()), NightAuditLog::getRvNumber, logQuery.getRvNumber())
                    .orderByDesc(NightAuditLog::getId);

            // 查询
            Page<NightAuditLog> pageResult = page(new Page<>(logQuery.getPage(), logQuery.getSize()), lambdaQueryWrapper);

            long total = pageResult.getTotal();
            if (total == 0) {
                return new ListPagedDTO<>();
            }

            List<NightAuditLog> records = pageResult.getRecords();

            //CreatedBy、ResolvedBy 的userId集合
            List<Integer> userIds = records.stream()
                    .flatMap(log -> Stream.of(log.getCreatedBy(), log.getResolvedBy()))
                    .filter(userId -> userId > BaseConstant.ZERO)
                    .distinct()
                    .collect(toList());
            //调用内部组件，获取用户信息
            Map<Integer, String> userId2Name = userInternalService.getNameMapByIds(tenantId, userIds);

            String timezone = propertyDTO.getTimezone();
            ZonedDateTime dateNow = DateTimeUtils.dateTimeNow(timezone);

            //List<NightAuditLog> -> List<NightAuditLogDTO>
            List<NightAuditLogDTO> logDTOList = beanMapper.mapList(records, NightAuditLogDTO.class,
                    (nightAuditLog, logDTO) -> {
                        Integer createdBy = nightAuditLog.getCreatedBy();
                        logDTO.setCreatedByName(userId2Name.get(createdBy));

                        Long resolvedTime = nightAuditLog.getResolvedTime();
                        if (resolvedTime == 0) {
                            Long emailDate = nightAuditLog.getEmailDate();
                            if (emailDate > 0) {
                                ZonedDateTime emailDateZoned = DateTimeUtils.toZonedDateTime(emailDate, timezone);
                                if (emailDateZoned.isBefore(dateNow)) {
                                    // 计算 Pending 状态的维持天数
                                    long pendingDays = Duration.between(emailDateZoned, dateNow).toDays();
                                    logDTO.setPendingDays(pendingDays + 1);
                                }
                            }
                        } else {
                            String fullName = userId2Name.get(nightAuditLog.getResolvedBy());
                            logDTO.setResolvedByName(fullName);
                        }
                    });
            return new ListPagedDTO<>(total, logDTOList);
        }

        /**
         * 统计数量
         *
         * @param propertyId 资产id
         * @param logQuery   查询参数
         * @return long
         * @author lichen
         * @date 2025/12/29
         */
        @Override
        public long count(int propertyId, NightAuditLogQuery logQuery) {
            // 构建查询条件
            LambdaQueryWrapper<NightAuditLog> lambdaQueryWrapper = new LambdaQueryWrapper<NightAuditLog>()
                    .eq(NightAuditLog::getPropertyId, propertyId)
                    .eq(Objects.nonNull(logQuery.getStatus()) && logQuery.getStatus() == NightAuditLogEnum.STATUS_PENDING.getStatus(),
                            NightAuditLog::getResolvedTime, BaseConstant.ZERO)
                    .gt(Objects.nonNull(logQuery.getStatus()) && logQuery.getStatus() == NightAuditLogEnum.STATUS_RESOLVED.getStatus(),
                            NightAuditLog::getResolvedTime, BaseConstant.ZERO)
                    .between(Objects.nonNull(logQuery.getEmailDateFrom()) && Objects.nonNull(logQuery.getEmailDateTo()),
                            NightAuditLog::getEmailDate, logQuery.getEmailDateFrom(), logQuery.getEmailDateTo())
                    .like(StringUtils.isNotBlank(logQuery.getEmailSubject()), NightAuditLog::getEmailSubject, logQuery.getEmailSubject())
                    .like(StringUtils.isNotBlank(logQuery.getAddressTo()), NightAuditLog::getAddressTo, logQuery.getAddressTo())
                    .like(StringUtils.isNotBlank(logQuery.getRvNumber()), NightAuditLog::getRvNumber, logQuery.getRvNumber());
            return count(lambdaQueryWrapper);
        }
    }