1. 程式人生 > 程式設計 >[Spring cloud 一步步實現廣告系統] 13. 索引服務編碼實現

[Spring cloud 一步步實現廣告系統] 13. 索引服務編碼實現

上一節我們分析了廣告索引的維護有2種,全量索引載入增量索引維護。因為廣告檢索是廣告系統中最為重要的環節,大家一定要認真理解我們索引設計的思路,接下來我們來編碼實現索引維護功能。

我們來定義一個介面,來接收所有index的增刪改查操作,介面定義一個範型,來接收2個引數,K代表我們索引的健值,V代表返回值。

/**
 * IIndexAware for 實現廣告索引的增刪改查
 *
 * @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
 */
public interface IIndexAware
<K,V>
{ /** * 通過key 獲取索引 */ V get(K key); /** * 新增索引 * @param key * @param value */ void add(K key,V value); /** * 更新索引 */ void update(K key,V value); /** * 刪除索引 */ void delete(K key,V value); } 複製程式碼

我們一定要知道,並不是所有的資料庫表都需要建立索引,比如User

表我們在資料檢索的時候其實是不需要的,當然也就沒必要建立索引,並且,也不是表中的所有欄位都需要索引,這個也是根據具體的業務來確定欄位資訊,比如我們接下來要編寫的推廣計劃索引中,推廣計劃名稱就可以不需要。下面,我們來實現我們的第一個正向索引

  • 首先建立操作推廣計劃的實體物件
/**
 * AdPlanIndexObject for 推廣計劃索引物件
 * 這個索引物件我們沒有新增 推廣計劃名稱
 * @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
 */
@Data
@NoArgsConstructor
@AllArgsConstructor public class AdPlanIndexObject { private Long planId; private Long userId; private Integer planStatus; private Date startDate; private Date endDate; /** * 根據實際欄位來更新索引 */ public void update(AdPlanIndexObject newObject) { if (null != newObject.getPlanId()) { this.planId = newObject.getPlanId(); } if (null != newObject.getUserId()) { this.userId = newObject.getUserId(); } if (null != newObject.getPlanStatus()) { this.planStatus = newObject.getPlanStatus(); } if (null != newObject.getStartDate()) { this.startDate = newObject.getStartDate(); } if (null != newObject.getEndDate()) { this.endDate = newObject.getEndDate(); } } } 複製程式碼
  • 然後建立推廣計劃索引實現類,並實現IIndexAware介面。
/**
 * AdPlanIndexAwareImpl for 推廣計劃索引實現類
 *
 * @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
 */
@Slf4j
@Component
public class AdPlanIndexAwareImpl implements IIndexAware<Long,AdPlanIndexObject> {

    private static Map<Long,AdPlanIndexObject> planIndexObjectMap;

    /**
     * 因為操作索引的過程中有可能對索引進行更新,為了防止多執行緒造成的執行緒不安全問題,我們不能使用hashmap,需要實現ConcurrentHashMap
     */
    static {
        planIndexObjectMap = new ConcurrentHashMap<>();
    }

    @Override
    public AdPlanIndexObject get(Long key) {
        return planIndexObjectMap.get(key);
    }

    @Override
    public void add(Long key,AdPlanIndexObject value) {

        log.info("AdPlanIndexAwareImpl before add::{}",planIndexObjectMap);
        planIndexObjectMap.put(key,value);
        log.info("AdPlanIndexAwareImpl after add::{}",planIndexObjectMap);
    }

    @Override
    public void update(Long key,AdPlanIndexObject value) {

        log.info("AdPlanIndexAwareImpl before update::{}",planIndexObjectMap);
				//查詢當前的索引資訊,如果不存在,直接新增索引資訊
        AdPlanIndexObject oldObj = planIndexObjectMap.get(key);
        if (null == oldObj) {
            planIndexObjectMap.put(key,value);
        } else {
            oldObj.update(value);
        }

        log.info("AdPlanIndexAwareImpl after update::{}",planIndexObjectMap);
    }

    @Override
    public void delete(Long key,AdPlanIndexObject value) {

        log.info("AdPlanIndexAwareImpl before delete::{}",planIndexObjectMap);
        planIndexObjectMap.remove(key);
        log.info("AdPlanIndexAwareImpl after delete::{}",planIndexObjectMap);
    }
}
複製程式碼

至此,我們已經完成了推廣計劃的索引物件和索引操作的程式碼編寫,大家可以參考上面的示例,依次完成推廣單元推廣創意地域興趣關鍵詞以及推廣創意和推廣單元的關聯索引,或者可直接從 Github傳送門 / Gitee傳送門 下載原始碼。

按照上述程式碼展示,我們已經實現了所有的索引操作的定義,但是實際情況中,我們需要使用這些服務的時候,需要在每一個Service中@Autowired注入,我們那麼多的索引操作類,還不包含後續還有可能需要新增的索引維度,工作量實在是太大,而且不方便維護,作為一個合格的程式設計師來說,這是非常不友好的,也許會讓後續的開發人員罵娘。

為了防止後續被罵,我們來編寫一個索引快取工具類com.sxzhongf.ad.index.IndexDataTableUtils,通過這個索引快取工具類來實現一次注入,解決後顧之憂。要實現這個工具類,我們需要實現2個介面:org.springframework.context.ApplicationContextAwareorg.springframework.core.PriorityOrdered

  • org.springframework.context.ApplicationContextAware,統一通過實現該介面的類,來操作Spring容器以及其中的Bean例項。 在Spring中,以Aware為字尾結束的類,大家可以簡單的理解為應用程式想要XXX,比如ApplicationContextAware代表應用程式想要ApplicationContext,BeanFactoryAware 表示應用程式想要BeanFactory...等等
  • org.springframework.core.PriorityOrdered元件載入順序,也可以使用org.springframework.core.Ordered 以下程式碼為我們的工具類:
package com.sxzhongf.ad.index;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.PriorityOrdered;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * IndexDataTableUtils for 所有索引服務需要快取的Java Bean
 *
 * 使用方式:
 * 獲取{@link com.sxzhongf.ad.index.creative.CreativeIndexAwareImpl}索引服務類
 * 如下:
 * {@code
 *   IndexDataTableUtils.of(CreativeIndexAwareImpl.class)
 * }
 * @author <a href="mailto:[email protected]">Isaac.Zhang | 若初</a>
 */
@Component
public class IndexDataTableUtils implements ApplicationContextAware,PriorityOrdered {

    //注入ApplicationContext
    private static ApplicationContext applicationContext;

    /**
     * 定義用於儲存所有Index的Map
     * Class標示我們的索引類
     */
    private static final Map<Class,Object> dataTableMap = new ConcurrentHashMap<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        IndexDataTableUtils.applicationContext = applicationContext;
    }

    /**
     * 獲取索引服務快取
     */
    public static <T> T of(Class<T> klass) {
        T instance = (T) dataTableMap.get(klass);
        //如果獲取到索引bean,直接返回當前bean
        if (null != instance) {
            return instance;
        }
        //首次獲取索引bean為空,寫入Map
        dataTableMap.put(klass,bean(klass));
        return (T) dataTableMap.get(klass);
    }

    /**
     * 獲取Spring 容器中的Bean物件
     */
    private static <T> T bean(String beanName) {
        return (T) applicationContext.getBean(beanName);
    }

    /**
     * 獲取Spring 容器中的Bean物件
     */
    private static <T> T bean(Class klass) {
        return (T) applicationContext.getBean(klass);
    }

    @Override
    public int getOrder() {
        return PriorityOrdered.HIGHEST_PRECEDENCE;
    }
}
複製程式碼