110 lines
4.3 KiB
Java
110 lines
4.3 KiB
Java
package com.example.mini_program.service;
|
|
|
|
import com.example.mini_program.config.WxMiniAppConfig;
|
|
import com.example.mini_program.util.HttpUtil;
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.beans.factory.annotation.Value;
|
|
import org.springframework.stereotype.Service;
|
|
|
|
import java.util.HashMap;
|
|
import java.util.Map;
|
|
|
|
/**
|
|
* 微信小程序订阅消息服务
|
|
* 调用微信 /cgi-bin/message/subscribe/send 接口推送订阅消息
|
|
* <p>
|
|
* 模板字段映射:
|
|
* name1 (姓名) → 访客姓名
|
|
* thing3 (事物) → 来访事由
|
|
* date8 (日期) → 预约时间
|
|
* thing10 (事物) → 到访区域
|
|
* phrase18 (短语) → 审批状态
|
|
*/
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class WxSubscribeMessageService {
|
|
|
|
private static final String GET_TOKEN_URL =
|
|
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s";
|
|
private static final String SEND_MSG_URL =
|
|
"https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=%s";
|
|
|
|
private final WxMiniAppConfig wxMiniAppConfig;
|
|
private final ObjectMapper objectMapper;
|
|
|
|
@Value("${wx.miniapp.subscribe-template-id:}")
|
|
private String templateId;
|
|
|
|
/**
|
|
* 获取小程序 access_token
|
|
*/
|
|
public String getAccessToken() {
|
|
String url = String.format(GET_TOKEN_URL, wxMiniAppConfig.getAppid(), wxMiniAppConfig.getSecret());
|
|
try {
|
|
Map<String, Object> resp = objectMapper.readValue(HttpUtil.get(url), Map.class);
|
|
if (resp.containsKey("access_token")) {
|
|
return (String) resp.get("access_token");
|
|
}
|
|
throw new RuntimeException("获取access_token失败: " + resp);
|
|
} catch (Exception e) {
|
|
throw new RuntimeException("获取access_token异常: " + e.getMessage(), e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 发送订阅消息
|
|
* 所有字段不允许为空字符串(微信返回47003),name类型不接受纯数字
|
|
*/
|
|
public void sendSubscribeMessage(String openid, String visitorName, String reason,
|
|
String visitTime, String area, String status) {
|
|
if (templateId == null || templateId.isEmpty()) {
|
|
log.warn("未配置 subscribe-template-id,跳过订阅消息推送");
|
|
return;
|
|
}
|
|
String accessToken = getAccessToken();
|
|
String url = String.format(SEND_MSG_URL, accessToken);
|
|
|
|
// name1 是 name 类型,纯数字会被微信拒绝,需加前缀兜底
|
|
String safeName = blankToDefault(visitorName, "访客");
|
|
if (safeName.matches("^\\d+$")) {
|
|
safeName = "访客" + safeName;
|
|
}
|
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
data.put("name1", Map.of("value", safeName));
|
|
data.put("thing3", Map.of("value", blankToDefault(reason, "来访")));
|
|
data.put("date8", Map.of("value", blankToDefault(visitTime, "待定")));
|
|
data.put("thing10", Map.of("value", blankToDefault(area, "未指定")));
|
|
data.put("phrase18", Map.of("value", blankToDefault(status, "待审核")));
|
|
|
|
Map<String, Object> body = new HashMap<>();
|
|
body.put("touser", openid);
|
|
body.put("template_id", templateId);
|
|
//点击模板卡片后的跳转页面,仅限本小程序内的页面。
|
|
body.put("page", "pages/records/records");
|
|
body.put("data", data);
|
|
//跳转小程序类型:developer为开发版;trial为体验版;formal为正式版;默认为正式版
|
|
body.put("miniprogram_state", "formal");
|
|
body.put("lang", "zh_CN");
|
|
|
|
try {
|
|
String json = objectMapper.writeValueAsString(body);
|
|
Map<String, Object> resp = objectMapper.readValue(HttpUtil.postJson(url, json), Map.class);
|
|
if ("0".equals(String.valueOf(resp.get("errcode")))) {
|
|
log.info("订阅消息推送成功, openid={}", openid);
|
|
} else {
|
|
log.error("订阅消息推送失败: {}", resp);
|
|
}
|
|
} catch (Exception e) {
|
|
log.error("订阅消息推送异常", e);
|
|
}
|
|
}
|
|
|
|
private static String blankToDefault(String val, String def) {
|
|
return (val != null && !val.isEmpty()) ? val : def;
|
|
}
|
|
}
|