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 + 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(); + } + } +}