《蒼穹外賣》項目相關(guān)技術(shù)總結(jié)

1. 利用AOP實現(xiàn)公共字段的自動填充

1.1 應(yīng)用場景

  • 項目中設(shè)計的員工表, 菜品表, 套餐表和分類表中均有涉及到創(chuàng)建時間, 創(chuàng)建人, 更新時間, 更新人這四個字段, 當我們對這四張表進行插入數(shù)據(jù)和修改數(shù)據(jù)時, 就涉及到對這四個字段的設(shè)置. 其中, 當插入數(shù)據(jù)時, 四個字段都需要設(shè)置, 當修改數(shù)據(jù)時, 則只需要設(shè)置更新時間和更新人, 所以對于這些重復的業(yè)務(wù)代碼, 可以采用AOP技術(shù)來實現(xiàn)這些公共字段的自動填充.

1.2 實現(xiàn)步驟

  1. 自定義注解@AutoFill 以及枚舉類 OperationType
/**
 * 自定義注解, 方便某些功能字段的自動填充
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoFill {
    //數(shù)據(jù)庫操作類型: UPDATE   INSERT
    OperationType value();
}
  • 因為插入和更新所需操作的字段不同, 所以創(chuàng)建枚舉類來區(qū)分操作的類型
/**
 * 數(shù)據(jù)庫操作類型
 */
public enum OperationType {
    /**
     * 更新操作
     */
    UPDATE,
    /**
     * 插入操作
     */
    INSERT
}
  1. 創(chuàng)建切面類利用AOP和反射的技術(shù)來實現(xiàn)公共字段自動填充的代碼邏輯
/**
 * 自定義切面, 實現(xiàn)公共字段自動填充代碼邏輯
 */
@Aspect
@Component
@Slf4j
public class AutoFillAspect {

