diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..8b7fcc4
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,86 @@
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.5.6
+
+
+
+ cn.csuosa
+ ChattingRoom-server
+ 1.0-SNAPSHOT
+
+
+ 14
+ 14
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+ 2.5.6
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ 2.5.6
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ 2.5.6
+
+
+
+
+ org.jline
+ jline
+ 3.20.0
+
+
+ org.jline
+ jline-terminal-jansi
+ 3.20.0
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ 2.5.5
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 14
+
+
+
+
+
+ src/main/java
+
+ **/*.*
+
+
+
+ src/main/resources
+
+ **/*.*
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelActions.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelActions.java
new file mode 100644
index 0000000..cdb1112
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelActions.java
@@ -0,0 +1,123 @@
+package cn.CSUOSA.ChattingRoomServer.Channel;
+
+import cn.CSUOSA.ChattingRoomServer.Main;
+import cn.CSUOSA.ChattingRoomServer.OverWriteMethod.Out;
+import cn.CSUOSA.ChattingRoomServer.ReturnParams.BoolMsgWithObj;
+import org.springframework.lang.Nullable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.CSUOSA.ChattingRoomServer.User.UserActions.verifyUser;
+
+@RestController
+@RequestMapping("/channel")
+public class ChannelActions
+{
+ public static boolean verifyChannel(String name, String ticket)
+ {
+ if (ticket == null)
+ ticket = "";
+ if (name.length() > 64 || (!ticket.isEmpty() && ticket.length() != 6))
+ return false;
+ else
+ {
+ //查找是否含有非法字符
+ for (char ch : name.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return false;
+ }
+ }
+ for (char ch : ticket.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return false;
+ }
+ }
+ return Main.ChannelList.containsKey(name) && Main.ChannelList.get(name).getTicket().equals(ticket);
+ }
+ }
+
+ //用户登录相关
+ @PostMapping("/reg")
+ public BoolMsgWithObj channelRegistry(@RequestParam(value = "usrNick") String usrNick, @RequestParam(value = "name") String name, String usrTicket, @Nullable String ticket)
+ {
+ if (!verifyUser(usrNick, usrTicket))
+ return new BoolMsgWithObj(false, "Authentication failed.");
+
+ if (name.length() > 64 || (ticket != null && ticket.length() != 6))
+ return new BoolMsgWithObj(false, "Name or Ticket too long.");
+ else
+ {
+ //查找是否含有非法字符
+ for (char ch : name.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return new BoolMsgWithObj(false, "Invalid name.");
+ }
+ }
+ if (ticket != null)
+ for (char ch : ticket.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return new BoolMsgWithObj(false, "Invalid ticket.");
+ }
+ }
+ if (Main.ChannelList.containsKey(name))
+ return new BoolMsgWithObj(false, "Channel already opened.");
+
+ Out.Info("Channel [" + name + "] Opened");
+ Main.ChannelList.put(name, new ChannelInfo(name, (ticket == null) ? "" : ticket));
+ Out.Info("User [" + usrNick + "] joined channel [" + name + "]");
+ Main.ChannelList.get(name).addMember(Main.UserList.get(usrNick));
+ return new BoolMsgWithObj(true, "");
+ }
+ }
+
+ @PostMapping("/join")
+ public BoolMsgWithObj channelJoin(@RequestParam(value = "usrNick") String usrNick, @RequestParam(value = "name") String name, String usrTicket, @Nullable String ticket)
+ {
+ if (!verifyUser(usrNick, usrTicket))
+ return new BoolMsgWithObj(false, "Authentication failed.");
+
+ if (name.length() > 64 || (ticket != null && ticket.length() != 6))
+ return new BoolMsgWithObj(false, "Nick or Ticket too long.");
+ else
+ {
+ //查找是否含有非法字符
+ for (char ch : name.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return new BoolMsgWithObj(false, "Invalid name.");
+ }
+ }
+ if (ticket != null)
+ for (char ch : ticket.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return new BoolMsgWithObj(false, "Invalid ticket.");
+ }
+ }
+ if (!Main.ChannelList.containsKey(name))
+ return new BoolMsgWithObj(false, "Channel does not exist.");
+ if (!Main.ChannelList.get(name).getTicket().isEmpty() && ticket == null)
+ return new BoolMsgWithObj(false, "This channel needs a ticket.");
+ if (!Main.ChannelList.get(name).getTicket().isEmpty() && ticket != null && !Main.ChannelList.get(name).getTicket().equals(ticket))
+ return new BoolMsgWithObj(false, "Wrong ticket.");
+ if (Main.ChannelList.get(name).getMembers().contains(Main.UserList.get(usrNick)))
+ return new BoolMsgWithObj(false, "You have already in this channel.");
+
+ Out.Info("User [" + usrNick + "] joined channel [" + name + "]");
+ Main.ChannelList.get(name).addMember(Main.UserList.get(usrNick));
+ return new BoolMsgWithObj(true, "");
+ }
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelChecker.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelChecker.java
new file mode 100644
index 0000000..9027ff9
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelChecker.java
@@ -0,0 +1,34 @@
+package cn.CSUOSA.ChattingRoomServer.Channel;
+
+import cn.CSUOSA.ChattingRoomServer.Main;
+import cn.CSUOSA.ChattingRoomServer.OverWriteMethod.Out;
+import cn.CSUOSA.ChattingRoomServer.User.UserInfo;
+
+public class ChannelChecker implements Runnable
+{
+ @Override
+ public void run()
+ {
+ if (!Main.ChannelList.isEmpty())
+ {
+ ChannelInfo channelInfo;
+ for (String nowKey : Main.ChannelList.keySet())
+ {
+ channelInfo = Main.ChannelList.get(nowKey);
+ for (UserInfo nowMember : channelInfo.getMembers())
+ if (nowMember != null && !Main.UserList.containsKey(nowMember.getNick()))
+ {
+ Out.Info("User [" + nowMember.getNick() + "] left channel [" + nowKey + "]");
+ channelInfo.removeMember(nowMember);
+ if (channelInfo.getMembers().isEmpty())
+ break;
+ }
+ if (channelInfo.getMembers().isEmpty())
+ {
+ Main.ChannelList.remove(nowKey);
+ Out.Info("Channel [" + nowKey + "] Closed");
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelInfo.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelInfo.java
index 9f6a7ae..68f7930 100644
--- a/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelInfo.java
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/Channel/ChannelInfo.java
@@ -1,13 +1,16 @@
package cn.CSUOSA.ChattingRoomServer.Channel;
+import cn.CSUOSA.ChattingRoomServer.Message.MessageInfo;
import cn.CSUOSA.ChattingRoomServer.User.UserInfo;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
public class ChannelInfo
{
private final List members;
+ private final ConcurrentHashMap messageList;
private final String name;
private final String ticket;
@@ -16,6 +19,7 @@ public ChannelInfo(String name, String ticket)
this.name = name;
this.ticket = ticket;
members = new ArrayList<>();
+ messageList = new ConcurrentHashMap<>();
}
public boolean addMember(UserInfo member)
@@ -37,7 +41,7 @@ public boolean removeMember(UserInfo member)
}
return false;
}
-
+
public String getName() {return name;}
public String getTicket() {return ticket;}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/ConsoleController/CmdProcessor.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/ConsoleController/CmdProcessor.java
new file mode 100644
index 0000000..62c09d7
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/ConsoleController/CmdProcessor.java
@@ -0,0 +1,50 @@
+package cn.CSUOSA.ChattingRoomServer.ConsoleController;
+
+import cn.CSUOSA.ChattingRoomServer.Main;
+import cn.CSUOSA.ChattingRoomServer.OverWriteMethod.Out;
+import org.jline.reader.LineReaderBuilder;
+import org.jline.terminal.TerminalBuilder;
+
+import java.io.IOException;
+
+public class CmdProcessor implements Runnable
+{
+ @Override
+ public void run()
+ {
+ try
+ {
+ Main.terminal = TerminalBuilder.builder()
+ .system(true)
+ .jansi(true)
+ .build();
+
+ Main.lineReader = LineReaderBuilder.builder()
+ .terminal(Main.terminal)
+ .build();
+
+ String prompt = "ChatServer> ";
+ while (true)
+ {
+ String line = Main.lineReader.readLine(prompt);
+ String[] args = line.split(" ");
+ CommandProcessor(args);
+ }
+ } catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void CommandProcessor(String[] args)
+ {
+ switch (args[0])
+ {
+ case "exit", "Exit", "EXIT" -> {
+ Out.Warn("Server Exit.");
+ System.exit(0);
+ }
+ case "list", "List", "LIST" -> Processors.listObject(args);
+ }
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/Main.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/Main.java
new file mode 100644
index 0000000..0c09383
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/Main.java
@@ -0,0 +1,40 @@
+package cn.CSUOSA.ChattingRoomServer;
+
+import cn.CSUOSA.ChattingRoomServer.Channel.ChannelInfo;
+import cn.CSUOSA.ChattingRoomServer.ConsoleController.CmdProcessor;
+import cn.CSUOSA.ChattingRoomServer.User.UserInfo;
+import cn.CSUOSA.ChattingRoomServer.User.UserMapTimer;
+import org.jline.reader.LineReader;
+import org.jline.terminal.Terminal;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.core.env.Environment;
+
+import java.util.HashMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+@SpringBootApplication
+public class Main
+{
+ public static ConcurrentHashMap UserList;
+ public static HashMap ChannelList;
+ public static UserMapTimer userMapTimer = new UserMapTimer();
+ public static Terminal terminal;
+ public static LineReader lineReader;
+ private static ConfigurableApplicationContext CAC;
+ @Autowired
+ Environment environment;
+
+ public static void main(String[] args)
+ {
+ UserList = new ConcurrentHashMap<>();
+ ChannelList = new HashMap<>();
+
+ CAC = SpringApplication.run(Main.class, args);
+ new Thread(userMapTimer).start();
+
+ new Thread(new CmdProcessor()).start();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/Message/MessageActions.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/Message/MessageActions.java
new file mode 100644
index 0000000..2767168
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/Message/MessageActions.java
@@ -0,0 +1,39 @@
+package cn.CSUOSA.ChattingRoomServer.Message;
+
+import cn.CSUOSA.ChattingRoomServer.ReturnParams.BoolMsgWithObj;
+import org.springframework.lang.Nullable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.ArrayList;
+
+import static cn.CSUOSA.ChattingRoomServer.Channel.ChannelActions.verifyChannel;
+import static cn.CSUOSA.ChattingRoomServer.User.UserActions.verifyUser;
+
+@RestController
+@RequestMapping("/msg")
+public class MessageActions
+{
+ @PostMapping("/send")
+ public BoolMsgWithObj sendMsg(@RequestParam(value = "usrNick") String usrNick, @RequestParam(value = "name") String name, String usrTicket, @Nullable String ticket, ArrayList msg)
+ {
+ if (!verifyUser(usrNick, usrTicket))
+ return new BoolMsgWithObj(false, "Authentication failed.");
+
+ if (!verifyChannel(name, ticket))
+ return new BoolMsgWithObj(false, "Un Known Channel.");
+
+ return new BoolMsgWithObj(true, "").setReturnObj(msg);
+ }
+
+ @PostMapping("/get")
+ public BoolMsgWithObj getMsg(@RequestParam(value = "nick") String nick, String ticket)
+ {
+ if (!verifyUser(nick, ticket))
+ return new BoolMsgWithObj(false, "Authentication failed.");
+
+ return new BoolMsgWithObj(true, "").setReturnObj(new MessageInfo(0, "1970-1-1 00:00:00", "Test", "Test", "TestMSG"));
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/Message/MessageInfo.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/Message/MessageInfo.java
new file mode 100644
index 0000000..d366c6a
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/Message/MessageInfo.java
@@ -0,0 +1,39 @@
+package cn.CSUOSA.ChattingRoomServer.Message;
+
+public class MessageInfo
+{
+ private final long id;
+ private final String recTime;
+ private final String channelName;
+ private final String senderNick;
+ private final String msg;
+
+ public MessageInfo(long id, String recTime, String channelName, String senderNick, String msg)
+ {
+ this.id = id;
+ this.recTime = recTime;
+ this.channelName = channelName;
+ this.senderNick = senderNick;
+ this.msg = msg;
+ }
+
+ public String getRecTime()
+ {
+ return recTime;
+ }
+
+ public String getChannelName()
+ {
+ return channelName;
+ }
+
+ public String getSenderNick()
+ {
+ return senderNick;
+ }
+
+ public String getMsg()
+ {
+ return msg;
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/ReturnParams/BoolMsgWithObj.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/ReturnParams/BoolMsgWithObj.java
new file mode 100644
index 0000000..e814c80
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/ReturnParams/BoolMsgWithObj.java
@@ -0,0 +1,35 @@
+package cn.CSUOSA.ChattingRoomServer.ReturnParams;
+
+public class BoolMsgWithObj
+{
+ boolean success;
+ String msg;
+ Object returnObj;
+
+ public BoolMsgWithObj(boolean b, String msg)
+ {
+ success = b;
+ this.msg = msg;
+ }
+
+ public Object getReturnObj()
+ {
+ return returnObj;
+ }
+
+ public boolean getSuccess()
+ {
+ return success;
+ }
+
+ public String getMsg()
+ {
+ return msg;
+ }
+
+ public BoolMsgWithObj setReturnObj(Object returnObj)
+ {
+ this.returnObj = returnObj;
+ return this;
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserActions.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserActions.java
new file mode 100644
index 0000000..f541d65
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserActions.java
@@ -0,0 +1,85 @@
+package cn.CSUOSA.ChattingRoomServer.User;
+
+import cn.CSUOSA.ChattingRoomServer.Main;
+import cn.CSUOSA.ChattingRoomServer.OverWriteMethod.Out;
+import cn.CSUOSA.ChattingRoomServer.ReturnParams.BoolMsgWithObj;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/user")
+public class UserActions
+{
+ //验证用户
+ public static boolean verifyUser(String nick, String ticket)
+ {
+ if (nick.length() > 32 || ticket.length() != 6)
+ return false;
+ else
+ {
+ //查找是否含有非法字符
+ for (char ch : nick.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return false;
+ }
+ }
+ for (char ch : ticket.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return false;
+ }
+ }
+ if (!Main.UserList.containsKey(nick) || !Main.UserList.get(nick).getTicket().equals(ticket))
+ return false;
+ Main.UserList.get(nick).ResetLeftTime();
+ return true;
+ }
+ }
+
+ //用户登录相关
+ @PostMapping("/login")
+ public BoolMsgWithObj userLogin(@RequestParam(value = "nick") String nick, String ticket)
+ {
+ if (nick.length() > 32 || ticket.length() != 6)
+ return new BoolMsgWithObj(false, "Nick or Ticket too long.");
+ else
+ {
+ //查找是否含有非法字符
+ for (char ch : nick.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return new BoolMsgWithObj(false, "Invalid nick.");
+ }
+ }
+ for (char ch : ticket.toCharArray())
+ {
+ if (((ch < 48) || (ch > 57)) && ((ch < 65) || (ch > 90)) && ((ch < 97) || (ch > 122)))
+ {
+ return new BoolMsgWithObj(false, "Invalid ticket.");
+ }
+ }
+ if (Main.UserList.containsKey(nick))
+ return new BoolMsgWithObj(false, "Nickname already taken.");
+
+ Out.Info("Nick [" + nick + "] Occupied");
+ Main.UserList.put(nick, new UserInfo(nick, ticket)); //添加昵称占用与计时器
+ return new BoolMsgWithObj(true, "");
+ }
+ }
+
+ @PostMapping("/renew")
+ public BoolMsgWithObj userRenew(@RequestParam(value = "nick") String nick, String ticket)
+ {
+ if (!verifyUser(nick, ticket))
+ return new BoolMsgWithObj(false, "Authentication failed.");
+
+ Main.UserList.get(nick).ResetLeftTime();
+ return new BoolMsgWithObj(true, "");
+ }
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserInfo.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserInfo.java
new file mode 100644
index 0000000..de2309c
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserInfo.java
@@ -0,0 +1,22 @@
+package cn.CSUOSA.ChattingRoomServer.User;
+
+public class UserInfo
+{
+ private static final int TimeInternal = 30; //second
+ private final String nick;
+ private final String ticket;
+ public int leftTime;
+
+ public UserInfo(String nick, String ticket)
+ {
+ this.nick = nick;
+ this.ticket = ticket;
+ this.leftTime = TimeInternal;
+ }
+
+ public void ResetLeftTime() {leftTime = TimeInternal;}
+
+ public String getNick() {return nick;}
+
+ public String getTicket() {return ticket;}
+}
diff --git a/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserMapTimer.java b/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserMapTimer.java
new file mode 100644
index 0000000..e09f60b
--- /dev/null
+++ b/src/main/java/cn/CSUOSA/ChattingRoomServer/User/UserMapTimer.java
@@ -0,0 +1,44 @@
+package cn.CSUOSA.ChattingRoomServer.User;
+
+import cn.CSUOSA.ChattingRoomServer.Channel.ChannelChecker;
+import cn.CSUOSA.ChattingRoomServer.Main;
+import cn.CSUOSA.ChattingRoomServer.OverWriteMethod.Out;
+
+public class UserMapTimer implements Runnable
+{
+ private Boolean Enabled = true;
+
+ public void setEnabled(Boolean enabled)
+ {
+ Enabled = enabled;
+ }
+
+ @Override
+ public synchronized void run()
+ {
+ try
+ {
+ while (Enabled)
+ {
+ if (!Main.UserList.isEmpty())
+ {
+ Main.UserList.forEach((key, value) -> {
+ UserInfo nickTime = Main.UserList.get(key);
+
+ nickTime.leftTime--;
+ if (nickTime.leftTime == 0)
+ {
+ Main.UserList.remove(key);
+ Out.Info("Nick [" + key + "] Released");
+ new Thread(new ChannelChecker()).start();
+ }
+ });
+ }
+ this.wait(1000);
+ }
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}