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

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

问题修复

回复消息失败原因

返回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);
    }
}

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

image

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

image

重构回复消息方法

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

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,639评论 6 513
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 94,093评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 167,079评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,329评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,343评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 52,047评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,645评论 3 421
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,565评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,095评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,201评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,338评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 36,014评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,701评论 3 332
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,194评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,320评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,685评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,345评论 2 358

推荐阅读更多精彩内容