> 文档中心 > JAVA基于Swing和netty仿QQ界面聊天小项目

JAVA基于Swing和netty仿QQ界面聊天小项目

1.前言
2.功能实现
3.模块划分
4.使用到知识
5.部分代码实现
6.运行例图

1.前言

   先说一下这个小项目也算是我在大学做的第一个应该算的上是的项目的项目,前前后后用了20天左右吧。先是用swing写好了仿QQ界面(界面很丑)最后逻辑实现都是后面断断续续加进去的。写这个项目之前没有很好的规划在逻辑实现方面与数据库逻辑交互过于频繁。走了很多的弯路

2.功能实现

1.修改功能(密码、昵称、个性签名)
2.添加好友、删除好友
3.单聊功能
4.判断好友是否在线

3.模块划分

在这里插入图片描述

4.使用的知识

  • netty
  • swing
  • 集合等同步阻塞队列synchronousQueue
  • 数据库MySQL中的CRUD
  • C3p0连接池
  • JSON字符串

5.部分代码实现

1.nettyController.java

接收到来自客户端的消息,与dao层进行交互dao层与之数据库进行交互

修改密码
修改密码逻辑图
添加好友
添加好友逻辑图

从添加好友逻辑实现上我走了很多的弯路频繁的访问数据库,这是一件很不好的事情

