diff --git a/pom.xml b/pom.xml index ec8412a..a38f594 100644 --- a/pom.xml +++ b/pom.xml @@ -132,6 +132,10 @@ easyexcel 3.2.1 + + org.springframework.boot + spring-boot-starter-websocket + diff --git a/src/main/java/top/xinsin/Main.java b/src/main/java/top/xinsin/Main.java index 127f10b..075a16f 100644 --- a/src/main/java/top/xinsin/Main.java +++ b/src/main/java/top/xinsin/Main.java @@ -3,9 +3,11 @@ package top.xinsin; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @MapperScan("top.xinsin.mapper") +@EnableScheduling public class Main { public static void main(String[] args) { SpringApplication.run(Main.class,args); diff --git a/src/main/java/top/xinsin/config/WebsocketConfiguration.java b/src/main/java/top/xinsin/config/WebsocketConfiguration.java new file mode 100644 index 0000000..452e022 --- /dev/null +++ b/src/main/java/top/xinsin/config/WebsocketConfiguration.java @@ -0,0 +1,22 @@ +package top.xinsin.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.config.annotation.EnableWebSocket; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import top.xinsin.controller.WebSocketController; +import top.xinsin.service.WebSocketService; + +@Configuration +@EnableWebSocket +public class WebsocketConfiguration { + @Bean + public ServerEndpointExporter serverEndpointExporter() { + return new ServerEndpointExporter(); + } + @Autowired + public void socketUserService(WebSocketService webSocketService){ + WebSocketController.webSocketService= webSocketService; + } +} diff --git a/src/main/java/top/xinsin/controller/WebSocketController.java b/src/main/java/top/xinsin/controller/WebSocketController.java new file mode 100644 index 0000000..0b23272 --- /dev/null +++ b/src/main/java/top/xinsin/controller/WebSocketController.java @@ -0,0 +1,85 @@ +package top.xinsin.controller; + + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import jakarta.websocket.*; +import jakarta.websocket.server.PathParam; +import jakarta.websocket.server.ServerEndpoint; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import top.xinsin.service.WebSocketService; +import top.xinsin.util.R; + +import java.io.IOException; +import java.net.http.WebSocket; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +@Component +@ServerEndpoint(value = "/api/auth/websocket/{userId}") +@Slf4j +public class WebSocketController { + private static ConcurrentHashMap webSocketMap = new ConcurrentHashMap<>(); + + private Session session; + private String userId; + public static WebSocketService webSocketService; + + @OnOpen + public void onOpen(Session session, @PathParam("userId") String userId) { + this.session = session; + this.userId = userId; + //加入map + webSocketMap.put(userId, this); + R stringR = webSocketService.addVerify(userId); + sendMessageByUserId(userId, stringR); + } + @OnClose + public void onClose() { + //从map中删除 + webSocketMap.remove(userId); + } + @OnMessage + public void onMessage(String message, Session session) { + sendMessageByUserId(userId,webSocketService.parseMessage(userId,message,webSocketMap)); + } + @OnError + public void onError(Session session, Throwable error) { + log.error("用户错误:" + this.userId + ",原因:" + error.getMessage()); + error.printStackTrace(); + } + /** + * 向客户端发送消息 + */ + @SneakyThrows + public void sendMessage(String message) { + this.session.getBasicRemote().sendText(message); + } + + /** + * 通过userId向客户端发送消息 + */ + @SneakyThrows + public void sendMessageByUserId(String userId, R message) { + if(webSocketMap.containsKey(userId)){ + webSocketMap.get(userId).sendMessage(JSON.toJSONString(message)); + }else{ + log.error("用户{}不在线",userId); + } + } + @Scheduled(fixedRate = 10000) + public void verifyTime(){ + List expireTimes = webSocketService.getExpireTimes(); + expireTimes.forEach(e -> { + if(webSocketMap.containsKey(e)){ + webSocketMap.get(e).sendMessage(JSON.toJSONString(R.success(new JSONObject().fluentPut("info","当前登录用户已过期")))); + } + }); + } +} diff --git a/src/main/java/top/xinsin/service/AccountService.java b/src/main/java/top/xinsin/service/AccountService.java index c705b44..f3d10c7 100644 --- a/src/main/java/top/xinsin/service/AccountService.java +++ b/src/main/java/top/xinsin/service/AccountService.java @@ -187,7 +187,7 @@ public class AccountService implements UserDetailsService { Verify verify = new Verify(accountAndStoreAdmin.getCreateId(), authAccount.getId(), accountAndStoreAdmin.getCountdown()); int insert1 = verifyMapper.insert(verify); if (insert1 == 1){ - redisTemplate.opsForValue().set(String.valueOf(authAccount.getId()),authAccount.getUsername(),verify.getCountdown(), TimeUnit.SECONDS); +// redisTemplate.opsForValue().set(String.valueOf(authAccount.getId()),authAccount.getUsername(),verify.getCountdown(), TimeUnit.SECONDS); return R.success(new JSONObject().fluentPut("status","添加该商户成功").fluentPut("nextVerificationTime",verify.getCountdown())); } } diff --git a/src/main/java/top/xinsin/service/WebSocketService.java b/src/main/java/top/xinsin/service/WebSocketService.java new file mode 100644 index 0000000..12d19d2 --- /dev/null +++ b/src/main/java/top/xinsin/service/WebSocketService.java @@ -0,0 +1,161 @@ +package top.xinsin.service; + +import com.alibaba.fastjson2.JSON; +import com.alibaba.fastjson2.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.stereotype.Service; +import top.xinsin.controller.WebSocketController; +import top.xinsin.mapper.VerifyMapper; +import top.xinsin.pojo.Verify; +import top.xinsin.util.HttpCodes; +import top.xinsin.util.R; +import top.xinsin.util.StringConstant; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import static org.apache.naming.SelectorContext.prefix; + +@Service +@Slf4j +public class WebSocketService { + @Resource + private RedisTemplate redisTemplate; + @Autowired + private VerifyMapper verifyMapper; + + public R addVerify(String userId){ + RedisSerializer stringSerializer = new StringRedisSerializer(); + redisTemplate.setValueSerializer(stringSerializer); + redisTemplate.setKeySerializer(stringSerializer); + if (userId.equals("-114514")){ + return R.success("检测为管理员登录"); + } + Object o = redisTemplate.opsForValue().get(StringConstant.VERIFY_PRE + userId); + Object o1 = redisTemplate.opsForValue().get(StringConstant.VERIFY_PRE + userId + "_no"); + System.out.println(o == null); + System.out.println(o1 == null); + if (o == null && o1 == null){ + Verify verify = verifyMapper.selectOne(new LambdaQueryWrapper().eq(Verify::getStoreId, userId)); + if (verify != null){ + if(userId.equals(String.valueOf(verify.getStoreId()))) { + redisTemplate.opsForValue().set(StringConstant.VERIFY_PRE + userId, String.valueOf(verify.getNum()), verify.getCountdown(), TimeUnit.SECONDS); + redisTemplate.opsForValue().set(StringConstant.VERIFY_PRE + userId + "_no", String.valueOf(verify.getNum())); + return R.success("已将该用户过期时间存入"); + } + } + }else{ + return R.success("当前用户已存在"); + } + return R.failed(HttpCodes.HTTP_CODES500,"不存在该用户"); + } + public R parseMessage(String userId, String message, ConcurrentHashMap webSocketMap){ + JSONObject parse = JSONObject.parse(message); + String type = (String) parse.get("type"); + switch (type) { + case "ping" -> { + return ping(userId); + } + case "addVerifyInfo" -> { + return addVerifyInfo(userId, parse); + } + case "getTime" -> { + return getTime(userId); + } + case "confirmVerifyInfo" -> { + return confirmVerifyInfo(parse,webSocketMap); + } + } + return null; + } + + private R confirmVerifyInfo(JSONObject parse, ConcurrentHashMap webSocketMap) { + String verifyResult = String.valueOf(parse.get("verifyResult")); + String userId = String.valueOf(parse.get("userId")); + + int i = Integer.parseInt(String.valueOf(redisTemplate.opsForValue().get(StringConstant.VERIFY_PRE + userId + "_no"))); + System.out.println(i); + Verify verify = new Verify(); + verify.setVerifyResult(verifyResult); + verify.setNum(i + 1); + int update = verifyMapper.update(verify, new LambdaQueryWrapper().eq(Verify::getStoreId, userId)); + if (update != 1){ + return R.failed(HttpCodes.HTTP_CODES500,new JSONObject().fluentPut("info","信息修改失败")); + } + if (verifyResult.equals("false")){ + if (webSocketMap.containsKey(userId)){ + webSocketMap.get(userId).sendMessage(JSON.toJSONString(new JSONObject().fluentPut("info","管理员未同意你的验证信息,你可以重新进行验证"))); + } + return R.success(new JSONObject().fluentPut("info","你已拒绝该商户的验证信息")); + }else{ + if (webSocketMap.containsKey(userId)){ + webSocketMap.get(userId).sendMessage(JSON.toJSONString(new JSONObject().fluentPut("info","管理员已同意你的验证信息,请重新登录"))); + redisTemplate.delete(StringConstant.VERIFY_PRE + userId + "_no"); + Verify verify1 = new Verify(); + verify.setVerifyPhone(""); + verify.setVerifyName(""); + verifyMapper.update(verify1, new LambdaQueryWrapper().eq(Verify::getStoreId, userId)); + } + return R.success(new JSONObject().fluentPut("info","你已同意该商户的验证信息")); + } + } + + private R getTime(String userId) { + Long expire = redisTemplate.getExpire(StringConstant.VERIFY_PRE + userId); + assert expire != null; + if (expire.equals(-2L)){ + Object o = redisTemplate.opsForValue().get(StringConstant.VERIFY_PRE + userId + "_no"); + if (o != null){ + return R.success(new JSONObject().fluentPut("info","当前用户登录已过期")); + } + return R.failed(HttpCodes.HTTP_CODES500,new JSONObject().fluentPut("info","没有该用户登录信息")); + }else{ + return R.success(new JSONObject().fluentPut("info",expire)); + } + } + + private R addVerifyInfo(String userId, JSONObject parse) { + Long expire = redisTemplate.getExpire(StringConstant.VERIFY_PRE + userId); + Object o = redisTemplate.opsForValue().get(StringConstant.VERIFY_PRE + userId + "_no"); + assert expire != null; + if (expire.equals(-2L) && o != null){ + Object verifyName = parse.get("verifyName"); + Object verifyPhone = parse.get("verifyPhone"); + Verify verify = new Verify(); + verify.setVerifyName(String.valueOf(verifyName)); + verify.setVerifyPhone(String.valueOf(verifyPhone)); + int update = verifyMapper.update(verify, new LambdaQueryWrapper().eq(Verify::getStoreId, userId)); + return update == 1 ? R.success(new JSONObject().fluentPut("info","验证信息添加成功,请耐心等待管理员审核")) : R.failed(HttpCodes.HTTP_CODES500,new JSONObject().fluentPut("info","添加验证信息失败")); + } + return R.failed(HttpCodes.HTTP_CODES500,new JSONObject().fluentPut("info","当前用户还未过期或者未登陆过")); + } + + private R ping(String userId) { + return R.success(new JSONObject().fluentPut("info","pong").fluentPut("time",redisTemplate.getExpire(StringConstant.VERIFY_PRE + userId))); + } + public List getExpireTimes(){ + List expireUser = new ArrayList<>(); + Set keys = redisTemplate.keys(prefix.concat(StringConstant.VERIFY_PRE + "*")); + assert keys != null; + keys.forEach(e -> { + if (Objects.equals(redisTemplate.getExpire(e), -1L)){ + expireUser.add(e); + redisTemplate.delete(e); + }else if (Objects.equals(redisTemplate.getExpire(e),-2L)){ + redisTemplate.delete(e); + } + }); + log.info("{}",keys); + return expireUser; + } +} diff --git a/src/main/java/top/xinsin/util/StringConstant.java b/src/main/java/top/xinsin/util/StringConstant.java index 5d8b0dc..8697786 100644 --- a/src/main/java/top/xinsin/util/StringConstant.java +++ b/src/main/java/top/xinsin/util/StringConstant.java @@ -5,4 +5,6 @@ public class StringConstant { public static final String EMAIL_REGEX = "\\w[-\\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\\.)+[A-Za-z]{2,14}"; public static final String USERNAME_REGEX = "[A-Za-z0-9_\\-一-龥]+"; public static final String ID_REGEX = "[0-9]*"; + + public static final String VERIFY_PRE = "verify_"; }