首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

站內信群發/全員發送實戰:Jsoup 遠程調用 + Gson 多層解析 + 事務性記錄

     編輯:Java 綜合教學

站內信群發/全員發送實戰:Jsoup 遠程調用 + Gson 多層解析 + 事務性記錄

後台管理系統常見需求:管理員發送站內信,支援「指定用戶群發」與「全員廣播」兩種模式。本文解析一個實戰 Controller,涵蓋 Jsoup 爬取遠程用戶列表、Gson 多層泛型解析、批量入庫、發送日誌記錄 完整鏈路。

核心 Controller 代碼

@Permission(Module.TZGL)
@RequestMapping(value = "/sendZnx", method = RequestMethod.POST)
@ResponseBody
public String sendZnx(HttpSession session,
                      @RequestParam(defaultValue = "") String[] userList,
                      @RequestParam(defaultValue = "") String content) {
    int rows = 0;
    String msg = "";

    try {
        if (userList.length == 0) { // 全員廣播模式
            // 1. Jsoup 遠程調用獲取用戶列表 JSON
            String result = Jsoup.connect(NMApi.url() + "/message/userlist")
                                 .timeout(10000).get().text();

            // 2. Gson 解析:JSON → Map → List → List
            Map<String, Object> data = new Gson().fromJson(result,
                new TypeToken<Map<String, Object>>(){}.getType());
            List<Object> rawList = (List<Object>) data.get("RecList");
            String jsonList = new Gson().toJson(rawList); // 中轉統一格式
            List<UserInfo> userInfoList = new Gson().fromJson(jsonList,
                new TypeToken<List<UserInfo>>(){}.getType());

            // 3. 批量構建站內信並入庫
            for (UserInfo u : userInfoList) {
                WebMessage wm = new WebMessage();
                wm.setClientId(u.getIcid());
                wm.setReceiveTime(new Date());
                wm.setType("系統通知");
                wm.setContent(content);
                wm.setStatus(0); // 未讀
                webMessageService.add(wm);
                rows++;
            }
            msg = "發送站內信成功,成功條數:" + rows;

        } else { // 指定用戶群發模式
            for (String uid : userList) {
                WebMessage wm = new WebMessage();
                wm.setClientId(Integer.parseInt(uid));
                wm.setReceiveTime(new Date());
                wm.setType("管理員消息");
                wm.setContent(content);
                wm.setStatus(0);
                webMessageService.add(wm);
                rows++;
            }
            msg = "發送站內信成功,成功條數:" + rows;

            // 4. 記錄發送日誌 (僅群發模式記錄)
            User admin = (User) session.getAttribute("user");
            MessageLog log = new MessageLog();
            log.setSendTime(new Date());
            log.setType("站內信");
            log.setSender(admin.getUnitName());
            log.setReceiver(Arrays.toString(userList));
            log.setContent(content);
            log.setResult(msg);
            log.setError("");
            messageLogService.add(log);
        }
        return msg;
    } catch (Exception e) {
        e.printStackTrace();
        return "系統出錯:" + e.getMessage();
    }
}

關鍵技術點深度解析

1. Jsoup 當 HTTP 客戶端

  • 優點:API 簡潔、自動處理 Cookie/重定向、支援 CSS 選擇器解析 HTML
  • 缺點:本質是 HTML 解析器,非專業 HTTP Client;生產環境高併發建議換 OkHttp/HttpClient
  • 超時設置:.timeout(10000) 同時覆蓋連接/讀取超時

2. Gson 多層泛型解析模式

// 遠程返回結構:{ "RecList": [ {icid:1, name:"A"}, {icid:2, name:"B"} ] }

// 步驟 1:JSON → Map
Map<String, Object> data = new Gson().fromJson(json,
    new TypeToken<Map<String, Object>>(){}.getType());

// 步驟 2:取出 List (實際為 LinkedTreeMap)
List<Object> raw = (List<Object>) data.get("RecList");

// 步驟 3:List → JSON 字符串 (統一格式)
String normalized = new Gson().toJson(raw);

// 步驟 4:JSON → List (目標實體)
List<UserInfo> list = new Gson().fromJson(normalized,
    new TypeToken<List<UserInfo>>(){}.getType());

為什麼要「中轉 toJson 再 fromJson」?
Gson 解析 List<Object> 時,泛型擦除導致元素變為 LinkedTreeMap,無法直接轉 UserInfo。序列化為 JSON 再反序列化可繞過類型擦除,是「類型擦除環境下安全轉換」的標準技巧。

3. 批量入庫效能優化建議

  • 當前逐條 service.add() → N 次 INSERT,大量用戶極慢
  • 改為 batchInsert(List<WebMessage>) 或 MyBatis <foreach> 批次 SQL
  • 加上 @Transactional 保證原子性,失敗全回滾

4. 異常處理與幂等性

  • 遠程調用失敗 → 直接返回錯誤,不入庫
  • 入庫中間拋異常 → 事務回滾,但前面已成功的用戶可能已收到信 (若無事務)
  • 建議:引入「發送任務表」+ 異步消費 (MQ),實現「至少一次」語義與重試

重構建議:職責分層

// Controller 薄層
@PostMapping("/send")
public R send(@Valid SendMsgCmd cmd) {
    return msgAppService.send(cmd.getUserIds(), cmd.getContent(), getCurrentUser());
}

// Application Service 編排
public R send(List<Integer> userIds, String content, User sender) {
    List<Integer> targets = userIds.isEmpty() ? userRemoteService.allUserIds() : userIds;
    msgDomainService.batchCreate(targets, content, sender);
    msgLogDomainService.record(sender, targets, content);
    return R.ok();
}

核心原則:Controller 只做參數校驗/协议轉換,業務編排下沉 Application Service,領域邏輯封裝 Domain Service,遠程調用封裝 Remote Service,各層單一職責、可單測、可替換。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved