# InternalHotelController.java

    package cloud.wconnect.rateshop.controller.internal;

    import cloud.wconnect.commonlib.component.BeanMapper;
    import cloud.wconnect.commonlib.cons.BaseErrorCodeEnum;
    import cloud.wconnect.commonlib.cons.CurrencyEnum;
    import cloud.wconnect.commonlib.cons.FunctionIdConstant;
    import cloud.wconnect.commonlib.exception.ForbiddenException;
    import cloud.wconnect.commonlib.pojo.dto.PropertyDTO;
    import cloud.wconnect.commonlib.pojo.query.OpenFunctionPropertyQuery;
    import cloud.wconnect.commonlib.service.PropertyInternalService;
    import cloud.wconnect.commonlib.util.JsonUtils;
    import cloud.wconnect.commonlib.util.SignUtils;
    import cloud.wconnect.rateshop.entity.rateshop.Hotel;
    import cloud.wconnect.rateshop.entity.rateshop.PropertyHotel;
    import cloud.wconnect.rateshop.entity.rateshop.Setting;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.RateConfigDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.hotel.CollectHotelUrlDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.internal.InternalHotelDTO;
    import cloud.wconnect.rateshop.pojo.dto.rateshop.internal.InternalPropertyDTO;
    import cloud.wconnect.rateshop.service.rateshop.IHotelService;
    import cloud.wconnect.rateshop.service.rateshop.IPropertyHotelService;
    import cloud.wconnect.rateshop.service.rateshop.ISettingService;
    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.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.collect.Lists;
    import org.apache.commons.collections.CollectionUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.*;

    import java.text.MessageFormat;
    import java.util.*;
    import java.util.stream.Collectors;

    /**
     * 抓取酒店信息 控制层
     *
     * @author lichen
     * @date 2023/6/17
     */
    @RestController
    @RequestMapping("/internal/hotels")
    public class InternalHotelController {

        @Value("${internal.service.auth-key.basic}")
        private String authKey;
        @Autowired
        private IHotelService hotelService;
        @Autowired
        private IPropertyHotelService propertyHotelService;
        @Autowired
        private PropertyInternalService propertyInternalService;
        @Autowired
        private BeanMapper beanMapper;
        @Autowired
        private ObjectMapper objectMapper;
        @Autowired
        private ISettingService settingService;

        /**
         * 查询酒店信息
         *
         * @param sign         签名
         * @param page         页码
         * @param size         每页大小
         * @param datasourceId 数据源ID
         * @return java.util.Map<java.lang.String, java.lang.Object>
         * @author lichen
         * @date 2023/6/17
         */
        @GetMapping
        public Map<String, Object> list(@RequestHeader String sign,
                                        @RequestParam(required = false) Integer page,
                                        @RequestParam(required = false) Integer size,
                                        @RequestParam Integer datasourceId) {
            //计算签名
            String actualSign;
            if (Objects.nonNull(page) && Objects.nonNull(size)) {
                actualSign = SignUtils.signWithKey(authKey, page, size, datasourceId);
            } else {
                actualSign = SignUtils.signWithKey(authKey, "", "", datasourceId);
            }

            //校验签名
            if (!actualSign.equals(sign)) {
                String logMsg = MessageFormat.format("The parameters may be modified illegally; expectedSign:{0}, "
                        + "actualSign:{1}, authKey:{2}", sign, actualSign, authKey);
                throw new ForbiddenException(BaseErrorCodeEnum.DATA_FORBIDDEN.getI18nCode(),
                        BaseErrorCodeEnum.DATA_FORBIDDEN.getCode(), logMsg);
            }

            /**
             * 1. 查询配置 指定数据源 的资产
             * 2. 调用basic模块，过滤掉关闭的和未开通rateShop模块的资产
             * 3. 分页去重查询property_hotel表
             */
            //配置rate shop setting的资产
            List<Setting> settings = settingService.list();
            List<Integer> propertyIdsBySetting = settings.stream().filter(setting -> {
                String rateConfig = setting.getRateConfig();
                List<RateConfigDTO> rateConfigDTOS = JsonUtils.convertStringToList(objectMapper, rateConfig, RateConfigDTO.class);
                //数据源ID集合
                Set<Integer> datasourceIds = rateConfigDTOS.stream().map(RateConfigDTO::getDatasourceId).collect(Collectors.toSet());
                return datasourceIds.contains(datasourceId);
            }).map(Setting::getPropertyId).toList();
            if(CollectionUtils.isEmpty(propertyIdsBySetting)) {
                return Map.of("total", 0, "hotels", Collections.emptyList(), "properties", Collections.emptyList());
            }

            //有效的资产ID集合
            OpenFunctionPropertyQuery openFunctionPropertyQuery = new OpenFunctionPropertyQuery();
            openFunctionPropertyQuery.setIds(propertyIdsBySetting);
            openFunctionPropertyQuery.setOpenFunctionIds(Lists.newArrayList(FunctionIdConstant.RATE_SHOP));
            List<PropertyDTO> validPropertyDTOS = propertyInternalService.getOpenFunctionProperty(openFunctionPropertyQuery)
                    .stream().filter(PropertyDTO::getStatus).toList();
            if(CollectionUtils.isEmpty(validPropertyDTOS)) {
                return Map.of("total", 0, "hotels", Collections.emptyList(), "properties", Collections.emptyList());
            }
            List<Integer> validPropertyIds = validPropertyDTOS.stream().map(PropertyDTO::getId).toList();

            //资产ID的销售地区集合
            Map<Integer, String> propertyId2Pos = settings.stream().filter(setting -> validPropertyIds.contains(setting.getPropertyId()))
                    .collect(Collectors.toMap(Setting::getPropertyId, Setting::getPos));

            //去重查询有效资产关联的hotelId
            long total;
            List<PropertyHotel> propertyHotels;
            LambdaQueryWrapper<PropertyHotel> queryWrapper = new QueryWrapper<PropertyHotel>().select("distinct hotel_id")
                    .lambda().in(PropertyHotel::getPropertyId, validPropertyIds).orderByAsc(PropertyHotel::getHotelId);
            if (Objects.nonNull(page) && Objects.nonNull(size)) {
                Page<PropertyHotel> resultPage = propertyHotelService.page(new Page<>(page, size), queryWrapper);
                total = resultPage.getTotal();
                propertyHotels = resultPage.getRecords();
            }else{
                propertyHotels = propertyHotelService.list(queryWrapper);
                total = propertyHotels.size();
            }
            if(total == 0){
                return Map.of("total", 0, "hotels", Collections.emptyList(), "properties", Collections.emptyList());
            }

            //酒店集合
            List<Integer> hotelIds = propertyHotels.stream().map(PropertyHotel::getHotelId).toList();
            List<Hotel> hotelList = hotelService.listByIds(hotelIds);

            //查询酒店关联的资产
            Map<Integer, List<Integer>> hotelId2PropertyIds = propertyHotelService.list(new LambdaQueryWrapper<PropertyHotel>()
                            .in(PropertyHotel::getHotelId, hotelIds)
                            .in(PropertyHotel::getPropertyId, validPropertyIds))
                    .stream().collect(Collectors.groupingBy(PropertyHotel::getHotelId,
                            Collectors.mapping(PropertyHotel::getPropertyId, Collectors.toList())));

            //Hotel -> InternalHotelDTO
            List<InternalHotelDTO> internalHotelDTOS = Lists.newArrayListWithCapacity(hotelIds.size());
            for (Hotel hotel : hotelList) {
                //获取酒店在指定数据源下配置的采集url
                Map<Integer, String> datasourceId2Url = JsonUtils.convertStringToList(objectMapper, hotel.getUrls(), CollectHotelUrlDTO.class)
                        .stream().collect(Collectors.toMap(CollectHotelUrlDTO::getDatasourceId, CollectHotelUrlDTO::getUrl));

                InternalHotelDTO dto = new InternalHotelDTO();
                dto.setId(hotel.getId());
                dto.setOwnedBy(hotelId2PropertyIds.get(hotel.getId()));
                dto.setUrl(datasourceId2Url.get(datasourceId));
                internalHotelDTOS.add(dto);
            }

            //PropertyDTO -> InternalPropertyDTO
            List<InternalPropertyDTO> internalPropertyDTOS = beanMapper.mapList(validPropertyDTOS, InternalPropertyDTO.class, (propertyDTO, internalPropertyDTO) -> {
                //资产配置的币种简称
                String currencySymbol = propertyDTO.getInternationalization().getNumber().getCurrency();
                internalPropertyDTO.setCurrencyId(CurrencyEnum.getCurrencyIdBySymbol(currencySymbol));
                //资产的销售地区
                internalPropertyDTO.setPos(propertyId2Pos.get(propertyDTO.getId()));
            });
            return Map.of("total", total, "hotels", internalHotelDTOS, "properties", internalPropertyDTOS);
        }

    }