後台管理系統常見需求:管理員發送站內信,支援「指定用戶群發」與「全員廣播」兩種模式。本文解析一個實戰 Controller,涵蓋 Jsoup 爬取遠程用戶列表、Gson 多層泛型解析、批量入庫、發送日誌記錄 完整鏈路。
@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
OkHttp/HttpClient.timeout(10000) 同時覆蓋連接/讀取超時// 遠程返回結構:{ "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
為什麼要「中轉 toJson 再 fromJson」?
Gson 解析List<Object>時,泛型擦除導致元素變為LinkedTreeMap,無法直接轉UserInfo。序列化為 JSON 再反序列化可繞過類型擦除,是「類型擦除環境下安全轉換」的標準技巧。
service.add() → N 次 INSERT,大量用戶極慢batchInsert(List<WebMessage>) 或 MyBatis <foreach> 批次 SQL@Transactional 保證原子性,失敗全回滾// 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,各層單一職責、可單測、可替換。