    /**
     * 切入點
     */
    @Before("execution(* com.sky.mapper.*.*(..)) && @annotation(com.sky.annotation.AutoFill)")
    public void autoFill(JoinPoint joinPoint) {
        log.info("開始進行公共字段的自動填充...");

        //獲取當前被攔截的方法上的數(shù)據(jù)庫操作類型
        //INSERT則需要更改四個數(shù)據(jù)
        //UPDATE則只需要更改兩個數(shù)據(jù)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();//方法簽名對象
        OperationType value = signature.getMethod().getAnnotation(AutoFill.class).value();//獲得方法注解的數(shù)據(jù)類型

        //獲取當前被攔截方法的參數(shù), 約定參數(shù)中的實體類放在第一位
        Object[] args = joinPoint.getArgs();
        if (args == null || args.length == 0) return;
        //獲取第一位的實體參數(shù), 約定參數(shù)中的實體類放在第一位
        //選用Object來接收是因為操作的表不一樣, 傳輸?shù)膶嶓w類不同, 所以直接采用Object
        Object entity = args[0];

        //準備賦值的數(shù)據(jù)
        LocalDateTime now = LocalDateTime.now();
        Long currentId = BaseContext.getCurrentId();

        //根據(jù)當前不同的數(shù)據(jù)庫類型, 來通過反射為相關(guān)屬性賦值
        if (value == OperationType.INSERT) {
            //為4個公共字段賦值
            try {
                //先得到4個set方法
                Method setCreateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_TIME, LocalDateTime.class);
                Method setCreateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_CREATE_USER, Long.class);
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通過反射為方法賦值
                setCreateTime.invoke(entity, now);
                setCreateUser.invoke(entity, currentId);
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } else if (value == OperationType.UPDATE) {
            //為2個公共字段賦值
            try {
                //先得到2個set方法
                Method setUpdateTime = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_TIME, LocalDateTime.class);
                Method setUpdateUser = entity.getClass().getDeclaredMethod(AutoFillConstant.SET_UPDATE_USER, Long.class);

                //通過反射為方法賦值
                setUpdateTime.invoke(entity, now);
                setUpdateUser.invoke(entity, currentId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

2. 使用阿里云來存儲上傳的圖片

2.1 應(yīng)用場景

  • 為了實現(xiàn)菜品圖片的上傳功能, 采用阿里云OSS來存儲上傳的圖片, 并返回圖片的url, 使其能夠在前端頁面顯示.

2.2 實現(xiàn)步驟

  • 通過配置阿里云OSS相關(guān)配置屬性, 以及圖片上傳工具類來完成圖片上傳.

3. 使用Redis來存儲店鋪的營業(yè)狀態(tài)

3.1 應(yīng)用場景

  • 店鋪的營業(yè)狀態(tài)設(shè)置為營業(yè)中和打烊中, 只有兩種狀態(tài), 所以可以將店鋪的營業(yè)狀態(tài)存儲到數(shù)據(jù)庫中, 方便修改和查詢, 由于僅有一個字段, 如果用Mysql數(shù)據(jù)庫來存儲較為浪費, 所以可以采用Redis數(shù)據(jù)庫來存儲.

3.2 實現(xiàn)步驟

  1. 導入Redis的maven坐標
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
  1. 創(chuàng)建redis的配置類
@Configuration
@Slf4j
public class RedisConfiguration {

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory){
        log.info("開始創(chuàng)建redis模板對象...");
        RedisTemplate redisTemplate = new RedisTemplate();
        //設(shè)置redis的連接工廠對象
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //設(shè)置redis Key序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}
  1. 注入RedisTemplate 并實現(xiàn)相關(guān)代碼, RedisTemplate 即為reids對象
    public static final String KEY = "SHOP_STATUS";

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 設(shè)置店鋪營業(yè)狀態(tài)
     *
     * @param status status
     * @return @return
     */
    @PutMapping("/{status}")
    public Result setStatus(@PathVariable Integer status) {
        log.info("設(shè)置店鋪營業(yè)狀態(tài)為: {}", status.equals(StatusConstant.ENABLE) ? "營業(yè)中" : "打烊中");
        redisTemplate.opsForValue().set(KEY, status);
        return Result.success();
    }

4. 利用Redis以及Spring Cache來實現(xiàn)菜品套餐數(shù)據(jù)的緩存

4.1 應(yīng)用場景

  • 在小程序端用戶點餐時就會頻繁查詢菜品及套餐數(shù)據(jù), 沒有緩存時, 用戶每切換一個分類就會重新查詢一次, 如果多個用戶查詢頻繁, 會使數(shù)據(jù)庫的壓力較大, 所以我們可以將查詢出來的數(shù)據(jù)緩存到Redis當中, 這樣就不會頻繁查詢數(shù)據(jù)庫, 當數(shù)據(jù)發(fā)生變更時, 我們將Redis中的原先的緩存刪除, 再次查詢再添加到緩存中即可.

4.2 Spring Cache介紹

  • Spring Cache是一個框架, 實現(xiàn)了基于注解的緩存功能, 只需要簡單地加一個注解, 就能實現(xiàn)緩存功能. Spring Cache提供了一層抽象, 底層可以切換不同的緩存實現(xiàn), 例如: EHCache, Caffeine, Redis . 我們導入不同的坐標即可切換不同的緩存, 無需改動代碼.
  • 導入Spring Cache的maven坐標
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
  • Spring Cache常用注解


4.3 實現(xiàn)步驟

  1. 導入Spring Cache和Redis的maven坐標(已導入)
  • 在啟動類上加入@EnableCaching注解, 開啟緩存注解功能
@EnableCaching //開啟緩存注解功能
  • 在用戶端接口SetmealController的 list方法上加入@Cacheable注解
    @Cacheable(cacheNames = "setmealCache",key = "#categoryId") //cacheNames::key
  • 在管理端接口SetmealController的save、delete、update览芳、startOrStop等方法上加入CacheEvict注解(數(shù)據(jù)發(fā)生變更則清理緩存)
    @CacheEvict(cacheNames = "setmealCache", key = "setmealDTO.categoryId")//精確清理
    @CacheEvict(cacheNames = "setmealCache", allEntries = true)//清除所有

5. 利用Spring Task完成過期訂單的定時處理

5.1 應(yīng)用場景

  • 當用戶下單后, 未在15分鐘之內(nèi)支付, 商家應(yīng)該自動取消該訂單. 對于每天商家派送的訂單未及時點擊完成, 我們決定在每天的凌晨1點來自動處理, 將未及時點擊完成的訂單自動更改為完成.

5.2 Spring Task介紹

  • Spring Task是一個任務(wù)調(diào)度框架, 用于處理定時任務(wù). 例如: 每月還款提醒, 超時未支付取消訂單, 生日發(fā)送祝福等等

5.2.1 Cron表達式

  • 是一個字符串, 可以通過cron表達式定義任務(wù)觸發(fā)的時間
  • 構(gòu)成規(guī)則: 分為6個或7個域, 有空格分隔開, 每個域代表一個含義
  • 每個域的含義分別為: 秒, 分鐘, 小時, 日, 月, 周, 年(可選)


5.2.2 Cron表達式在線生成器

5.2.3 Spring Task使用步驟

  1. 導入maven坐標 spring-context
  2. 啟動類上添加注解 @EnableScheduling 開啟任務(wù)調(diào)度
@EnableScheduling //開啟任務(wù)調(diào)度
  1. 自定義定時任務(wù)類(包含定時任務(wù)的業(yè)務(wù)邏輯, 觸發(fā)時間), 類中寫方法來實現(xiàn)業(yè)務(wù)邏輯, 在方法上加 @Scheduled 注解寫入cron表達式來指定觸發(fā)時間
@Component //需要實例化,交給IOC容器管理
@Slf4j
public class MyTask {
    /**
     * 定時任務(wù),每隔5秒觸發(fā)一次
     */
    @Scheduled(cron = "0/5 * * * * ?")
    public void executeTask() {
        log.info("定時任務(wù)開始執(zhí)行: {}", new Date());
    }
}
部分輸出結(jié)果如下
定時任務(wù)開始執(zhí)行: Mon Jul 29 20:06:20 CST 2024
定時任務(wù)開始執(zhí)行: Mon Jul 29 20:06:25 CST 2024
定時任務(wù)開始執(zhí)行: Mon Jul 29 20:06:30 CST 2024
定時任務(wù)開始執(zhí)行: Mon Jul 29 20:06:35 CST 2024

5.3 業(yè)務(wù)代碼實現(xiàn)

/**
 * 定時任務(wù)類,定時處理超時狀態(tài)訂單
 */
@Slf4j
@Component
public class OrderTask {
    @Autowired
    private OrderMapper orderMapper;

    /**
     * 處理超時訂單的方法
     */
    @Scheduled(cron = "0 * * * * ?")    //每分鐘觸發(fā)一次
    //@Scheduled(cron = "1/5 * * * * ?")    //測試采用第一秒開始,每五秒觸發(fā)
    public void processTimeoutOrder() {
        log.info("定時處理超時訂單: {}", LocalDateTime.now());

        //select * from orders where status = ? and order_time < (當前時間 - 15分鐘)
        LocalDateTime OrderTime = LocalDateTime.now().plusMinutes(-15);
        List<Orders> list = orderMapper.getByStatusAndOrderTime(Orders.PENDING_PAYMENT, OrderTime);

        if (list != null && !list.isEmpty()) {
            for (Orders orders : list) {
                orders.setStatus(Orders.CANCELLED);
                orders.setCancelTime(LocalDateTime.now());
                orders.setCancelReason(MessageConstant.ORDER_PAID_OVER_TIME);
                orderMapper.update(orders);
            }
        }
    }

    /**
     * 處理一直處于派送中的訂單
     */
    @Scheduled(cron = "0 0 1 * * ?")    //每天凌晨1點觸發(fā)
    //@Scheduled(cron = "0/5 * * * * ?")    //測試采用第0秒開始,每五秒觸發(fā)
    public void processDeliveryOrder() {
        log.info("凌晨1點處理處于派送中的訂單: {}", LocalDateTime.now());

        LocalDateTime time = LocalDateTime.now().plusMinutes(-60);
        List<Orders> list = orderMapper.getByStatusAndOrderTime(Orders.DELIVERY_IN_PROGRESS, time);

        if (list != null && !list.isEmpty()) {
            for (Orders orders : list) {
                orders.setStatus(Orders.COMPLETED);
                orderMapper.update(orders);
            }
        }
    }
}

6. 使用Web Socket完成客戶端與服務(wù)器的長連接

6.1 應(yīng)用場景

  • 當用戶下單時, 管理客戶端要實時有來單提醒及語音播報; 當用戶催單時, 也要實時語音播報.

6.2 Web Socket介紹

  • WebSocket是基于TCP的一種新的網(wǎng)絡(luò)協(xié)議抱究。它實現(xiàn)了瀏覽器與服務(wù)器全雙工通信——瀏覽器和服務(wù)器只需要完成一次握手蛋哭,兩者之間就可以創(chuàng)建持久性的連接添祸,并進行雙向數(shù)據(jù)傳輸。

6.2.1 與HTTP協(xié)議的對比

6.2.2 Web Socket應(yīng)用場景

  • 觀看視頻的彈幕
  • 網(wǎng)頁聊天對話
  • 體育實況數(shù)據(jù)實時更新
  • 股票基金報價實時更新

6.2.3 WebSocket缺點:

  • 服務(wù)器長期維護長連接需要一定的成本, 各個瀏覽器支持程度不一樣, WebSocket 是長連接绍申,受網(wǎng)絡(luò)限制比較大拒名,需要處理好重連

結(jié)論:WebSocket并不能完全取代HTTP吩愧,它只適合在特定的場景下使用

7. 利用Apache POI完成報表的制作

7.1 應(yīng)用場景

  • 當管理人員點擊數(shù)據(jù)導出時, 可以得到近30天的銷售數(shù)據(jù)統(tǒng)計報表(Excel表).

7.2 Apache POI介紹

  • Apache POI 是一個處理 Miscrosoft Office 各種文件格式的開源項目. 簡單來說就是, 我們可以使用 POI 在 Java 程序中對 Miscrosoft Office 各種文件進行讀寫操作, 一般情況下, POI 都是用于操作 Excel 文件.

7.2.1 Apache POI應(yīng)用場景

  • 銀行網(wǎng)銀系統(tǒng)導出交易明細
  • 各種業(yè)務(wù)系統(tǒng)導出Excel報表
  • 批量導入業(yè)務(wù)數(shù)據(jù)

7.2.2 使用POI的步驟

  1. 導入 Apache POI 的 maven 坐標
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
        </dependency>
  1. 根據(jù)POI提供的各種方法來操作Excel文件(讀取操作需要結(jié)合輸入輸出流來實現(xiàn))

7.3 業(yè)務(wù)代碼

  • 對于復雜的表格, 我們一般不采用Java代碼來制作Excel表, 先提前創(chuàng)建好Excel表, 然后導入表格, 以該表格為模板, 利用Java代碼往里面填充數(shù)據(jù)即可.
    /**
     * 導出運營數(shù)據(jù)報表
     *
     * @param response response
     */
    @Override
    public void exportBusinessDate(HttpServletResponse response) {
        //1. 查詢數(shù)據(jù)庫,獲取營業(yè)數(shù)據(jù)--查詢最近30天營業(yè)數(shù)據(jù)
        LocalDate dateBeginTime = LocalDate.now().minusDays(30);
        LocalDate dateEndTime = LocalDate.now().minusDays(1);
        LocalDateTime beginTime = LocalDateTime.of(dateBeginTime, LocalTime.MIN);
        LocalDateTime endTime = LocalDateTime.of(dateEndTime, LocalTime.MAX);
        BusinessDataVO businessData = workspaceService.getBusinessData(beginTime, endTime);

        //2. 通過POI將數(shù)據(jù)寫入到Excel文件中
        //獲取輸入流將Excel文件模板讀取出來
        InputStream inputStream = this.getClass().getClassLoader()
                .getResourceAsStream("template/運營數(shù)據(jù)報表模板.xlsx");

        try {
            //基于模板創(chuàng)建新的Excel文件
            assert inputStream != null;
            XSSFWorkbook excel = new XSSFWorkbook(inputStream);

            //獲取表格文件的sheet頁
            XSSFSheet sheet = excel.getSheet("Sheet1");

            //填充數(shù)據(jù)時間
            sheet.getRow(1).getCell(1).setCellValue("時間" + dateBeginTime + "至" + dateEndTime);

            //獲得第4行并填充相關(guān)數(shù)據(jù)
            XSSFRow row = sheet.getRow(3);
            row.getCell(2).setCellValue(businessData.getTurnover());
            row.getCell(4).setCellValue(businessData.getOrderCompletionRate());
            row.getCell(6).setCellValue(businessData.getNewUsers());
            //獲得第5行并填充相關(guān)數(shù)據(jù)
            row = sheet.getRow(4);
            row.getCell(2).setCellValue(businessData.getValidOrderCount());
            row.getCell(4).setCellValue(businessData.getUnitPrice());

            //填充明細
            for (int i = 0; i < 30; i++) {
                //獲取到某一天
                LocalDate date = dateBeginTime.plusDays(i);
                //查詢某一天的營業(yè)數(shù)據(jù)
                BusinessDataVO vo = workspaceService.getBusinessData(LocalDateTime.of(date, LocalTime.MIN),
                        LocalDateTime.of(date, LocalTime.MAX));

                //獲得某一行
                row = sheet.getRow(7 + i);
                //填充數(shù)據(jù)
                row.getCell(1).setCellValue(date.toString());
                row.getCell(2).setCellValue(vo.getTurnover());
                row.getCell(3).setCellValue(vo.getValidOrderCount());
                row.getCell(4).setCellValue(vo.getOrderCompletionRate());
                row.getCell(5).setCellValue(vo.getUnitPrice());
                row.getCell(6).setCellValue(vo.getNewUsers());
            }

            //3. 通過輸出流將Excel文件下載到客戶端瀏覽器
            ServletOutputStream outputStream = response.getOutputStream();
            excel.write(outputStream);

            //關(guān)閉資源
            outputStream.close();
            excel.close();
            inputStream.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市增显,隨后出現(xiàn)的幾起案子雁佳,更是在濱河造成了極大的恐慌,老刑警劉巖同云,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件糖权,死亡現(xiàn)場離奇詭異,居然都是意外死亡炸站,警方通過查閱死者的電腦和手機星澳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來武契,“玉大人募判,你說我怎么就攤上這事≈渌簦” “怎么了?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵释液,是天一觀的道長全释。 經(jīng)常有香客問我,道長误债,這世上最難降的妖魔是什么浸船? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮寝蹈,結(jié)果婚禮上李命,老公的妹妹穿的比我還像新娘。我一直安慰自己箫老,他們只是感情好封字,可當我...
    茶點故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般阔籽。 火紅的嫁衣襯著肌膚如雪流妻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天笆制,我揣著相機與錄音绅这,去河邊找鬼。 笑死在辆,一個胖子當著我的面吹牛证薇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匆篓,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼棕叫,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了奕删?” 一聲冷哼從身側(cè)響起俺泣,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎完残,沒想到半個月后伏钠,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡谨设,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年熟掂,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扎拣。...
    茶點故事閱讀 39,977評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赴肚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出二蓝,到底是詐尸還是另有隱情誉券,我是刑警寧澤,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布刊愚,位于F島的核電站踊跟,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鸥诽。R本人自食惡果不足惜商玫,卻給世界環(huán)境...
    茶點故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望牡借。 院中可真熱鬧拳昌,春花似錦、人聲如沸钠龙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刻像,卻和暖如春畅买,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背细睡。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工谷羞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溜徙。 一個月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓湃缎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蠢壹。 傳聞我的和親對象是個殘疾皇子嗓违,可洞房花燭夜當晚...
    茶點故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容