微信公号快速开发(五)修复消息发送并整合JPA

578 阅读3分钟

该文主要因为有网友提到,回复微信消息时总是提示服务故障。我自己也试了我的代码,确实出了问题,推测该原因是微信对消息文本的格式做了更新导致的,后面验证确实如此。

问题修复

回复消息失败原因

返回xml文本格式不正确,在回复的field中,若要求有CData的形式,必须要以相应格式输出,且不能转义。

解决步骤

自定义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);
    }
}

启动成功后,即可看到数据库表已自动建立

如果启动测试失败,需要参看自己启动参数是否正确

重构回复消息方法

以下以被动回复文本消息为例:

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, 则默认公众号不返回消息

测试通过:


详细过程,可参考源代码:github.com/chetwhy/clo…