• 基于SmartQQ协议的QQ聊天机器人-5


    本节主题是项目回顾,从总体上分析QQ机器人的数据流

    1. 项目的生命周期:

    /**
     * 下面是我的理解和注释:
     * 本模块功能:提供各种qq服务的基础函数库 
     * 项目的运行流程是:
     * 比如,我现在1.0版最主要的一个功能就是:接受群消息,并自动回复
     * 它的流程是:
     * 0. 系统在哪里加载了XiaoVGetUpServlet呢???{因为整个项目只有XiaoVGetUpServlet调用了QQService.java},而QQService必须启动
     * 答案见:XiaoVGetUpServlet.java中我的注释 {其实是一个非常简单的机制,在下面}
     * 
     * 1. 本项目的web.xml中定义了XiaoVGetUpServlet.java的map,系统在latke.properties中定义了项目启动的第一个request,然后导向到DispatcherServlet,依次导向到QRCodeShowServlet,然后是XiaoVGetUpServlet
     * 2. XiaoVGetUpServlet中使用了QQService的initQQClient,initQQClient中定义了用于回复消息的callback{即onGroupMessage};initQQClient又使用了SmartQQClient
     * 3. SmartQQClient会做三件事:
    		 * 每次new一个SmartQQClient,就回去执行同名的构造方法SmartQQClient
    		 * 1. 登陆
    		 * 2. 开启一个守护线程,循环地跑:做一件事:接收消息pollMessage
    		 * 3. 本执行callback {即onGroupMessage}
     * 4. onGroupMessage又会去调用onQQGroupMessage,{这两个名字很像,不要看错了,我一开始就看晕了。。。}
     * 5. onQQGroupMessage依次做了三件事:1. 判断提问是否“感兴趣”;2.如果感兴趣就调用answer获得提问的答案;3.并把答案给sendMessageToGroup
     * 6. sendMessageToGroup会调用SmartQQClient类的sendMessageToGroup{好坑,同名的。。。},把答案以response的形式发送到对应群
     * 
     */
    

    2. 对应的核心代码分析:

    2.1 QQService.java

    ... //省略一部分非核心代码,见源码
    @Service
    public class QQService {
        
        ...// 省略
    
        /**
         * Initializes QQ client.
         */
        public void initQQClient() {
            LOGGER.info("开始初始化小薇");
    
            xiaoV = new SmartQQClient(new MessageCallback() {
            	// 有个疑问:这里new了一个SmartQQClient,而SmartQQClient的构造方法里面是有login的,但是事实是我只登陆了一次
                @Override
                public void onMessage(final Message message) {
                    new Thread(() -> {
                        try {
                            Thread.sleep(500 + RandomUtils.nextInt(1000));
    
                            final String content = message.getContent();
                            final String key = XiaoVs.getString("qq.bot.key");
                            if (!StringUtils.startsWith(content, key)) { // 不是管理命令,只是普通的私聊
                                // 让小薇进行自我介绍
                                xiaoV.sendMessageToFriend(message.getUserId(), XIAO_V_INTRO);
    
                                return;
                            }
    
                            final String msg = StringUtils.substringAfter(content, key);
                            LOGGER.info("Received admin message: " + msg);
                            sendToPushQQGroups(msg);
                        } catch (final Exception e) {
                            LOGGER.log(Level.ERROR, "XiaoV on group message error", e);
                        }
                    }).start();
                }
    
                @Override
                public void onGroupMessage(final GroupMessage message) {
                    new Thread(() -> {
                        try {
                            Thread.sleep(500 + RandomUtils.nextInt(1000));
    
                            onQQGroupMessage(message);
                        } catch (final Exception e) {
                            LOGGER.log(Level.ERROR, "XiaoV on group message error", e);
                        }
                    }).start();
                }
    
                @Override
                public void onDiscussMessage(final DiscussMessage message) {
                    new Thread(() -> {
                        try {
                            Thread.sleep(500 + RandomUtils.nextInt(1000));
    
                            onQQDiscussMessage(message);
                        } catch (final Exception e) {
                            LOGGER.log(Level.ERROR, "XiaoV on group message error", e);
                        }
                    }).start();
                }
            });
    
            reloadGroups();
            reloadDiscusses();
    
            LOGGER.info("小薇初始化完毕");
        }
    
        ...//省略
    
        private void sendMessageToGroup(final Long groupId, final String msg) {
            Group group = QQ_GROUPS.get(groupId);
            if (null == group) {
                reloadGroups();
    
                group = QQ_GROUPS.get(groupId);
            }
    
            if (null == group) {
                LOGGER.log(Level.ERROR, "Group list error [groupId=" + groupId + "], 请先参考项目主页 FAQ 解决"
                        + "(https://github.com/b3log/xiaov#报错-group-list-error-groupidxxxx-please-report-this-bug-to-developer-怎么破),"
                        + "如果还有问题,请到论坛讨论帖中进行反馈(https://hacpai.com/article/1467011936362)");
    
                return;
            }
    
            LOGGER.info("Pushing [msg=" + msg + "] to QQ qun [" + group.getName() + "]");
            xiaoV.sendMessageToGroup(groupId, msg);
        }
    
        ..//省略
        
        /*
         * 这是我的注释1.0
         * 这个函数非常重要,我会抽时间把这个函数讲清楚 TODO
         * */
    
        private void onQQGroupMessage(final GroupMessage message) throws SQLException {
            final long groupId = message.getGroupId();
            final long userId = message.getUserId();// 获取消息的sender的QQ号,与机器人的QQ做比较
            //LOGGER.debug(Long.toString(userId));
            //final long botId = XiaoVs.getInt("qq.bot.id");//从配置文件中读当前机器人的QQ号 {还有点bug,后面再修}
            
            // 为了解决2872995315溢出的问题,只能把userId和机器人ID比较由 Long比较   转化成  字符串比较
            String s_userId = Long.toString(userId);
            //final String s_botId = "2872995315";//暂时写死
            final String s_botId = XiaoVs.getString("qq.bot.id"); //从xiaov.properties配置文件中读
    
            final String content = message.getContent();
            final String userName = Long.toHexString(message.getUserId());
            // Push to third system
            String qqMsg = content.replaceAll("\["face",[0-9]+\]", "");
            if (StringUtils.isNotBlank(qqMsg)) {
                qqMsg = "<p>" + qqMsg + "</p>";
                sendToThird(qqMsg, userName);
            }
    
            String msg = "";
            // 下面是对于QQ用户提问的语句进行合法性分析,如果符合规则,那就收集答案,并发送到QQ群 {要避免机器人自问自答的情况发生}
            /*
            if (StringUtils.contains(content, XiaoVs.QQ_BOT_NAME)
                    || (StringUtils.length(content) > 6
                    && (StringUtils.contains(content, "?") || StringUtils.contains(content, "?") || StringUtils.contains(content, "问")))) {
                msg = answer(content, userName);
            }*/
            if ( StringUtils.contains(content, XiaoVs.QQ_BOT_NAME) // TODO:这里是对提问的基本要求{过滤不合法的提问}
                 || (StringUtils.length(content) > 0) && !(s_userId.equals(s_botId))  ) { //彻底解决了机器人自问自答的bug
                msg = answer(content, userName);
            }
            
            if (StringUtils.isBlank(msg)) {
                return;
            }
    
            if (RandomUtils.nextFloat() >= 0.9) {
                Long latestAdTime = GROUP_AD_TIME.get(groupId);
                if (null == latestAdTime) {
                    latestAdTime = 0L;
                }
    
                final long now = System.currentTimeMillis();
    
                if (now - latestAdTime > 1000 * 60 * 30) {
                    msg = msg + "。
    " + ADS.get(RandomUtils.nextInt(ADS.size()));
    
                    GROUP_AD_TIME.put(groupId, now);
                }
            }
    
            sendMessageToGroup(groupId, msg);
        }
    
        ...//省略
        
        /*
         * 这是我对xiaov-1.0的注释1.0
         * 这个函数非常重要,定义了提问和回答这两个功能的数据结构及数据来源
         * 我会抽时间把这个函数讲清楚 TODO
         * */
    
        private String answer(final String content, final String userName) throws SQLException {
            if (keywords.size() == 0) // 加载一次即可
            {
                // 第一次使用,注册下jdbc驱动,通过new一个AnswersFromSQLite对象来激发static代码块
                // AnswersFromSQLite namexxx = new AnswersFromSQLite();
                // 获取keys,只调用一次
                keywords = AnswersFromSQLite.getAllKeys();
            }
            // LOGGER.debug(keywords.get(0));// 测试下content
            
            String keyword = "";
            for (final String kw : keywords) {
                if (StringUtils.containsIgnoreCase(content, kw)) {
                    keyword = kw;
                    break;
                }
            }
            
           // LOGGER.debug(content);// 测试下content
           // LOGGER.debug(keyword);// 测试下keyword有没有捕捉到
    
            String ret = "";
            String msg = replaceBotName(content);
            if (StringUtils.isNotBlank(keyword)) {
                try {
                    ret = AnswersFromSQLite.getValue(keyword);//我自定义的回复函数
                	ret= URLEncoder.encode(ret, "UTF-8");
                } catch (final UnsupportedEncodingException e) {
                    LOGGER.log(Level.ERROR, "Search key encoding failed", e);
                }
            } else if (StringUtils.contains(content, XiaoVs.QQ_BOT_NAME) && StringUtils.isNotBlank(msg)) {
                if (1 == QQ_BOT_TYPE && StringUtils.isNotBlank(userName)) {
                  ... //省略
            }
            
            try {
            	ret= URLDecoder.decode(ret, "UTF-8");
            } catch (final UnsupportedEncodingException e) {
                LOGGER.log(Level.ERROR, "ret decoding failed", e);
            }
            return ret;
        }
        
    ... //省略
       
    }
    

    2.2 XiaoVGetUpServlet.java

    /**
     * 下面是我的注释:
     * 在web.xml定义了一个Servlet配置项,就是把一个url路由和这个XiaoVGetUpServlet类绑定了,后来只要访问那个url,就跳转到这个class来处理
     * 问题来了,访问那个url被谁读了呢?我需要进一步看源码找答案!!!
     * 上述的疑问其实是对jvm加载web.xml的机制不熟悉导致的:
     * 但是:
     * 不用管,因为本项目的web.xml配置了XiaoVGetUpServlet为<load-on-startup>3</load-on-startup>,就是说优秀级为第3自动加载
     * 一旦项目启动,读取web.xml,然后等待优先级到了3,就自动调用XiaoVGetUpServlet的init(),接下来的流程见QQService.java中我的描述
     */
    public class XiaoVGetUpServlet extends HttpServlet {
    
        /**
         * Serial version UID.
         */
        private static final long serialVersionUID = 1L;
    
        /**
         * Logger.
         */
        private static final Logger LOGGER = Logger.getLogger(XiaoVGetUpServlet.class);
    
        /**
         * Bean manager.
         */
        private LatkeBeanManager beanManager;
    
        @Override
        public void init() throws ServletException {
            new Thread(() -> {
                try {
                    Thread.sleep(3000);
                } catch (final Exception e) {
                    LOGGER.log(Level.ERROR, e.getMessage());
                }
    
                beanManager = Lifecycle.getBeanManager();
    
                final QQService qqService = beanManager.getReference(QQService.class);
                qqService.initQQClient();
            }).start();
        }
    }
    

    2.3 SmartQQClient.java

    /**
     * 下面是我的理解和注释:
     * Api客户端.
     * 每次new一个SmartQQClient,就回去执行同名的构造方法SmartQQClient
     * 1. 登陆
     * 2. 开启一个守护线程,循环地跑:做一件事:接收消息pollMessage
     * 3. 本执行callback
     */
    public class SmartQQClient implements Closeable {
    
        ...//省略
        //线程开关
        private volatile boolean pollStarted;
    
        /*
         * 每次new 一个SmartQQClient就自动执行构造函数
         * */
        public SmartQQClient(final MessageCallback callback) {
            this.client = Client.pooled().maxPerRoute(5).maxTotal(10).build();
            this.session = client.session();
            login();
            if (callback != null) {
                this.pollStarted = true;
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (true) {        	//这就是循环接受群消息的核心所在;这个线程永远在循环地 接收群消息{即循环地监听}
                            if (!pollStarted) {
                                return;
                            }
                            try {
                                pollMessage(callback); //仅仅是接受消息,callback中会定义如何解析消息和回复消息
                            } catch (RequestException e) {
                                //忽略SocketTimeoutException
                                if (!(e.getCause() instanceof SocketTimeoutException)) {
                                    LOGGER.error(e.getMessage());
                                }
                            } catch (Exception e) {
                                LOGGER.error(e.getMessage());
                            }
                        }
                    }
                }).start();
            }
        }
    
        /**
         * 登录
         */
        private void login() {
            getQRCode();
            String url = verifyQRCode();
            getPtwebqq(url);
            getVfwebqq();
            getUinAndPsessionid();
            getFriendStatus(); //修复Api返回码[103]的问题
            //登录成功欢迎语
            UserInfo userInfo = getAccountInfo();
            LOGGER.info(userInfo.getNick() + ",欢迎!");
        }
    
        //登录流程1:获取二维码
        private void getQRCode() {
            LOGGER.debug("开始获取二维码");
    
            //本地存储二维码图片
            String filePath;
            try {
                filePath = new File("qrcode.png").getCanonicalPath();
            } catch (IOException e) {
                throw new IllegalStateException("二维码保存失败");
            }
            Response response = session.get(ApiURL.GET_QR_CODE.getUrl())
                    .addHeader("User-Agent", ApiURL.USER_AGENT)
                    .file(filePath);
            for (Cookie cookie : response.getCookies()) {
                if (Objects.equals(cookie.getName(), "qrsig")) {
                    qrsig = cookie.getValue();
                    break;
                }
            }
            LOGGER.info("二维码已保存在 " + filePath + " 文件中,请打开手机QQ并扫描二维码");
        }
    
        //用于生成ptqrtoken的哈希函数
        private static int hash33(String s) {
            int e = 0, n = s.length();
            for (int i = 0; n > i; ++i)
                e += (e << 5) + s.charAt(i);
            return 2147483647 & e;
        }
    
        //登录流程2:校验二维码
        private String verifyQRCode() {
            LOGGER.debug("等待扫描二维码");
    
            //阻塞直到确认二维码认证成功
            while (true) {
                sleep(1);
                Response<String> response = get(ApiURL.VERIFY_QR_CODE, hash33(qrsig));
                String result = response.getBody();
                if (result.contains("成功")) {
                    for (String content : result.split("','")) {
                        if (content.startsWith("http")) {
                            LOGGER.info("正在登录,请稍后");
    
                            return content;
                        }
                    }
                } else if (result.contains("已失效")) {
                    LOGGER.info("二维码已失效,尝试重新获取二维码");
                    getQRCode();
                }
            }
    
        }
    
        //登录流程3:获取ptwebqq
        private void getPtwebqq(String url) {
            LOGGER.debug("开始获取ptwebqq");
    
            Response<String> response = get(ApiURL.GET_PTWEBQQ, url);
            this.ptwebqq = response.getCookies().get("ptwebqq").iterator().next().getValue();
        }
    
        //登录流程4:获取vfwebqq
        private void getVfwebqq() {
            LOGGER.debug("开始获取vfwebqq");
    
            Response<String> response = get(ApiURL.GET_VFWEBQQ, ptwebqq);
            int retryTimes4Vfwebqq = retryTimesOnFailed;
            while (response.getStatusCode() == 404 && retryTimes4Vfwebqq > 0) {
                response = get(ApiURL.GET_VFWEBQQ, ptwebqq);
                retryTimes4Vfwebqq--; 
            }
            this.vfwebqq = getJsonObjectResult(response).getString("vfwebqq");
        }
    
        //登录流程5:获取uin和psessionid
        private void getUinAndPsessionid() {
            LOGGER.debug("开始获取uin和psessionid");
    
            JSONObject r = new JSONObject();
            r.put("ptwebqq", ptwebqq);
            r.put("clientid", Client_ID);
            r.put("psessionid", "");
            r.put("status", "online");
    
            Response<String> response = post(ApiURL.GET_UIN_AND_PSESSIONID, r);
            JSONObject result = getJsonObjectResult(response);
            this.psessionid = result.getString("psessionid");
            this.uin = result.getLongValue("uin");
        }
    
        /**
         * 获取群列表
         *
         * @return
         */
        public List<Group> getGroupList() {
            LOGGER.debug("开始获取群列表");
    
            JSONObject r = new JSONObject();
            r.put("vfwebqq", vfwebqq);
            r.put("hash", hash());
    
            Response<String> response = post(ApiURL.GET_GROUP_LIST, r);
            int retryTimes4getGroupList = retryTimesOnFailed;
            while (response.getStatusCode() == 404 && retryTimes4getGroupList > 0) {
                response = post(ApiURL.GET_GROUP_LIST, r);
                retryTimes4getGroupList--;
            }
            JSONObject result = getJsonObjectResult(response);
            return JSON.parseArray(result.getJSONArray("gnamelist").toJSONString(), Group.class);
        }
    
        /**
         * 拉取消息
         *
         * @param callback 获取消息后的回调
         * 
         * 下面是我的注释:
         * 这个函数非常重要,有时间需要把它讲清楚 TODO
         */
        private void pollMessage(MessageCallback callback) {
            LOGGER.debug("开始接收消息");
    
            JSONObject r = new JSONObject();
            r.put("ptwebqq", ptwebqq);
            r.put("clientid", Client_ID);
            r.put("psessionid", psessionid);
            r.put("key", "");
            
         // 先用post(){本质是post方式}把r发给ApiURL.POLL_MESSAGE,得到response
            Response<String> response = post(ApiURL.POLL_MESSAGE, r); 
            JSONArray array = getJsonArrayResult(response);// 改造成JsonArray格式
            for (int i = 0; array != null && i < array.size(); i++) {
                JSONObject message = array.getJSONObject(i);
                String type = message.getString("poll_type");
                if ("message".equals(type)) { // 确认是message是qq私聊的消息类型
                    callback.onMessage(new Message(message.getJSONObject("value")));
                } else if ("group_message".equals(type)) { //qq群消息 {这也是目前我使用和研究的模块:QQ群}
                    callback.onGroupMessage(new GroupMessage(message.getJSONObject("value")));
                } else if ("discu_message".equals(type)) { //qq讨论组消息
                    callback.onDiscussMessage(new DiscussMessage(message.getJSONObject("value")));
                }
            }
        }
    
        /**
         * 发送群消息
         *
         * @param groupId 群id
         * @param msg     消息内容
         * 
         * 
         * 下面是我的注释:
         * 这个函数也很重要,抽时间讲清楚 TODO
         */
        public void sendMessageToGroup(long groupId, String msg) {
            LOGGER.debug("开始发送群消息");
            JSONObject r = new JSONObject();
            r.put("group_uin", groupId);
            r.put("content", JSON.toJSONString(Arrays.asList(msg, Arrays.asList("font", Font.DEFAULT_FONT))));  //注意这里虽然格式是Json,但是实际是String
            LOGGER.debug(r.get("content"));
            r.put("face", 573);
            r.put("clientid", Client_ID);
            r.put("msg_id", MESSAGE_ID++);
            r.put("psessionid", psessionid);
    
            Response<String> response = postWithRetry(ApiURL.SEND_MESSAGE_TO_GROUP, r);
            checkSendMsgResult(response);
        }
    }
    

    2.4 web.xml

        <listener>
            <listener-class>org.b3log.xiaov.XiaoVServletListener</listener-class>
        </listener>
        
        <filter>
            <filter-name>EncodingFilter</filter-name>
            <filter-class>org.b3log.latke.servlet.filter.EncodingFilter</filter-class>
            <init-param>
                <param-name>requestEncoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
            <init-param>
                <param-name>responseEncoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>EncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
        
        <session-config>
            <session-timeout>
                60
            </session-timeout>
        </session-config>
        
        <servlet>
            <servlet-name>DispatcherServlet</servlet-name> <!-- 项目启动后第1个就加载这个调度器,执行它的init -->
            <servlet-class>org.b3log.latke.servlet.DispatcherServlet</servlet-class>
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>DispatcherServlet</servlet-name>
            <url-pattern>/*</url-pattern>
        </servlet-mapping>
        
        <servlet>
            <servlet-name>XiaoVGetUpServlet</servlet-name> <!-- 项目启动后第3个就加载这个小薇机器人的唤醒 -->
            <servlet-class>org.b3log.xiaov.processor.XiaoVGetUpServlet</servlet-class>
            <load-on-startup>3</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>XiaoVGetUpServlet</servlet-name>
            <url-pattern>/getup</url-pattern>
        </servlet-mapping>
        
        <servlet>
            <servlet-name>QRCodeShowServlet</servlet-name> <!-- 项目启动后第2个就加载这个二维码处理器 -->
            <servlet-class>org.b3log.xiaov.processor.QRCodeShowServlet</servlet-class>
            <load-on-startup>2</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>QRCodeShowServlet</servlet-name>
            <url-pattern>/login</url-pattern>
        </servlet-mapping>
    </web-app>
    

    2.5 我的回复函数AnswersFromSQLite.java

    ...//省略
    
    public class AnswersFromSQLite {
    
        public static String getValue(String key) throws SQLException {
            // 测试查询某条记录
            Dao<t_answers, Integer> dao = getDao();
            List<t_answers> ans = queryByOPtions(dao, key);
            // logger.info(ans.get(0).getValue());
            if (ans != null) {
                return ans.get(0).getValue(); // 仅返回第一条记录的value字段
            }
            return null;
        }
    
    
        public static List<String> getAllKeys() throws SQLException {
            
            Dao<t_answers, Integer> dao = getDao();
            List<t_answers> t_answers = queryByOPtions(dao);
            List<String> keys = new ArrayList<String>();
            for (t_answers t : t_answers) {
                keys.add(t.getKey());
                // logger.info(t.getKey());
            }
            return keys;
        }
    
        private static List<t_answers> queryByOPtions(Dao<t_answers, Integer> dao) throws SQLException {
            
            //按条件查询多条记录并分页并倒序 这里用到QueryBuilder
            QueryBuilder<t_answers, Integer> queryBuilder = dao.queryBuilder();
    
            //queryBuilder.where().eq("is_delete", 0).and().eq("status", 0);
            //queryBuilder.limit((long) 10);
            queryBuilder.where().eq("is_delete", 0); // 执行逻辑上的“有效”查询,以后删除也是“逻辑删除” {除非不得已,不要物理删除}
            queryBuilder.orderBy("id", false);
           
            List<t_answers> ts = dao.query(queryBuilder.prepare());
            return ts;
        }
        
        private static List<t_answers> queryByOPtions(Dao<t_answers, Integer> dao, String key) throws SQLException {
            //按条件查询多条记录并分页并倒序 这里用到QueryBuilder
            QueryBuilder<t_answers, Integer> queryBuilder = dao.queryBuilder();
    
            //queryBuilder.where().eq("is_delete", 0).and().eq("status", 0);
            //queryBuilder.limit((long) 10);
            queryBuilder.where().eq("is_delete", 0).and().eq("key", key);
            queryBuilder.orderBy("id", false);
           
            List<t_answers> t_answerss = dao.query(queryBuilder.prepare());
            return t_answerss;
        }
    
        private static Dao<t_answers, Integer> getDao() throws SQLException {
            //     //E:/Software_install/SQLite/Repo/d_xiaov.db
            String databaseUrl = "jdbc:sqlite:D:/Myeclipse15/Workspace/db/d_xiaov.db";//d_xiaov.db放在当前项目根目录下 //【只能写绝对路径,否则SQLite报错】
    
            //创建一个JDBC连接
            ConnectionSource connectionSource = new JdbcConnectionSource(databaseUrl);
            // logger.info(connectionSource.toString());
    
            //删除表同时忽略错误
            //TableUtils.dropTable(connectionSource, t_answers.class, true);
            //创建Table
            //TableUtils.createTable(connectionSource, t_answers.class);
    
            //实例化一个DAO,对表进行数据操作
            Dao<t_answers, Integer> dao = DaoManager.createDao(connectionSource, t_answers.class);
            return dao;
        }
    }
    

    2.6 Some Tricks:

    1.注意小坑:本项目中Application,java只是SmartQQ协议给的测试demo,与本项目无关,不要被这个名字和它的main方法迷惑(它其实和入口文件甚至是本项目没有任何关系,删掉都可以的)
    2.注意一个【大坑】:SQLite数据库在win10下不能使用相对路径创建DB_PATH,【必须写绝对路径】,否则会报错:“找不到表”(其实实际表和数据库及代码都很正常)
    3.更多细节:其他函数均属于非核心部分(对实现【群消息自动回复】这个需求而言),我做了部分注释,直接写在每个文件中,有兴趣可以翻看。

    3. web.xml的配置与java web 项目启动:

    1.java项目的启动入口
    2.severlet
    3.listenr
    4.filter

    参考:
    https://blog.csdn.net/fjtnylk/article/details/50717753
    https://blog.csdn.net/reggergdsg/article/details/52698022
    https://blog.csdn.net/guihaijinfen/article/details/8363839
    http://www.blogjava.net/xzclog/archive/2011/09/29/359789.html
    https://www.cnblogs.com/whgk/p/6399262.html
    https://blog.csdn.net/xuke6677/article/details/44752207
    https://www.cnblogs.com/ygj0930/p/6374384.html
    https://blog.csdn.net/reggergdsg/article/details/52891311
    https://blog.csdn.net/reggergdsg/article/details/52821502
    https://blog.csdn.net/reggergdsg/article/details/52962774
    https://blog.csdn.net/reggergdsg/article/details/53024827

    4. 总结:

    1. 目前已实现【关键字,提问和答案】从sqlite读入
    2. TODO: 如某同事0所言,理想的交互方式如下:用户提一个问题,机器人分析用户的提问中的关键字集合,然后返回几个完整的提问句子及其对应答案;返回用户若干个【提示性的提问句子集合】并且每个提问后面附带【答案的编号】,然后用户二次回复【答案编号】,机器人再次回复一个完整的答案句子。
    3. TODO:项目部署到阿里云【先用滴滴云练手】
    4. TODO:接入百度NLP,实现分词和智能API回复
  • 相关阅读:
    Jquery所有获取对象
    使用VS Code 调试Vue
    Http请求
    Xml,Json序列化
    SqlServer函数使用
    FastReport关闭打印提示框
    求面试经验
    pyspark基于python虚拟环境运行
    idea配置本地spark本地开发环境
    carbondata使用总结
  • 原文地址:https://www.cnblogs.com/LS1314/p/9040980.html
Copyright © 2020-2023  润新知