1.产品展示
1.1这是我们的登录界面
2.细节!!!talk is cheap,show me the code
• UI设计
• Socket网络编程
• 多线程与线程通讯
2.1主页面
2.1.1注册表记住密码
/**
* 注册表实现记住密码,注册表在我们的电脑里面,用键值对的方式存取
*/
Preferences pre = Preferences.userNodeForPackage(getClass());
// 注册表实现记住密码
pre = Preferences.userNodeForPackage(UserLoginUI.class);
pre.put(text_uname.getText(), text_pwd.getText());//存放密码
pre.get(text_uname.getText(), null)//根据键,获取密码
2.1.2界面布局
- 首先,我们采用的SWT编程,SWT的窗口会自动提供一个title框,这个title框可玩性太小,不能设置样式图层等,所以我们选择去掉title框
//自定义拖拽事件开始
private int startX;// 记录鼠标移动前的X坐标
private int startY;// 记录鼠标移动前的y坐标
private int lastX;// 记录鼠标移动后的x坐标
private int lastY;// 记录鼠标移动后的y坐标
private boolean isdown;// 记录鼠标是否按下
UserMainUI thisOBJ = this;// 实现窗口拖拉 自己创建自己这个类
/**
* 设置指定区域label_topimage窗口移动事件,你要在那个组件上添加拖拽事件就在那里添加,我这里是在label里添加的
*/
label_topimage.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
thisOBJ.isdown = true;
thisOBJ.startX = e.x;
thisOBJ.startY = e.y;
}
@Override
public void mouseUp(MouseEvent e) {
thisOBJ.isdown = false;
super.mouseUp(e);
}
});
label_topimage.addMouseMoveListener(new MouseMoveListener() {
@Override
public void mouseMove(MouseEvent arg0) {
if (thisOBJ.isdown) {
thisOBJ.lastX = arg0.x;
thisOBJ.lastY = arg0.y;
int x = thisOBJ.lastX - thisOBJ.startX;
int y = thisOBJ.lastY - thisOBJ.startY;
org.eclipse.swt.graphics.Point location = shell.getLocation();
int xx = location.x;
int yy = location.y;
shell.setLocation(x + xx, y + yy);
}
}
});
- 添加自定义的按钮,这里我的思路是直接在label里面贴图,然后设置label的MouseEnter、MouseExit、MouseDown事件,鼠标进入和离开label时通过更改label里贴的图造成按钮的感觉,通过MouseDown添加最小化shell.setMinimized(true)和做大化shell.setMaximized(true)以及销毁shell.dispose。
- 这里关键的是当多个Composite面板和label框堆叠在一起时候的显示问题,我们的做法是把背景图的label放在最下面一层,这样就能呈现比较好的效果了
2.1.3用户主页采用的堆栈式布局StackLayout
主页我们采用的是堆栈式布局,这样就能配合自定义的label按钮的点击事件呈现切换界面的效果
SWT里的Composite面板为我们提供了六种布局方式,分别是
- Absolute layout绝对布局
- GridLayout网格式布局
- FillLayout填充式布局
- RowLayout行式布局
- FormLayout从布局
- StackLayout堆栈式布局
StackLayout堆栈式布局,他是所有的子面板都在堆叠在父面板上,可以通过父面板选在在最上面的那块,但只能看到堆在最上面的那块,可以设计触发事件改变最上面的面板达到切换界面的效果
private StackLayout stackLayoutne;// 堆栈式布局
// 采用堆栈式布局
stackLayoutne = new StackLayout();
composite_down.setLayout(stackLayoutne);
// 将备选的三块面板层叠放到composite_down主面板下
composite_tree = new Composite_tree(composite_down, SWT.NONE, fromQQ);
composite_message = new Composite_message(composite_down, SWT.NONE, fromQQ);
composite_weChat = new Composite_weChat(composite_down, SWT.NONE);
stackLayoutne.topControl = composite_message;// 将composite_message聊天面板设置为主面板
FormData fd_composite_down = new FormData();
fd_composite_down.bottom = new FormAttachment(0, 673);
fd_composite_down.right = new FormAttachment(0, 379);
fd_composite_down.top = new FormAttachment(0, 222);
fd_composite_down.left = new FormAttachment(0);
composite_down.setLayoutData(fd_composite_down);
composite_down.setBackground(SWTResourceManager.getColor(SWT.COLOR_WHITE));
//事件里面切换面板
label_center_center.addMouseListener(new MouseAdapter() {
@Override
public void mouseDown(MouseEvent e) {
lblNewLabel_center_left.setImage(SWTResourceManager.getImage(UserMainUI.class, "/images/消息_不亮.png"));
label_center_center.setImage(SWTResourceManager.getImage(UserMainUI.class, "/images/联系人_亮.png"));
label_center_right.setImage(SWTResourceManager.getImage(UserMainUI.class, "/images/空间_不亮.png"));
stackLayoutne.topControl = composite_tree;// 将composite_message聊天面板设置为主面板
composite_down.layout();// 重置布局,刷新界面
}
});
2.1.4用户主界面里的子面板采用SWT自带的滑动面板ScrolledComposite
这样就可以在以面板为父类的组件增长时自动产生滚动条,如果嫌ScrolledComposite太丑不好看的话,可以自己写一个子类继承ScrolledComposite,重写里面的方法
//设置滚动面板
ScrolledComposite scrolledComposite = new ScrolledComposite(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
scrolledComposite.setBounds(0, 0, 380, 496);
scrolledComposite.setExpandHorizontal(true);
scrolledComposite.setExpandVertical(true);
Composite composite_1 = new Composite(scrolledComposite, SWT.NONE);
composite_1.setBackground(SWTResourceManager.getColor(SWT.COLOR_WHITE));
composite_1.setLayout(new RowLayout(SWT.VERTICAL));// 先将面板设置成行式布局RowLayout,再设置为VERTICAL垂直增长
composite_1.setSize(composite_1.computeSize(SWT.DEFAULT, SWT.DEFAULT));
scrolledComposite.setContent(composite_1);
// 这一行代码至关重要,关于自动增长序列,每次判断下是不是超出设定好的大小,超出了自动设置滑动条
scrolledComposite.setMinSize(composite_1.computeSize(SWT.DEFAULT, SWT.DEFAULT));
//这里值得注意的是,如果是动态增长Composite里的组件,想要自动增长滚动条必须每次刷新后判断
//-----------------<!--自动增长组件的代码--!>------------------------------------------------------------------
composite_1.layout();// 重置布局,刷新界面
// 超级注意!!!必须重置页面再判断是否增长滚动条,否则会出现无法及时增长滚动条的异常
// 这一行代码至关重要,关于自动增长序列,每次判断下是不是超出设定好的大小,超出了设置滑动条
scrolledComposite_chat.setMinSize(composite_1.computeSize(SWT.DEFAULT, SWT.DEFAULT));
2.1.5其他界面
其他界面基本上是自定义的Composite,每次用的时候new一个,并通过构造函数传递参数进去
eclipse里面组件太多了,网上的资料也很分散不系统,可以看看这本书——>《Eclipse SWT/JFACE 核心应用》 清华大学出版社
2.2Socket编程与多线程
建立实时的通讯软件有很多常用的模式,例如同步阻塞IO(JAVA BIO)、同步非阻塞IO(Java NIO) 、异步阻塞IO(Java NIO)、(Java AIO(NIO.2))异步非阻塞IO,由于临近期末考试,笔者也是边学边用,所以用的是最简单也是在业务层次最不成熟的方案同步阻塞IO(JAVA BIO),关于这些模式B站上面有个很好的课程,黑马程序员-Java的IO模式讲解(AIO&BIO&NIO),期考完后我会去好好看的哈哈哈,我这里写的也很简单,通俗易懂,当然问题也还有很多
2.2.1Socket编程
我这里采用的是TCP协议,总体的思路是每个客户端只需要跟服务器进行聊天,不对用户产生点对点的聊天,由服务器充当中介的角色进行消息的转发。每个客户端在连接服务器的时候会先发送一条消息,通过自己写的简单协议将自己的fromQQ,对方的toQQ和消息message以及信号值传递过去,其中信号值flag用来判断用户是想进行单聊群聊还是和机器人聊天,代码里面写的都很详细,大家可以看看,有问题多多指出一起学习哈
package com.fx.net;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import com.yc.util.YcUtil;
import com.yc.util.fxNetUtil;
/**
* 采用TCP协议的服务器,设计思路为当有用户连接上服务器时,
* 用一个map将其socket存起来,key为用户qq号,为群聊时,将群聊里所有用户的socket存在list里面
*
* @author fx
* @time 2021年6月30日08:44:25
*
*/
public class ServiceDemo {
/**
* 分别用map和qunliao存放不同连接的scoket对象
*/
static Map<String, Socket> map = new HashMap<String, Socket>();// 用来存放单人聊天的fromQQ和其socket对象
// 用来放不同对象的map,包含一组socket对象
static volatile List<Socket> list = new ArrayList<Socket>();//用volatile修饰可以使其他线程实时获取到这个变量
public static void main(String[] args) {
// 服务器
// 创建一个服务器
System.out.println("++++++++++++++++++++");
System.out.println("腾迅QQ服务器启动");
System.out.println("++++++++++++++++++++");
System.out.println("服务器启动,时间:" + new SimpleDateFormat("yyyy年MM月dd日HH:mm").format(new Date()));
// 各种对象用以连接
Scanner inScanner = null;
ServerSocket ss = null;
Socket socket = null;
String message;// 记录客户端发送过来的协议消息
try {
ss = new ServerSocket(9999);//占有并开放端口
while (true) {
// 创建一个接收连接客户端的对象 9999为开放的端口
socket = ss.accept();
String ip_port = new String(socket.getRemoteSocketAddress().toString()).split("/")[1];
System.out.println("有客户端联接上来了,它是:" + ip_port);
// 首先要接受客户端发到的第一句话,包含fromQQ,toQQ,message和flag信号值
inScanner = new Scanner(socket.getInputStream());// 接受收入流里的字节流
message = inScanner.nextLine();//这样接收消息高效且简单
if ("0".equals(YcUtil.objToString(fxNetUtil.getflag(message)))) {
// 判断为与机器人聊天
new Thread(new ServiceThread(socket)).start();// new一个机器人的线程与用户交流
} else if ("1".equals(YcUtil.objToString(fxNetUtil.getflag(message)))) {
// 判断为单聊,先将socket对象存到map中
map.put(fxNetUtil.getfromQQ(message), socket);
// 查找其聊天对象是否在线上,在线上应存在socket对象
if (map.containsValue(map.get(fxNetUtil.gettoQQ(message)))) {
// 存在,即通过两个好友的socket对象进行聊天
new Thread(new ServiceThreadsingleChat(map.get(fxNetUtil.gettoQQ(message)),
map.get(fxNetUtil.getfromQQ(message)))).start();
}
if (map.size() == 2) {
// 当有两个用户建立连接后,清空map,为后面用户腾出空间
map.clear();
}
} else if ("2".equals(YcUtil.objToString(fxNetUtil.getflag(message)))) {
// 判断为群聊,先将此时访问的用户qq和其socket对象存储起来
list.add(socket);
// 每个用户进入群聊都开启一个线程进行监控,但list对象共享
new Thread(new ServiceThreadWechat(message, socket)).start();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {// 关闭所有资源
if (null != socket) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != ss) {
try {
ss.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
其他单聊群聊与和机器人聊天的代码在源码里都有,可以去参看一下,问题还是有很多滴,这里就不献丑展示了,只能说代码能实现,但一点都不优美
2.2.2接入图灵机器人API,进行机器人聊天
代码是网上东拼西凑的,我就截取来一下爬取到数据里面的有效字段,其他的也搞不懂
package com.fx.http;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.List;
import java.util.Map;
/**
* @Author fx
* @Date 2021年7月1日09:20:25
*/
public class HttpTest {
static String url = null;
static String param = "";
public static String GetUrl(String question) {
if (null == question) {
question = "衡阳天气怎么样";
}
url = "http://api.jisuapi.com/iqa/query?appkey=62958a3a6ef3c56d&question=" + encode(question);
String result = "";// 访问返回结果
BufferedReader read = null;// 读取访问结果
try {
// 创建url
URL realurl = new URL(url + "?" + param);
// 打开连接
URLConnection connection = realurl.openConnection();
// 设置通用的请求属性
connection.setRequestProperty("accept", "*/*");
connection.setRequestProperty("connection", "Keep-Alive");
connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
// 建立连接
connection.connect();
// 获取所有响应头字段
Map<String, List<String>> map = connection.getHeaderFields();
read = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;// 循环读取
while ((line = read.readLine()) != null) {
result += line;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (read != null) {// 关闭流
try {
read.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 截取需要的有效字符
return new String(result).split("\"content\":\"")[1].split("\",\"relquestion\"")[0];
}
/**
* Unicode编码
*
* @param url
* @return
*/
public static String encode(String url)
{
try {
String encodeURL = URLEncoder.encode(url, "UTF-8");
return encodeURL;
} catch (UnsupportedEncodingException e) {
return "Issue while encoding" + e.getMessage();
}
}
/**
* Unicode解码
*
* @param url
* @return
*/
public static String decode(String url) {
try {
String prevURL = "";
String decodeURL = url;
while (!prevURL.equals(decodeURL)) {
prevURL = decodeURL;
decodeURL = URLDecoder.decode(decodeURL, "UTF-8");
}
return decodeURL;
} catch (UnsupportedEncodingException e) {
return "Issue while decoding" + e.getMessage();
}
}
}
其他的问题暑假再一一完善叭,仓库里面的代码有很多试错时候留下的初期版本,没时间去掉了!!!写了半个月,现在要好好准备期末考试了
笔记我是先放在了我的笔记本里面,里面效果会好一点
笔记本地址:我的笔记,大佬轻喷!
源码地址:我的Gitee,@奉先大大鸭