EzyPlatform đã hoàn thành dự án vận chuyển hàng xuyên biên giới trong 6 ngày thế nào? Phần 2
Back To Blogs- Một mã vận đơn chỉ được tối đa 10 kg do yêu cầu của hải quan vậy nên nếu lúc nhập mà quá 10 kg thì phải tự động chia nhỏ thành các mã vận đơn khác nhau, ví dụ nếu nhập vào là 31kg thì tự động phải tách thành các vận đơn 10, 10, 10 và 1kg.
- Giá tiền sẽ cần random từ 2 đến 100.
- Vận đơn được gắn ngẫu nhiên vào một kho nào đó ở đầu Trung Quốc.
- Thông tin người nhận cũng sẽ được gắn ngẫu nhiên.
Giải quyết vấn đề tuỳ biến cài đặt
Yêu cầu của khách hàng có thể thay đổi từ 10kg lên 20kg hay các thông số khác cũng vậy nên tốt hơn hết là mình nên đưa vào phần cài đặt để tuỳ chỉnh khi cần:
- Cho phép tuỳ chỉnh số cân tối đa của vận đơn.
- Cho phép cài đặt khoảng giá random.
- Cho phép cài đặt loại tiền.
- Cho phép cài đặt quốc gia đầu vào để lấy danh sách kho ngẫu nhiên.
Và kết quả là một giao diện cài đặt thế này:
Giải quyết vấn đề lấy ngẫu nhiên dữ liệu
Tạo ngẫu nhiên giá tiền thì đơn giản rồi, nhưng làm thế nào để lấy được ngẫu nhiên kho và người nhận?
- Cách 1: Lấy ngẫu nhiên từ min id và max id của bản ghi kho và người dùng sau đó random: Cách này cũng ổn nhưng giả sử có kho hoặc người dùng đã bị xoá thì lại phải random nhiều lần, tiêu cực có thể random mãi.
- Cách 2: Truy vấn toàn bộ kho ở quốc gia đầu vào và người dùng sau đó random: Cách này chỉ phù hợp với bộ dữ liệu nhỏ, danh sách người nhận mà khách hàng gửi lên đến vài chục nghìn.
- Cách 3: Lưu cache thông tin kho và người dùng: Cách này là phù hợp vì mặc dù dữ liệu nhiều nhưng dung lượng nhỏ, không cần phải truy vấn cơ sở dữ liệu nhiều lần nên rất nhanh.
Mình đã tạo ra hai lớp lưu cache như sau:
Lớp lưu cache các kho từ quốc gia đầu vào
Mình tạo ra một lớp có tên AdminEzyDeliveryShopWarehouseService
với mã nguồn như sau:
public class AdminEzyDeliveryShopWarehouseService extends EzyDeliveryShopWarehouseService { private final AdminScheduler scheduler; private final AdminEzyDeliverySettingService settingService; private final AdminShopService shopService; private final AdminEzyDeliveryWarehouseRepository warehouseRepository; private final AtomicBoolean deliveryOrderInputWarehouseIdCachingStarted = new AtomicBoolean(); private final AtomicReference<Long> lastDeliveryOrderWarehouseShopId = new AtomicReference<>(); private final AtomicReference<Long> lastDeliveryOrderWarehouseCountryId = new AtomicReference<>(); private final AtomicReference<LocalDateTime> lastDeliveryOrderWarehouseUpdatedAt = new AtomicReference<>(); private final List<Long> cachedDeliveryOrderWarehouseIds = new ArrayList<>(); public void startCacheDeliveryOrderWarehouseIdsSchedule() { if (deliveryOrderInputWarehouseIdCachingStarted.compareAndSet(false, true)) { scheduler.scheduleAtFixRate( this::cacheDeliveryOrderWarehouseIds, 0, 5, TimeUnit.SECONDS ); } } @Override public long randomInputDeliveryOrderFromWarehouseId() { synchronized (cachedDeliveryOrderWarehouseIds) { int index = ThreadLocalRandom .current() .nextInt(cachedDeliveryOrderWarehouseIds.size()); return cachedDeliveryOrderWarehouseIds.get(index); } } private void cacheDeliveryOrderWarehouseIds() { long countryId = settingService.getInputDeliveryOrderCountryId(); if (countryId <= 0) { return; } long shopId = shopService.getDefaultShopId(); if (shopId <= 0) { return; } AdminShopWarehouseUpdatedAtResult result = warehouseRepository .findLastUpdatedAtByShopIdAndCountryIdOrderByUpdatedAtDesc( shopId, countryId ); if (result == null) { return; } Long lastShopId = lastDeliveryOrderWarehouseShopId.get(); Long lastCountryId = lastDeliveryOrderWarehouseCountryId.get(); LocalDateTime lastTime = lastDeliveryOrderWarehouseUpdatedAt.get(); LocalDateTime updatedAt = result.getUpdatedAt(); boolean needToCache = lastShopId == null || !lastShopId.equals(shopId) || lastCountryId == null || !lastCountryId.equals(countryId) || lastTime == null || updatedAt.isAfter(lastTime); if (needToCache) { doCacheDeliveryOrderWarehouseIds(shopId, countryId); lastDeliveryOrderWarehouseShopId.set(shopId); lastDeliveryOrderWarehouseCountryId.set(countryId); lastDeliveryOrderWarehouseUpdatedAt.set(updatedAt); } } private void doCacheDeliveryOrderWarehouseIds(long shopId, long countryId) { List<Long> warehouseIds = newArrayList( warehouseRepository.findShopWarehouseIdsByShopIdAndCountryId( shopId, countryId ), IdResult::getId ); synchronized (cachedDeliveryOrderWarehouseIds) { cachedDeliveryOrderWarehouseIds.clear(); cachedDeliveryOrderWarehouseIds.addAll(warehouseIds); } } }
Ở đây khi có sự thay đổi của quốc gia đầu vào hay một kho mới được thêm sẽ dẫn đến cập nhật lại cache. Còn khi sử dụng sẽ chỉ cần gọi hàm randomInputDeliveryOrderFromWarehouseId
để lấy ra id của một kho ngẫu nhiên.
Lớp lưu cache danh sách người nhận hàng
Mình cũng tạo ra một lớp có tên AdminEzyDeliveryReceiverService
với mã nguồn như sau:
public class AdminEzyDeliveryReceiverService extends EzyDeliveryReceiverService { private final AdminScheduler scheduler; private final AdminEventHandlerManager eventHandlerManager; private final AdminUserService userService; private final AdminEzyDeliveryUserRoleRepository userRoleRepository; private final AtomicBoolean fakeDeliveryOrderReceiversCachingStarted = new AtomicBoolean(); private final AtomicReference<LocalDateTime> lastFakeDeliveryOrderReceiverTime = new AtomicReference<>(); private final List<EzyDeliveryReceiverModel> cachedFakeDeliveryOrderReceivers = new ArrayList<>(); public void startFakeDeliveryReceiversSchedule() { if (fakeDeliveryOrderReceiversCachingStarted.compareAndSet(false, true)) { scheduler.scheduleAtFixRate( this::cacheFakeDeliveryReceivers, 0, 5, TimeUnit.SECONDS ); } } @Override public EzyDeliveryReceiverModel randomFakeDeliveryOrderReceiver() { synchronized (cachedFakeDeliveryOrderReceivers) { int index = ThreadLocalRandom .current() .nextInt(cachedFakeDeliveryOrderReceivers.size()); return cachedFakeDeliveryOrderReceivers.get(index); } } private void cacheFakeDeliveryReceivers() { AdminEzyDeliveryUserRoleCreatedAtResult result = userRoleRepository .findLastFakeReceiverRoleCreatedAt(); if (result == null) { return; } LocalDateTime lastTime = lastFakeDeliveryOrderReceiverTime.get(); LocalDateTime createdAt = result.getCreatedAt(); boolean needToCache = lastTime == null || createdAt.isAfter(lastTime); if (needToCache) { doCacheFakeDeliveryReceivers(); lastFakeDeliveryOrderReceiverTime.set(createdAt); } } private void doCacheFakeDeliveryReceivers() { List<Long> userIds = newArrayList( userRoleRepository.findFakeReceiverIds(), IdResult::getId ); List<UserModel> users = userService.getUserListByIds(userIds); Map<Long, String> addressByUserId = eventHandlerManager .handleEvent( INTERNAL_EVENT_NAME_FETCH_CUSTOMER_ADDRESS_MAP_BY_ID, userIds ); if (addressByUserId == null) { addressByUserId = Collections.emptyMap(); } Map<Long, String> addressByUserIdFinal = addressByUserId; List<EzyDeliveryReceiverModel> receivers = newArrayList( users, it -> EzyDeliveryReceiverModel.builder() .displayName(it.getName()) .phoneNumber(it.getPhone()) .email(it.getEmail()) .address( addressByUserIdFinal.getOrDefault( it.getId(), UNKNOWN ) ) .build() ); synchronized (cachedFakeDeliveryOrderReceivers) { cachedFakeDeliveryOrderReceivers.clear(); cachedFakeDeliveryOrderReceivers.addAll(receivers); } } }
Khi có một người dùng với quyền fake_receiver
được thêm mới sẽ dẫn đến cache được load lại. Bạn cần lưu ý rằng mình sẽ không lưu toàn bộ danh sách người dùng vì như thế có thể rất nhiều và có thể vi phạm chính sách bảo mật dữ liệu của người dùng thật.
Khi cần sử dụng sẽ chỉ đơn giản là gọi hàm randomFakeDeliveryOrderReceiver
để lấy ra ngẫu nhiên một người nhận hàng mà thôi.
Rõ ràng là một dự án thực tế, đặc biệt là trong dự án liên quan đến logistics thế này thì yêu cầu khách hàng đặt ra là phức tạp và tỉ mỉ, nếu không có sẵn một kiến trúc mã nguồn tốt thì rất khó để đáp ứng được nhu cầu của khách hàng.