该文主要因为有网友提到,回复微信消息时总是提示服务故障。我自己也试了我的代码,确实出了问题,推测该原因是微信对消息文本的格式做了更新导致的,后面验证确实如此。
问题修复
回复消息失败原因
返回xml文本格式不正确,在回复的field中,若要求有CData的形式,必须要以相应格式输出,且不能转义。
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();
}
}
在输出的实体类中添加该类型适配器注解
注意,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;
}
新增转义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");
// 不转义,根据需要添加
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,后面只要将输出的实体类,转为xml输出即可
其实在回复消息时,比如回复关键词,从数据库引入是比较合理的,便于更新维护,因此,以下借助这个需求,进行整合JPA的示例
整合JPA
准备
引入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>
新建数据库-服务微信公众号
- 建库
CREATE DATABASE IF NOT EXISTS flow_gzh DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci;
- 授权
grant all privileges on *.* to 'root'@'%' identified by '***' with grant option;
FLUSH PRIVILEGES;
配置数据源与jpa
spring:
# mysql数据源配置
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建表
创建关系对象的消息实体类
@Entity
@Data
@Table(name = "t_reply_message")
public class ReplyMessage {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String keyword;
private String text;
}
创建编写基于JPA的repository
@Repository
public interface ReplyMessageRepository extends JpaRepository<ReplyMessage,Long> {}
编写测试类,运行时创建表格
@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);
}
}
启动成功后,即可看到数据库表已自动建立
imageimage如果启动测试失败,需要参看自己启动参数是否正确
重构回复消息方法
以下以被动回复文本消息为例:
Repository中新增关键词查询的方法
@Repository
public interface ReplyMessageRepository extends JpaRepository<ReplyMessage,Long> {
ReplyMessage findByKeyword(String keyword);
}
如果Repository不能满足需求,建议使用@Query等注解自定义sql查询
处理被动回复文本消息方法
// 文本消息回复
private String handleTextMsg(String toUser, String fromUser, Long createTime, String rcvContent)
throws JAXBException {
// 关键字回复,可使用properties或数据库
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("公众号接收消息:[{}}, 回复消息:[{}]",rcvMsg,replyMsg);
return replyMsg;
}
- 注:如果返回null, 则默认公众号不返回消息
测试通过:
image详细过程,可参考源代码:https://github.com/chetwhy/cloud-flow
网友评论