package chat.Project.controller;import chat.Project.bean.information;import chat.Project.constant.EnMsgType;import chat.Project.dao.*;import chat.utils.CacheUtil;import chat.utils.JsonUtils;import com.fasterxml.jackson.databind.node.ObjectNode;import io.netty.channel.Channel;import java.util.ArrayList;import java.util.Iterator;public class NettyController {    private static UserDao userDao = new UserDaoImpl();    private static informationDao informationDao = new informationDaoImpl();    private static friendDao friendDao = new friendDaoImpl();    public static String processing(String message, Channel channel){ //解析客户端发送的消息 ObjectNode jsonNodes = JsonUtils.getObjectNode(message); String msgtype = jsonNodes.get("msgtype").asText(); if (EnMsgType.EN_MSG_LOGIN.toString().equals(msgtype)){     //登录操作     return loginOperation(jsonNodes,channel); }else if (EnMsgType.EN_MSG_MODIFY_SIGNATURE.toString().equals(msgtype)){     //修改签名     return modifySignature(jsonNodes); }else if (EnMsgType.EN_MSG_MODIFY_NICKNAME.toString().equals(msgtype)){     //修改昵称     return modifyNickname(jsonNodes); }else if (EnMsgType.EN_MSG_GETINFORMATION.toString().equals(msgtype)){     //获取登录信息     return getInformation(jsonNodes); }else if (EnMsgType.EN_MSG_VERIFY_PASSWORD.toString().equals(msgtype)){     //进行修改密码     return verifyPasswd(jsonNodes); }else if (EnMsgType.EN_MSG_CHAT.toString().equals(msgtype)){     //单聊模式     return SingleChat(jsonNodes); }else if (EnMsgType.EN_MSG_GET_ID.toString().equals(msgtype)){     //获取id     return getId(jsonNodes); }else if (EnMsgType.EN_MSG_GET_FRIEND.toString().equals(msgtype)){     //获取好友列表     return getFriend(jsonNodes); }else if (EnMsgType.EN_MSG_ADD_FRIEND.toString().equals(msgtype)){     //添加好友     return addFriends(jsonNodes); }else if (EnMsgType.EN_MSG_DEL_FRIEND.toString().equals(msgtype)){     //删除好友     return delFriend(jsonNodes); }else if (EnMsgType.EN_MSG_ACTIVE_STATE.toString().equals(msgtype)){     //判断好友的在线状态     return friendIsActive(jsonNodes); } return "";    }    //判断好友在线状态    private static String friendIsActive(ObjectNode jsonNodes) { int friendId = jsonNodes.get("friendId").asInt(); ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_ACTIVE_STATE.toString()); //客户端保证用户独立存在且是好友 Channel channel = CacheUtil.get(friendId); //判断用户是否在线 if (channel == null){     //用户不在线     objectNode.put("code",200); }else {     //用户在线     objectNode.put("code",300); } return objectNode.toString();    }    //添加好友    private static String delFriend(ObjectNode jsonNodes) { Integer friendId = jsonNodes.get("friendId").asInt(); int userId = jsonNodes.get("id").asInt(); String localName = jsonNodes.get("localName").asText(); //封装发回客户端的JSON ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_DEL_FRIEND.toString()); objectNode.put("code",200); //验证是否存在当前好友 information information = informationDao.getInformation(friendId); String friendName = information.getNickname(); //查询自己是否有该好友 boolean exist = friendDao.isExist(friendName,userId); if (exist){     //存在当前好友进行删除操作     friendDao.delFriend(userId,friendName);     friendDao.delFriend(friendId,localName);     objectNode.put("code",300); } return objectNode.toString();    }    //添加好友    private static String addFriends(ObjectNode jsonNodes) { Integer friendId = jsonNodes.get("friendId").asInt(); int userId = jsonNodes.get("id").asInt(); String localName = jsonNodes.get("localName").asText(); //验证是否有ID boolean exists = userDao.verifyExistFriend(friendId); ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_ADD_FRIEND.toString()); objectNode.put("code",200); if(exists){     //表示存在此id     objectNode.put("code",300);     //获取好友昵称     information information = informationDao.getInformation(friendId);     String friendNickname = information.getNickname();     //进行添加好友的操作   两个对应的信息都应该添加     friendDao.addFriends(userId,localName,friendNickname);     friendDao.addFriends(friendId,friendNickname,localName); } return objectNode.toString();    }    //获取好友列表    private static String getFriend(ObjectNode jsonNodes) { int uid = jsonNodes.get("uid").asInt(); //返回ArrayLis集合 ArrayList<String> friends = friendDao.getFriends(uid); //封装JSON ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_GET_FRIEND.toString()); //写回friend集合 Iterator<String> iterator = friends.iterator(); int i = 0; while (iterator.hasNext()){     objectNode.put("res"+i,iterator.next());     i++; } //记录好友个数 objectNode.put("count",i); return objectNode.toString();    }    //获取id    private static String getId(ObjectNode jsonNodes) { String nickname = jsonNodes.get("nickname").asText(); information information = informationDao.nicknameGetId(nickname); //联系人的id int uid = information.getUid(); //封装JSON ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_GET_ID.toString()); objectNode.put("uid",uid); return objectNode.toString();    }    //单聊模式    private static String SingleChat(ObjectNode jsonNodes) { int id = jsonNodes.get("id").asInt(); //根据id在friend表获取登录用户名 //封装JSON数据服务端转发数据 ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_CHAT.toString()); //客户端保证用户独立存在且是好友 Channel channel = CacheUtil.get(id); //判断用户是否在线 if (channel == null){     //用户不在线     objectNode.put("code",200); }else{     //用户在线     objectNode.put("code",300);     //消息转发     channel.writeAndFlush(jsonNodes.toString()); } return objectNode.toString();    }    //修改密码    private static String verifyPasswd(ObjectNode jsonNodes) { int id = jsonNodes.get("id").asInt(); String oldPasswd = jsonNodes.get("oldPasswd").asText(); String newPasswd = jsonNodes.get("newPasswd").asText(); boolean exits = userDao.verifyPassword(oldPasswd, id); ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_VERIFY_PASSWORD.toString()); objectNode.put("code",200); if (exits){     //验证成功     userDao.modifyPasswd(newPasswd,id);     objectNode.put("code",300); } return objectNode.toString();    }    //获取信息    private static String getInformation(ObjectNode jsonNodes) { int id = jsonNodes.get("id").asInt(); information information = informationDao.getInformation(id); //封装JSON发回客户端 ObjectNode objectNode = JsonUtils.getObjectNode(); objectNode.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); objectNode.put("srctype",EnMsgType.EN_MSG_GETINFORMATION.toString()); objectNode.put("Nickname",information.getNickname()); objectNode.put("Signature",information.getSignature()); return objectNode.toString();    }    //修改昵称    private static String modifyNickname(ObjectNode jsonNodes) { int id = jsonNodes.get("id").asInt(); String nickname = jsonNodes.get("nickname").asText(); //进行存储 informationDao.storeNickname(nickname,id); return "";    }    //修改签名    private static String modifySignature(ObjectNode jsonNodes) { int id = jsonNodes.get("id").asInt(); String signature = jsonNodes.get("signature").asText(); //进行存储 informationDao.storeSignature(signature,id); return "";    }    //登录操作    private static String loginOperation(ObjectNode objectNode,Channel channel) { int id = objectNode.get("id").asInt(); String passwd = objectNode.get("passwd").asText(); //进行数据库查询 boolean exits = userDao.getInformation(id, passwd); ObjectNode jsonNodes = JsonUtils.getObjectNode(); jsonNodes.put("msgtype",EnMsgType.EN_MSG_ACK.toString()); jsonNodes.put("srctype",EnMsgType.EN_MSG_LOGIN.toString()); jsonNodes.put("code",300); //返回状态码 if (exits){     jsonNodes.put("code",200);     //添加用户的在线信息     CacheUtil.put(id,channel); } return jsonNodes.toString();    }}

2.ClientHandler.java

客户端接受来自服务端返回的消息根据返回的状态码来判断是否操作成功
package chat.Project.netty;import chat.Frame.chat.ChatFrame;import chat.Frame.chat.linkmen;import chat.Frame.chat.login;import chat.Project.constant.EnMsgType;import chat.util.JsonUtils;import com.fasterxml.jackson.databind.node.ObjectNode;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import java.util.concurrent.SynchronousQueue;public class ClientHandler extends SimpleChannelInboundHandler<String> {    //定义一个同步阻塞队列状态码    public static SynchronousQueue<Object> queue = new SynchronousQueue<>();    public static String Nickname;    public String Signature;    @Override    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {    }    //客户端接收数据    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println(msg); //解析服务端发送的消息 ObjectNode jsonNodes = JsonUtils.getObjectNode((String) msg); String msgtype = jsonNodes.get("msgtype").asText(); if (EnMsgType.EN_MSG_ACK.toString().equals(msgtype)) {     String srctype = jsonNodes.get("srctype").asText();     if (EnMsgType.EN_MSG_LOGIN.toString().equals(srctype)) {  //登录操作  queue.offer(jsonNodes.get("code").asInt());     }else if(EnMsgType.EN_MSG_GETINFORMATION.toString().equals(srctype)){  //存取信息   Nickname = jsonNodes.get("Nickname").asText();   Signature = jsonNodes.get("Signature").asText();  linkmen.label_1.setText(Nickname);  linkmen.field.setText(Signature);     }else if (EnMsgType.EN_MSG_CHAT.toString().equals(srctype)){  //发送端返回消息  queue.offer(jsonNodes.get("code").asInt());     }else if (EnMsgType.EN_MSG_GET_ID.toString().equals(srctype)){  int uid = jsonNodes.get("uid").asInt();  queue.offer(uid);     }else if (EnMsgType.EN_MSG_GET_FRIEND.toString().equals(srctype)){  //获取登录用户的好友  int count = jsonNodes.get("count").asInt();  login.friend = new String[count];  for ( int i = 0;i<count;i++){      login.friend[i] = jsonNodes.get("res"+i).asText();      System.out.println(jsonNodes.get("res"+i));  }     }else if (EnMsgType.EN_MSG_ADD_FRIEND.toString().equals(srctype)){  //添加好友  queue.offer(jsonNodes.get("code").asInt());     }else if (EnMsgType.EN_MSG_DEL_FRIEND.toString().equals(srctype)){  //删除好友  queue.offer(jsonNodes.get("code").asInt());     }else if (EnMsgType.EN_MSG_ACTIVE_STATE.toString().equals(srctype)){  //好友在线状态  queue.offer(jsonNodes.get("code").asInt());     } }else if (EnMsgType.EN_MSG_VERIFY_PASSWORD.toString().equals(msgtype)){     //修改密码     int code = 0;     code = jsonNodes.get("code").asInt();     queue.offer(code); }else if (EnMsgType.EN_MSG_CHAT.toString().equals(msgtype)){     //接收端接受消息  封装朋友昵称     String message = " "+ jsonNodes.get("message").asText();     //聊天显示框读取消息     ChatFrame.sb.append(message+"\n");     ChatFrame.displayTextPanel.setText(ChatFrame.sb.toString()); }    }}

3.linkmen.java

这是登录成功的界面
package chat.Frame.chat;import chat.Frame.operation.alterColumn.changeNickname;import chat.Frame.operation.alterColumn.changePassword;import chat.Frame.operation.alterColumn.changeSignature;import chat.Frame.operation.friendHandle.addFriend;import chat.Frame.operation.friendHandle.delFriend;import chat.Frame.tipFrame;import chat.Project.services.sendServers;import io.netty.channel.Channel;import javax.swing.*;import javax.swing.event.ListSelectionEvent;import javax.swing.event.ListSelectionListener;import java.awt.*;import java.awt.event.ItemEvent;import java.awt.event.ItemListener;/** * 联系人界面 */public class linkmen extends JFrame {    //容器    private JFrame frame;    //标签    private JLabel  label_2, label_3, label_4, label;    //昵称    public static JLabel label_1;    //状态框    private JComboBox box, box_1, box_2;    //图片    private ImageIcon icon_1, icon;    //文本    private JTextField  field_1;    //个性签名    public static JTextField field;    //面板    private JPanel panel_1, panel_3, panel;    //滚动面板    public JScrollPane panel_2;    //列表    public static JList list;    //与服务端通信的通道    private Channel channel;    //用户的id    private Integer id;    //暂存oldPasswd    public static JLabel label_5,label_6;    //好友列表数组    private String[] fd;    //列表    public static DefaultListModel<String> model;    public linkmen(Integer id, Channel channel,String[] fd) { this.id = id; this.channel = channel; this.fd = fd;    }    public void init() { //初始化面板1并设置信息 panel_1 = new JPanel(); panel_1.setLayout(null); panel_1.setLocation(0, 0); panel_1.setBorder(BorderFactory.createTitledBorder("资料卡")); panel_1.setSize(new Dimension(295, 148)); panel_1.setOpaque(false); //初始化面板3并设置信息 panel_3 = new JPanel(); panel_3.setLayout(null); panel_3.setBorder(BorderFactory.createTitledBorder("系统设置")); panel_3.setLocation(0, 617); panel_3.setSize(new Dimension(295, 55)); panel_3.setOpaque(false); //设置头像标签 label_2 = new JLabel(new ImageIcon("E:\\聊天软件\\untitled\\src\\imageSource\\4.png")); label_2.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); label_2.setBounds(15, 15, 100, 100); panel_1.add(label_2); //初始暂存标签 label_5 = new JLabel(); label_6 = new JLabel(); //设置昵称标签 label_1 = new JLabel(""); label_1.setBounds(130, 10, 100, 30); label_1.setFont(new Font("宋体", Font.PLAIN, 18)); panel_1.add(label_1); list = new JList<String>(model); //设置每个列表的高 list.setFixedCellHeight(20); list.setSelectionBackground(new Color(0xD8FF2F)); list.addListSelectionListener(new ListSelectionListener() {     @Override     public void valueChanged(ListSelectionEvent e) {  //打开一个聊天窗口  if (e.getValueIsAdjusting()) {      for (int i = 0; i < model.size(); i++) {   if (model.get(i).equals(list.getSelectedValue())){//获取id有错误int ids = new sendServers(channel).getId((String) list.getSelectedValue());if (ids!=0) {    new sendServers(channel).friendIsActive(ids);    new ChatFrame(ids, channel).setVisible(true);}else{    System.out.println("好友不存在");}   }      }  }     } }); //初始化面板二 panel_2 = new JScrollPane(list); panel_2.setBorder(BorderFactory.createTitledBorder("联系人")); panel_2.setLocation(0, 147); panel_2.setSize(new Dimension(295, 470)); panel_2.getViewport().setOpaque(false); list.setOpaque(false); panel_2.setOpaque(false); //设置在线状态bBox(); box = new JComboBox(); box.addItem("✅在线"); box.addItem("\uD83D\uDCBF隐身"); box.addItem("\uD83D\uDCBB忙碌"); box.addItem("❎离线"); box.setBounds(200, 10, 70, 30); panel_1.add(box); //设置个性签名的标签 label_4 = new JLabel("个性签名:"); label_4.setFont(new Font("宋体", Font.PLAIN, 16)); label_4.setForeground(Color.BLUE); label_4.setBounds(120, 50, 100, 20); panel_1.add(label_4); //设置文本 field = new JTextField(""); field.setBounds(120, 80, 160, 30); panel_1.add(field); label_3 = new JLabel("\uD83D\uDD0D"); label_3.setForeground(Color.RED); label_3.setBounds(10, 122, 20, 20); panel_1.add(label_3); //设置搜索栏 field_1 = new JTextField(); field_1.setBounds(30, 120, 250, 25); panel_1.add(field_1); //对面板三进行初始化 box_1 = new JComboBox(); box_1.addItem("\uD83D\uDD12\uD83D\uDD28\uD83D\uDD13"); box_1.addItem("修改密码"); box_1.addItem("修改昵称"); box_1.addItem("修改签名"); box_1.setBounds(8, 20, 100, 25); panel_3.add(box_1); box_1.addItemListener(new ItemListener() {     @Override     public void itemStateChanged(ItemEvent e) {  if ("修改签名".equals(box_1.getSelectedItem())) {      //执行一次      if (e.getStateChange() == ItemEvent.SELECTED) {   changeSignature changeSignature = new changeSignature(linkmen.this);   changeSignature.setVisible(true);   field.setText(changeSignature.jTextField.getText());   String signature = field.getText();   //存储签名的方法   new sendServers(channel).modifySignature(signature, id);      }  }  if ("修改密码".equals(box_1.getSelectedItem())) {      if (e.getStateChange() == ItemEvent.SELECTED) {   changePassword changePassword = new changePassword(linkmen.this);   changePassword.setVisible(true);   label_5.setText(changePassword.oldPassword.getText());   String oldPasswd = label_5.getText();   label_6.setText(new String(changePassword.newPassword.getPassword()));   String newPasswd = label_6.getText();   //进行验证   new sendServers(channel).verifyPasswd(oldPasswd, id,newPasswd);      }  }  if ("修改昵称".equals(box_1.getSelectedItem())) {      if (e.getStateChange() == ItemEvent.SELECTED) {   changeNickname changeNickname = new changeNickname(linkmen.this);   changeNickname.setVisible(true);   label_1.setText(changeNickname.jTextField.getText());   String nickname = label_1.getText();   //存储昵称   new sendServers(channel).modifyNickname(nickname, id);      }  }     } }); //添加好友、删除好友 box_2 = new JComboBox(); box_2.addItem("\uD83D\uDC65"); box_2.addItem("添加好友"); box_2.addItem("删除好友"); box_2.setBounds(170, 20, 100, 25); box_2.addItemListener(new ItemListener() {     @Override     public void itemStateChanged(ItemEvent e) {  if ("添加好友".equals(box_2.getSelectedItem())) {      if (e.getStateChange() == ItemEvent.SELECTED) {   addFriend addFriend = new addFriend(linkmen.this);   addFriend.setVisible(true);   //读取要搜索的ID   String friendIds = addFriend.jTextField.getText();   //判断是否是字符串   if (judgeDigit(friendIds)){int friendId = Integer.parseInt(friendIds);//搜索数据库new sendServers(channel).addFriendOperate(friendId,id,label_1.getText());   }else {new tipFrame().init("输入参数错误");   }      }  }  if ("删除好友".equals(box_2.getSelectedItem())) {      if (e.getStateChange() == ItemEvent.SELECTED) {   delFriend delFriend = new delFriend(linkmen.this);   delFriend.setVisible(true);   //对其数据库进行删除操作   String friendIds = delFriend.TextField.getText();   //判断是否是字符串   if(judgeDigit(friendIds)){int friendId = Integer.parseInt(friendIds);//操作数据库new sendServers(channel).delFriendOperate(friendId,id,label_1.getText());   }else{new tipFrame().init("输入参数错误");   }      }  }     } }); panel_3.add(box_2); //设置frame信息 frame = new JFrame(); //设置窗体信息 frame.setTitle("腾讯QQ"); //给窗体设置图片 icon_1 = new ImageIcon("E:\\聊天软件\\untitled\\src\\imageSource\\3.png"); frame.setIconImage(icon_1.getImage()); icon = new ImageIcon("E:\\聊天软件\\untitled\\src\\imageSource\\5.png"); label = new JLabel(icon); //获取窗口的第二层,将label放入 frame.getLayeredPane().add(label, new Integer(Integer.MIN_VALUE)); //获取frame的顶层容器,并设置为透明 panel = (JPanel) frame.getContentPane(); panel.setOpaque(false); frame.setLayout(null); frame.setLocation(750, 150); frame.setSize(287, 700); frame.setVisible(true); frame.setResizable(false); label.setBounds(0, 0, 287, 700); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.add(panel_1); frame.add(panel_2); frame.add(panel_3);    }    public void mian() { //初始化面板2并设置信息 model = new DefaultListModel<>(); for (int i = 0; i < fd.length; i++) {     model.addElement(fd[i]); } init(); //更新昵称和签名 new sendServers(channel).update(id); //获取用户的昵称,和好友列表 //设置签名和昵称字体初始样式和大小 label_1.setFont(new Font("宋体", Font.PLAIN, 18)); field.setFont(new Font("宋体", Font.PLAIN, 18));    }    //判断是否是数字    private static boolean judgeDigit(String string){ for (int i = 0; i < string.length(); i++) {     if (!Character.isDigit(string.charAt(i))){  return false;     } } return true;    }}

4.tipFrame

提示操作状态窗口
package chat.Frame;import chat.Frame.chat.linkmen;import chat.Frame.operation.alterColumn.changeNickname;import javax.swing.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;public class tipFrame extends JDialog {    private Container container;    //显示错误信息    public JLabel label;    //确认按钮    private JButton button;    public tipFrame(){    }    public void init(String msg){ container = getContentPane(); label = new JLabel(msg); label.setBounds(70,0,200,70); label.setFont(new Font("微软雅黑",Font.PLAIN,20)); container.add(label); button = new JButton("确认"); button.setBounds(35,50,140,40); container.add(button); setBounds(780,170,220,140); setLayout(null); setVisible(true); container.setBackground(new Color(0xD8FFD5)); //提示窗口前置 setAlwaysOnTop(true); button.addActionListener(new ActionListener() {     @Override     public void actionPerformed(ActionEvent e) {  tipFrame.this.dispose();     } });    }}

6.运行例图

1.登录界面在这里插入图片描述

注册账号和忘记密码没有添加事件现在就是个摆设

2.联系人界面

联系人界面
这里面的所有功能都可以使用

3.聊天界面

聊天窗口
这个里面表情按钮没弄好

4.通信的过程

在这里插入图片描述

5.修改操作

修改操作

6.好友的操作

在这里插入图片描述
代码由于有点多不能全部贴出,希望三连,如需获取源码,可以扫描下方二维码或者搜索并关注微信订阅号“果汁简历”,回复“QQ” 获取源码。