該文主要因?yàn)橛芯W(wǎng)友提到,回復(fù)微信消息時(shí)總是提示服務(wù)故障。我自己也試了我的代碼,確實(shí)出了問題,推測該原因是微信對消息文本的格式做了更新導(dǎo)致的,后面驗(yàn)證確實(shí)如此。
問題修復(fù)
回復(fù)消息失敗原因
返回xml文本格式不正確,在回復(fù)的field中,若要求有CData的形式,必須要以相應(yīng)格式輸出,且不能轉(zhuǎn)義。
image
解決步驟
自定義CData適配器
public class CDataAdapter extends XmlAdapter<String, String> {
@Override
public String unmarshal(String v) throws Exception {
return v;
}
@Override
public String marshal(String v) throws Exception {
return new StringBuilder("<![CDATA[").append(v).append("]]>").toString();
}
}
在輸出的實(shí)體類中添加該類型適配器注解
注意,createTime不要求cdata
@Data
@XmlRootElement(name = "xml")
@XmlAccessorType(XmlAccessType.FIELD)
@Accessors(chain = true)
public class TextReplyMsg{
@XmlJavaTypeAdapter(CDataAdapter.class)
@XmlElement(name = "ToUserName")
private String toUserName;
@XmlJavaTypeAdapter(CDataAdapter.class)
@XmlElement(name = "FromUserName")
private String fromUserName;
@XmlElement(name = "CreateTime")
private Long createTime;
@XmlJavaTypeAdapter(CDataAdapter.class)
@XmlElement(name = "MsgType")
private final String msgType = MsgType.TEXT;
@XmlJavaTypeAdapter(CDataAdapter.class)
@XmlElement(name = "Content")
private String content;
}
新增轉(zhuǎn)義xml的方法
public static String beanToXml(Object obj, java.lang.Class<?> clazz) throws JAXBException {
String result = null;
try {
JAXBContext context = JAXBContext.newInstance(clazz);
Marshaller marshaller = context.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8");
// 不轉(zhuǎn)義,根據(jù)需要添加
marshaller.setProperty(CharacterEscapeHandler.class.getName(),
(CharacterEscapeHandler)(chars, start, length, isAttVal, writer) -> writer.write(chars, start, length));
StringWriter writer = new StringWriter();
marshaller.marshal(obj, writer);
result = writer.toString();
} catch (JAXBException e) {
e.printStackTrace();
System.out.println("JAXBException happened.");
}
return result;
}
ok,后面只要將輸出的實(shí)體類,轉(zhuǎn)為xml輸出即可
其實(shí)在回復(fù)消息時(shí),比如回復(fù)關(guān)鍵詞,從數(shù)據(jù)庫引入是比較合理的,便于更新維護(hù),因此,以下借助這個(gè)需求,進(jìn)行整合JPA的示例
整合JPA
準(zhǔn)備
引入JPA, MySql依賴
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--jpa-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
新建數(shù)據(jù)庫-服務(wù)微信公眾號
- 建庫
CREATE DATABASE IF NOT EXISTS flow_gzh DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;
- 授權(quán)
grant all privileges on *.* to 'root'@'%' identified by '***' with grant option;
FLUSH PRIVILEGES;
配置數(shù)據(jù)源與jpa
spring:
# mysql數(shù)據(jù)源配置
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://120.79.27.209:33306/flow_gzh?characterEncoding=utf8&useSSL=false
username: root
password: ENC(xQ/ZcWd0bfj9qaCd6qyNlA==)
# jpa配置
jpa:
show-sql: true
database: mysql
hibernate:
ddl-auto: update
naming:
physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
properties.hibernate.dialect: org.hibernate.dialect.MySQL5Dialect
利用JPA建表
創(chuàng)建關(guān)系對象的消息實(shí)體類
@Entity
@Data
@Table(name = "t_reply_message")
public class ReplyMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String keyword;
private String text;
}
創(chuàng)建編寫基于JPA的repository
@Repository
public interface ReplyMessageRepository extends JpaRepository<ReplyMessage,Long> {}
編寫測試類,運(yùn)行時(shí)創(chuàng)建表格
@SpringBootTest
@RunWith(SpringRunner.class)
@ActiveProfiles("dev")
@ContextConfiguration(classes = {SpringDataJpaConfig.class, GzhApplication.class})
public class GzhApplicationTests {
private static final Logger logger = LoggerFactory.getLogger(GzhApplicationTests.class);
@Autowired
private ReplyMessageRepository replyMessageRepository;
@Before
public void create() {
ReplyMessage replyMessage = new ReplyMessage();
replyMessage.setKeyword("who");
replyMessage.setText("王二狗");
replyMessageRepository.save(replyMessage);
assert replyMessage.getId() > 0 : "error";
}
@Test
public void getData() {
List<ReplyMessage> all = replyMessageRepository.findAll();
assert all!=null:"table is null";
all.forEach(System.out::println);
}
}
啟動成功后,即可看到數(shù)據(jù)庫表已自動建立
image
如果啟動測試失敗,需要參看自己啟動參數(shù)是否正確
image
重構(gòu)回復(fù)消息方法
以下以被動回復(fù)文本消息為例:
Repository中新增關(guān)鍵詞查詢的方法
@Repository
public interface ReplyMessageRepository extends JpaRepository<ReplyMessage,Long> {
ReplyMessage findByKeyword(String keyword);
}
如果Repository不能滿足需求,建議使用@Query等注解自定義sql查詢
處理被動回復(fù)文本消息方法
// 文本消息回復(fù)
private String handleTextMsg(String toUser, String fromUser, Long createTime, String rcvContent)
throws JAXBException {
// 關(guān)鍵字回復(fù),可使用properties或數(shù)據(jù)庫
ReplyMessage replyMessage = replyMessageRepository.findByKeyword(rcvContent);
if (replyMessage != null && !replyMessage.getText().isEmpty()) {
TextReplyMsg textReplyMsg = new TextReplyMsg().setToUserName(toUser).setFromUserName(fromUser)
.setCreateTime(createTime).setContent(replyMessage.getText());
return XmlConvertUtil.beanToXml(textReplyMsg, TextReplyMsg.class);
}
return null;
}
消息接口
@PostMapping("/portal")
public String handleMessage(@RequestBody RcvCommonMsg rcvCommonMsg) throws JAXBException {
String replyMsg = messageService.handleMessage(rcvCommonMsg);
// TODO 暫不支持直接bean注解輸出
String rcvMsg = XmlConvertUtil.beanToXml(rcvCommonMsg, RcvCommonMsg.class);
logger.info("公眾號接收消息:[{}}, 回復(fù)消息:[{}]",rcvMsg,replyMsg);
return replyMsg;
}
- 注:如果返回null, 則默認(rèn)公眾號不返回消息
測試通過:
image
詳細(xì)過程,可參考源代碼:https://github.com/chetwhy/cloud-flow