[java手把手教程][第二季]java后端博客系统文章系统——No11

By - Last updated: 星期六, 八月 5, 2017
  • IDE为 idea2017.1.5
  • JDK环境为 1.8
  • gradle构建,版本:2.14.1
  • Mysql版本为 5.5.27
  • Tomcat版本为 7.0.52
  • 流程图绘制(xmind)
  • 建模分析软件 PowerDesigner16.5
  • 数据库工具MySQLWorkBench,版本:6.3.7build
  • 本期目标

    完成微信公众号相关接入

    资源引入

    既然我们要开发微信相关的功能,那么我们需要微信相关的资源。首先是打开微信官方的开发者文档。接着我们应该构建微信相关的代码了。?

    事实上并不是这样,我们在开源中国的java项目中可以找到一些跟微信相关的工具,本文中我采用了fastweixin 来快速进行开发。

    compile 'com.github.sd4324530:fastweixin:1.3.15'

    参照fastweixin说明进行开发

    实现微信互访的Controller

    为什么说要实现这个?

    • 配置微信相关设置
    • 根据生成的设置和微信服务器互联
    • 跟微信服务器交互,绑定微信账号
    • 获取和微信交互数据的令牌

    所以,我们有一大堆事情要做,但是此时此刻我们采用的fastweixin已经做好一大步,我们按照他的说明编写微信Controller。

    @RestController
    @RequestMapping("/weixin")
    public class WeixinController extends WeixinControllerSupport {
        private static final Logger log = LoggerFactory.getLogger(WeixinController.class);
        private static final String TOKEN = "weixin";   //默认Token为weixin
    
        @Autowired
        private WeichatServiceImpl weichatService;
        @Autowired
        private PostService postService;
    
        @Override
        public void bindServer(HttpServletRequest request, HttpServletResponse response) {
            String signature = request.getParameter("signature");
            String timestamp = request.getParameter("timestamp");
            String nonce = request.getParameter("nonce");
            LogPrintUtil.getInstance(WeixinController.class).logOutLittle("bindWeiXin:\fsignature = "
                    + signature + "\ntimestamp"
                    + timestamp + "\nnonce" + nonce);
            super.bindServer(request, response);
        }
    
        //设置TOKEN,用于绑定微信服务器
        @Override
        protected String getToken() {
            return weichatService.getWeiConfig().getToken();
        }
    
        //使用安全模式时设置:APPID
        //不再强制重写,有加密需要时自行重写该方法
        @Override
        protected String getAppId() {
            return weichatService.getWeiConfig().getAppid();
        }
    
        //使用安全模式时设置:密钥
        //不再强制重写,有加密需要时自行重写该方法
        @Override
        protected String getAESKey() {
            return null;
        }
    
        //重写父类方法,处理对应的微信消息
        @Override
        protected BaseMsg handleTextMsg(TextReqMsg msg) {
            String content = msg.getContent();
            LogPrintUtil.getInstance(WeixinController.class).logOutLittle(String.format("用户发送到服务器的内容:{%s}", content));
    
            List<Article> articles = new ArrayList<>();
            List<PostCustom> byKeyword = null;
            try {
                byKeyword = postService.findByKeyword(content, null, null);
                if (null != byKeyword && byKeyword.size() > 0) {
                    int count = 0;
                    for (PostCustom postCustom : byKeyword) {
                        if (count >= 5) break;
                        Article article = new Article();
                        article.setTitle(postCustom.getPostTitle());
                        article.setDescription(HtmlUtil.getTextFromHtml(postCustom.getPostContent()));
                        article.setUrl("http://acheng1314.cn/front/post/" + postCustom.getId());
                        articles.add(article);
                        count++;
                    }
                    return new NewsMsg(articles);
                }
            } catch (NotFoundException e) {
                e.printStackTrace();
            }
            return new TextMsg("暂未找到该信息!");
        }
    
        /*1.1版本新增,重写父类方法,加入自定义微信消息处理器
         *不是必须的,上面的方法是统一处理所有的文本消息,如果业务觉复杂,上面的会显得比较乱
         *这个机制就是为了应对这种情况,每个MessageHandle就是一个业务,只处理指定的那部分消息
         */
        @Override
        protected List<MessageHandle> initMessageHandles() {
            List<MessageHandle> handles = new ArrayList<MessageHandle>();
    //                handles.add(new MyMessageHandle());
            return handles;
        }
    
        //1.1版本新增,重写父类方法,加入自定义微信事件处理器,同上
        @Override
        protected List<EventHandle> initEventHandles() {
            List<EventHandle> handles = new ArrayList<EventHandle>();
    //                handles.add(new MyEventHandle());
            return handles;
        }
    
        /**
         * 处理图片消息,有需要时子类重写
         *
         * @param msg 请求消息对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleImageMsg(ImageReqMsg msg) {
            return super.handleImageMsg(msg);
        }
    
        /**
         * 处理语音消息,有需要时子类重写
         *
         * @param msg 请求消息对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleVoiceMsg(VoiceReqMsg msg) {
            return super.handleVoiceMsg(msg);
        }
    
        /**
         * 处理视频消息,有需要时子类重写
         *
         * @param msg 请求消息对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleVideoMsg(VideoReqMsg msg) {
            return super.handleVideoMsg(msg);
        }
    
        /**
         * 处理小视频消息,有需要时子类重写
         *
         * @param msg 请求消息对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg hadnleShortVideoMsg(VideoReqMsg msg) {
            return super.hadnleShortVideoMsg(msg);
        }
    
        /**
         * 处理地理位置消息,有需要时子类重写
         *
         * @param msg 请求消息对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleLocationMsg(LocationReqMsg msg) {
            return super.handleLocationMsg(msg);
        }
    
        /**
         * 处理链接消息,有需要时子类重写
         *
         * @param msg 请求消息对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleLinkMsg(LinkReqMsg msg) {
            return super.handleLinkMsg(msg);
        }
    
        /**
         * 处理扫描二维码事件,有需要时子类重写
         *
         * @param event 扫描二维码事件对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleQrCodeEvent(QrCodeEvent event) {
            return super.handleQrCodeEvent(event);
        }
    
        /**
         * 处理地理位置事件,有需要时子类重写
         *
         * @param event 地理位置事件对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleLocationEvent(LocationEvent event) {
            return super.handleLocationEvent(event);
        }
    
        /**
         * 处理菜单点击事件,有需要时子类重写
         *
         * @param event 菜单点击事件对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleMenuClickEvent(MenuEvent event) {
            LogPrintUtil.getInstance(this.getClass()).logOutLittle("点击" + event.toString());
            MyWeChatMenu myWeChatMenu = weichatService.findOneById(StringUtils.toInt(event.getEventKey()));
            try {
                List<Article> articles = new ArrayList<>();
                List<PostCustom> keyword = postService.findByKeyword(myWeChatMenu.getKeyword(), null, null);
                if (null != keyword && keyword.size() > 0) {
                    int i = 0;
                    for (PostCustom postCustom : keyword) {
                        if (i >= 5) break;
                        Article article = new Article();
                        article.setTitle(postCustom.getPostTitle());
                        article.setDescription(HtmlUtil.getTextFromHtml(postCustom.getPostContent()));
                        article.setUrl("http://acheng1314.cn/front/post/" + postCustom.getId());
                        articles.add(article);
                        i++;
                    }
                    return new NewsMsg(articles);
                }
            } catch (NotFoundException e) {
                e.printStackTrace();
            }
            return new TextMsg("暂未找到该信息!");
        }
    
        /**
         * 处理菜单跳转事件,有需要时子类重写
         *
         * @param event 菜单跳转事件对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleMenuViewEvent(MenuEvent event) {
            LogPrintUtil.getInstance(this.getClass()).logOutLittle("点击跳转" + event.toString());
            return super.handleMenuViewEvent(event);
        }
    
        /**
         * 处理菜单扫描推事件,有需要时子类重写
         *
         * @param event 菜单扫描推事件对象
         * @return 响应的消息对象
         */
        @Override
        protected BaseMsg handleScanCodeEvent(ScanCodeEvent event) {
            return super.handleScanCodeEvent(event);
        }
    
        /**
         * 处理菜单弹出相册事件,有需要时子类重写
         *
         * @param event 菜单弹出相册事件
         * @return 响应的消息对象
         */
        @Override
        protected BaseMsg handlePSendPicsInfoEvent(SendPicsInfoEvent event) {
            return super.handlePSendPicsInfoEvent(event);
        }
    
        /**
         * 处理模版消息发送事件,有需要时子类重写
         *
         * @param event 菜单弹出相册事件
         * @return 响应的消息对象
         */
        @Override
        protected BaseMsg handleTemplateMsgEvent(TemplateMsgEvent event) {
            return super.handleTemplateMsgEvent(event);
        }
    
        /**
         * 处理添加关注事件,有需要时子类重写
         *
         * @param event 添加关注事件对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleSubscribe(BaseEvent event) {
            return super.handleSubscribe(event);
        }
    
        /**
         * 接收群发消息的回调方法
         *
         * @param event 群发回调方法
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg callBackAllMessage(SendMessageEvent event) {
            return super.callBackAllMessage(event);
        }
    
        /**
         * 处理取消关注事件,有需要时子类重写
         *
         * @param event 取消关注事件对象
         * @return 响应消息对象
         */
        @Override
        protected BaseMsg handleUnsubscribe(BaseEvent event) {
            return super.handleUnsubscribe(event);
        }
    
    }

    我们看上面的众多方法都已经打上了javadoc,现在我们需要关注的主要是下面的这三个方法:

    //设置TOKEN,用于绑定微信服务器
        @Override
        protected String getToken() {
            return weichatService.getWeiConfig().getToken();
        }
    
        //使用安全模式时设置:APPID
        //不再强制重写,有加密需要时自行重写该方法
        @Override
        protected String getAppId() {
            return weichatService.getWeiConfig().getAppid();
        }
    
        //使用安全模式时设置:密钥
        //不再强制重写,有加密需要时自行重写该方法
        @Override
        protected String getAESKey() {
            return null;
        }

    同时在微信的开发者设置页面也有对应的设置来控制,测试账号如下:

    微信测试号设置页面

    按照上面的Controller来讲,URL已经可以设置了,就是我们服务器域名+/weixin。

    当然,这不是重点!但是按照前面我们的开发习惯来讲,微信相关的一些设置能够持久化到服务器那就是最好的了。所以我们还是写到数据库中。(刚开始其实我是写到properties中,但是由于properties的特性,所以数据不刷新。干脆我也就存储到数据库中。)

    /*创建数据库表cc_site_option,用来存储站点基础信息*/
    SET NAMES utf8;
    -- ----------------------------
    --  Table structure for `cc_site_option`
    -- ----------------------------
    DROP TABLE IF EXISTS `cc_site_option`;
    CREATE TABLE `cc_site_option` (
      `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
      `option_key` varchar(128) DEFAULT NULL COMMENT '配置KEY',
      `option_value` text COMMENT '配置内容',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8 COMMENT='配置信息表,用来保存网站的所有配置信息。';

    其实在上面的表中大家细心点可以看到我是采用了类似Map的存储结构,也就是说我们的数据通俗来讲也就是键值对的形式,所以读取数据的时候存储用的List >。简要的Dao如下:

    @Repository("siteConfigDao")
    public interface SiteConfigDao extends Dao {
    
        @Deprecated
        @Override
        public int add(Serializable serializable);
    
        @Deprecated
        @Override
        public int del(Serializable serializable);
    
        @Deprecated
        @Override
        public int update(Serializable serializable);
    
        @Deprecated
        @Override
        public Serializable findOneById(Serializable Id);
    
        @Override
        List<HashMap<String, String>> findAll();
    
        Serializable findOneByKey(@Param("mKey") Serializable key);
    
        void updateOneByKey(@Param("mKey") Serializable key, @Param("mValue") Serializable value);
    
        //    @Insert("INSERT INTO `cc_site_option` (`option_key`,`option_value`) VALUES (#{mKey},#{mValue});")
        void insertOne(@Param("mKey") Serializable key, @Param("mValue") Serializable value);
    }

    唯一细节一点的就是对应的Service中获取想要的某一些数据。同时,我们的微信菜单也是需要存储的,如下:

    CREATE TABLE `cc_wechat_menu` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` text NOT NULL COMMENT '微信菜单的名字',
      `parent_id` int(11) DEFAULT '0' COMMENT '父级菜单的id,最外层菜单的parent_id为0',
      `type` varchar(255) DEFAULT NULL COMMENT '微信菜单类型,deleted表示删除,其他的都是微信上面的相同类型,click=点击推事件,view=跳转URL,scancode_push=扫码推事件,scancode_waitmsg=扫码推事件且弹出“消息接收中”提示框,pic_sysphoto=弹出系统拍照发图,pic_photo_or_album=弹出拍照或者相册发图,pic_weixin=弹出微信相册发图器,location_select=弹出地理位置选择器,',
      `keyword` text COMMENT '填写的关键字将会触发“自动回复”匹配的内容,访问网页请填写URL地址。',
      `position` int(11) DEFAULT '0' COMMENT '排序的数字决定了菜单在什么位置。',
      PRIMARY KEY (`id`),
      UNIQUE KEY `cc_wechat_menu_id_uindex` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='微信菜单表';

    当然到这里后,我们需要的是微信的Dao(这次在Dao中采用了注解插入sql的方式,这种方式可以懒得创建mapper文件。)。

    @Repository("weChatDao")
    public interface WeChatDao extends Dao<MyWeChatMenu> {
    
        @Override
        int add(MyWeChatMenu weChatMenu);
    
        @Update("UPDATE `cc_wechat_menu` SET type='deleted' WHERE id=#{id}")
        @Override
        int del(MyWeChatMenu weChatMenu);
    
        @Update("UPDATE `cc_wechat_menu` SET name=#{name},parent_id=#{parentId},type=#{type},keyword=#{keyword},position=#{position} WHERE id=#{id}")
        @Override
        int update(MyWeChatMenu weChatMenu);
    
        @Select("SELECT * FROM `cc_wechat_menu` WHERE id=#{id}")
        @Override
        MyWeChatMenu findOneById(Serializable Id);
    
        @Select("SELECT * FROM `cc_wechat_menu` WHERE type!='deleted'")
        @Override
        List<MyWeChatMenu> findAll();
    
        @Select("SELECT * FROM `cc_wechat_menu` WHERE type!='deleted' AND parent_id=0")
        List<MyWeChatMenu> getParentWeiMenu();
    }

    简单来说上面的注解插入sql语句这样执行,注意一点就是这几个sql的使用。剩下的就是微信的Service,如下:

    发表在 其他 • Tags: , , , ↑Top 文章来源