教你写个QQ机器人(2)搭建项目框架


开始SpringBoot之旅

2019/7/5 18:05 未完待续.....(其实是下班了,回去再写)

目前项目的目录(可能会有所改动),详细代码见我的GithubSunAlwaysOnline/kq


以下是我用到的依赖

MyBatis-plus:是MyBatis的增强版,封装了一些常用的sql方法,我们设置可以不用在dao、service与mapper文件编写方法。

Redis:内存型数据库,我把查询出来的垃圾分类存储在Redis中,提高查询效率。

Jsoup:请求外部的垃圾分类接口时,返回的是Html页面,因此需要对页面进行抓取数据。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sun</groupId>
    <artifactId>kq</artifactId>
    <version>1.0</version>
    <name>kq</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.1</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.38</version>
        </dependency>

        <!--Redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.11.3</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

配置MyBatis-plus与RestTemplate(SpringBoot中用来发送HTTP请求)

MybatisPlusConfig.java(分页暂时没用到,但在数据量过多时会用到,先给配置上)

package com.sun.kq.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.injector.ISqlInjector;
import com.baomidou.mybatisplus.extension.injector.LogicSqlInjector;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.sun.kq.dao")
public class MybatisPlusConfig {

	/**
	 * 分页插件
	 *
	 * @return PaginationInterceptor
	 */
	@Bean
	public PaginationInterceptor paginationInterceptor() {
		PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
		paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
		return paginationInterceptor;
	}

	/**
	 * 逻辑删除插件
	 *
	 * @return LogicSqlInjector
	 */
	@Bean
	public ISqlInjector sqlInjector() {
		return new LogicSqlInjector();
	}

}
RestTemplateConfig.java
package com.sun.kq.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

/**
 * RestTemplate配置类
 */
@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
        return new RestTemplate(factory);
    }

    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory() {
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(5000);//ms
        factory.setConnectTimeout(15000);//ms
        return factory;
    }
}

常量类(主要保存请求地址)

URLConst.java

package com.sun.kq.constant;

public class URLConst {

    /**
     * HTTP服务器地址
     */
    public static final String URL = "http://127.0.0.1:5700";

    /**
     * 发送私聊消息
     */
    public static final String SEND_PRIVATE_MSG = "/send_private_msg";

    /**
     * 获取群列表
     */
    public static final String GET_GROUP_LIST = "/get_group_list";


    /**
     * 获取该垃圾的分类,后面加垃圾名称的URL编码
     */
    public static final String GET_RUBBISH_TYPE = "https://lajifenleiapp.com/sk/";
}

编写消息上报的接口,用来接收QQ里收到的消息

MsgController.java(主要是看/receive接口)

/msg/receive接口我们在插件中已经配置过,酷Q收到消息后,cqhhtp插件调用其sdk获取消息,然后像本机的8080/msg/receive接口上报酷Q收到的消息,这种方式实现了跨语言使用酷Q的方式。

package com.sun.kq.controller;

import com.sun.kq.model.PrivateMsg;
import com.sun.kq.model.ReplyMsg;
import com.sun.kq.model.Result;
import com.sun.kq.service.MsgService;
import com.sun.kq.service.PrivateMsgService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/msg")
public class MsgController {

    @Autowired
    MsgService msgService;


    /**
     * 消息上报接口
     *
     * @param request
     */
    @RequestMapping("/receive")
    public ReplyMsg receive(HttpServletRequest request) {
        return msgService.receive(request);
    }

}

MsgService.java

package com.sun.kq.service;

import com.sun.kq.model.PrivateMsg;
import com.sun.kq.model.ReplyMsg;
import com.sun.kq.model.Result;

import javax.servlet.http.HttpServletRequest;

public interface MsgService {


    /**
     * 接收消息(私聊、群、讨论组、其他)并响应
     *
     * @param request
     * @return
     */
    ReplyMsg receive(HttpServletRequest request);
}

他的实现类MsgServiceImpl.java(主要看recevie方法)

package com.sun.kq.service.impl;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.kq.entity.Keyword;
import com.sun.kq.entity.Rubbish;
import com.sun.kq.model.PrivateMsg;
import com.sun.kq.model.ReceiveMsg;
import com.sun.kq.model.ReplyMsg;
import com.sun.kq.model.Result;
import com.sun.kq.service.*;
import com.sun.kq.util.KeywordsUtil;
import com.sun.kq.util.MsgUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.stream.Collectors;

import static com.sun.kq.constant.URLConst.*;

@Service
public class MsgServiceImpl implements MsgService {

    @Autowired
    RestTemplate restTemplate;

    @Autowired
    RubbishService rubbishService;

    @Autowired
    KeywordService keywordService;

    @Autowired
    PrivateMsgService privateMsgService;

    @Autowired
    GroupMsgService groupMsgService;


    @Override
    public ReplyMsg receive(HttpServletRequest request) {
        String msg = null;
        try {
            msg = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            System.out.println(msg);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (msg == null) {
            return null;
        }

        //解析收到的消息
        ReceiveMsg receiveMsg = null;
        try {
            receiveMsg = new ObjectMapper().readValue(msg, ReceiveMsg.class);
        } catch (IOException e) {
            e.printStackTrace();
        }

        return handleReceiveMsg(receiveMsg);


    }


    /**
     * 由收到的讨论组消息的内容转发给特定的方法(暂时用不到,先留着)
     *
     * @param receiveMsg
     */
    private ReplyMsg handleDiscussMsg(ReceiveMsg receiveMsg) {
        return null;
    }


    /**
     * 由收到的消息类型转发给特定的方法
     *
     * @param receiveMsg
     */
    private ReplyMsg handleReceiveMsg(ReceiveMsg receiveMsg) {
        String post_type = receiveMsg.getPost_type();
        switch (post_type) {

            case "message":
                //收到消息
                String message_type = receiveMsg.getMessage_type();
                switch (message_type) {
                    case "private":
                        //私聊消息
                        return privateMsgService.handlePrivateMsg(receiveMsg);
                    case "group":
                        //群消息
                        return groupMsgService.handleGroupMsg(receiveMsg);
                    case "discuss":
                        return handleDiscussMsg(receiveMsg);
                }
                break;

            case "notice":
                //群、讨论组变动等通知类事件
                break;

            case "request":
                //加好友请求、加群请求/邀请
                break;
            case "meta_event":
                //生命周期元事件
                //可以初始化缓存
                //keywordService.getAllDataIntoCache();
                break;
        }

        return null;
    }

}

上方代码中,直接用一行代码获取请求中的数据,接着用jackson中ObjectMapper类来解析生成ReceiveMsg对象,然后转交给handleReceiveMsg方法,handleReceiveMsg方法依据消息类型(私聊、群聊等)转交给不同的方法进行处理。


privateMsgService.handlePrivateMsg方法的内容见下一节教你写个QQ机器人(3)