| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- package com.ruoyi.app.map;
- import com.alibaba.fastjson2.JSONArray;
- import com.alibaba.fastjson2.JSONObject;
- import com.ruoyi.common.exception.ServiceException;
- import com.ruoyi.common.utils.MessageUtils;
- import com.ruoyi.system.domain.PosStore;
- import com.ruoyi.system.mapper.PosStoreMapper;
- import org.springframework.stereotype.Service;
- import java.math.BigDecimal;
- import java.util.ArrayList;
- import java.util.HashSet;
- import java.util.List;
- import java.util.Set;
- /** 只读所选业务门店、手动同步人工地点源表;搜索索引另行发布。 */
- @Service
- public class MerchantMapSyncService {
- private final PosStoreMapper stores;
- private final SelfHostedMapAdminClient map;
- public MerchantMapSyncService(PosStoreMapper stores, SelfHostedMapAdminClient map) {
- this.stores = stores;
- this.map = map;
- }
- public SyncResult sync(MerchantMapSyncRequest request) {
- if (request == null) throw invalidRequest();
- List<Long> ids = request.getStoreIds() == null ? List.of() : request.getStoreIds();
- List<Long> removals = request.getRemoveStoreIds() == null ? List.of() : request.getRemoveStoreIds();
- Set<Long> seen = new HashSet<>();
- if (ids.size() + removals.size() < 1 || ids.size() + removals.size() > 100) throw invalidRequest();
- for (List<Long> group : List.of(ids, removals)) {
- for (Long id : group) if (id == null || id <= 0 || !seen.add(id)) throw invalidRequest();
- }
- List<RowResult> rows = new ArrayList<>();
- List<JSONObject> places = new ArrayList<>();
- List<RowResult> submitted = new ArrayList<>();
- for (Long id : ids) {
- RowResult row = new RowResult(id, "UPSERT");
- rows.add(row);
- PosStore store = stores.selectPosStoreById(id);
- if (store == null) {
- row.fail("STORE_NOT_FOUND", "map.merchantSync.storeMissing");
- continue;
- }
- if (!validStore(store)) {
- row.fail("INVALID_STORE", "map.merchantSync.invalidStore");
- continue;
- }
- JSONObject place = new JSONObject();
- place.put("external_id", externalId(id));
- place.put("name", store.getPosName().trim());
- place.put("address", store.getAddress() == null ? "" : store.getAddress().trim());
- place.put("lat", store.getLatitude());
- place.put("lng", store.getLongitude());
- place.put("type", "business");
- places.add(place);
- submitted.add(row);
- }
- // 已尝试的写入遇到超时/响应异常也可能部分成功,仍提示索引发布和幂等重试。
- boolean rebuild = !places.isEmpty() || !removals.isEmpty();
- if (!places.isEmpty()) {
- try {
- Set<Integer> rejected = rejectedIndexes(map.importPlaces(places), places.size());
- for (int index = 0; index < submitted.size(); index++) {
- if (rejected.contains(index)) submitted.get(index).fail("INVALID_STORE", "map.merchantSync.invalidStore");
- else submitted.get(index).succeed();
- }
- } catch (RuntimeException failure) {
- for (RowResult row : submitted) row.fail("MAP_UNAVAILABLE", "map.merchantSync.unavailable");
- }
- }
- for (Long id : removals) {
- RowResult row = new RowResult(id, "REMOVE");
- rows.add(row);
- try {
- JSONObject result = map.deletePlace(externalId(id));
- Object removed = result == null ? null : result.get("removed");
- if (result == null || !"removed".equals(result.getString("status"))
- || !(removed instanceof Integer count) || count < 0 || count > 1
- || !Boolean.TRUE.equals(result.get("index_rebuild_required"))) throw malformed();
- row.succeed();
- } catch (RuntimeException failure) {
- row.fail("MAP_UNAVAILABLE", "map.merchantSync.unavailable");
- }
- }
- return new SyncResult(rows, rebuild);
- }
- private Set<Integer> rejectedIndexes(JSONObject response, int total) {
- if (response == null || !(response.get("imported") instanceof Integer imported)
- || !(response.get("rejected") instanceof Integer rejected)
- || imported < 0 || rejected < 0 || imported + rejected != total
- || !(response.get("rejections") instanceof JSONArray rejections) || rejections.size() != rejected)
- throw malformed();
- Set<Integer> indexes = new HashSet<>();
- for (Object value : rejections) {
- if (!(value instanceof JSONObject row) || !(row.get("index") instanceof Integer index)
- || index < 0 || index >= total || !indexes.add(index)) throw malformed();
- }
- if (imported > 0 && !Boolean.TRUE.equals(response.get("index_rebuild_required"))) throw malformed();
- return indexes;
- }
- private boolean validStore(PosStore store) {
- // state=1 是休息/打烊,不作为永久停用依据;移除只能来自显式 removeStoreIds。
- return store.getPosName() != null && !store.getPosName().isBlank() && store.getPosName().trim().length() <= 200
- && (store.getAddress() == null || store.getAddress().trim().length() <= 500)
- && (store.getDelFlag() == null || "0".equals(store.getDelFlag()))
- && (store.getOffShelf() == null || "0".equals(store.getOffShelf()))
- && store.getLatitude() != null && store.getLatitude().abs().compareTo(BigDecimal.valueOf(90)) <= 0
- && store.getLongitude() != null && store.getLongitude().abs().compareTo(BigDecimal.valueOf(180)) <= 0;
- }
- private String externalId(Long id) { return "foodie-store-" + id; }
- private IllegalStateException malformed() { return new IllegalStateException("invalid merchant sync response"); }
- private ServiceException invalidRequest() { return new ServiceException(MessageUtils.message("map.merchantSync.invalid")); }
- public static class SyncResult {
- private final List<RowResult> rows;
- private final boolean indexRebuildRequired;
- public SyncResult(List<RowResult> rows, boolean indexRebuildRequired) {
- this.rows = rows;
- this.indexRebuildRequired = indexRebuildRequired;
- }
- public List<RowResult> getRows() { return rows; }
- public boolean isIndexRebuildRequired() { return indexRebuildRequired; }
- }
- public static class RowResult {
- private final Long storeId;
- private final String action;
- private boolean success;
- private String code;
- private String message;
- public RowResult(Long storeId, String action) { this.storeId = storeId; this.action = action; }
- private void succeed() { success = true; code = "OK"; message = null; }
- private void fail(String code, String key) { success = false; this.code = code; message = MessageUtils.message(key); }
- public Long getStoreId() { return storeId; }
- public String getAction() { return action; }
- public boolean isSuccess() { return success; }
- public String getCode() { return code; }
- public String getMessage() { return message; }
- }
- }
|