websocketSerivce/pom.xml
@@ -126,6 +126,12 @@ <artifactId>logging-interceptor</artifactId> <version>4.9.3</version> </dependency> <!-- IP地址转归属地 --> <dependency> <groupId>org.lionsoul</groupId> <artifactId>ip2region</artifactId> <version>2.6.4</version> </dependency> </dependencies> <build> <finalName>websocketSerivce</finalName> websocketSerivce/src/main/java/org/example/controller/AdminLogin.java
New file @@ -0,0 +1,56 @@ package org.example.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.example.common.ServerResponse; import org.example.pojo.User; import org.example.server.impl.UserServiceImpl; import org.example.util.MD5Util; import org.example.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; 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.security.SecureRandom; import java.util.Base64; import java.util.Date; /** * @program: demo * @description: * @create: 2024-07-29 11:42 **/ @RestController @RequestMapping("/admin") public class AdminLogin { @Autowired private UserServiceImpl userService; @PostMapping("/login") public ServerResponse saveUser(@RequestParam("account") int account , @RequestParam("password") String password) { User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getAccount, account).eq(User::getIsRoot,1)); if(null == user){ return ServerResponse.createBySuccessMsg("管理员账号不存在"); } if (!MD5Util.verify(password, user.getPassword())) { return ServerResponse.createBySuccessMsg("密码错误"); } String token = generateToken(); RedisUtil.set(user.getAccount(),token); return ServerResponse.createBySuccess(token); } // 生成指定长度的随机 token public static String generateToken() { SecureRandom secureRandom = new SecureRandom(); byte[] token = new byte[16]; secureRandom.nextBytes(token); return Base64.getUrlEncoder().withoutPadding().encodeToString(token); } } websocketSerivce/src/main/java/org/example/controller/Login.java
New file @@ -0,0 +1,89 @@ package org.example.controller; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import org.example.common.ServerResponse; import org.example.dao.LogMapper; import org.example.pojo.Log; import org.example.pojo.User; import org.example.server.UserService; import org.example.server.impl.UserServiceImpl; import org.example.util.IpAddressUtil; import org.example.util.MD5Util; import org.example.util.RedisUtil; import org.springframework.beans.factory.annotation.Autowired; 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 javax.servlet.http.HttpServletRequest; import java.security.SecureRandom; import java.util.Base64; import java.util.Date; /** * @program: demo * @description: * @create: 2024-07-29 11:17 **/ @RestController @RequestMapping("/login") public class Login { @Autowired private UserServiceImpl userService; @Autowired private LogMapper logMapper; @PostMapping("/login") public ServerResponse saveUser(@RequestParam("account") String account , @RequestParam("password") String password, HttpServletRequest request) { User user = userService.getOne(new LambdaQueryWrapper<User>().eq(User::getAccount, account)); if(null == user){ return ServerResponse.createBySuccessMsg("用户不存在"); } if (!MD5Util.verify(password, user.getPassword())) { return ServerResponse.createBySuccessMsg("密码错误"); } //判断是否锁定 if(user.getIsLock() == 1){ return ServerResponse.createBySuccessMsg("账号已被锁定"); } //判断是否到期 if(new Date().after(user.getEndTime())){ return ServerResponse.createBySuccessMsg("账号已到期"); } String token = generateToken(); RedisUtil.set(user.getAccount(),token); String ip = IpAddressUtil.getIpAddress(request); String address = null; address = IpAddressUtil.getIpPossessionByFile(ip); if(null == address){ address = IpAddressUtil.getIpAddressByOnline(ip); } Log log = new Log(); log.setIp(ip); log.setAccount(account); log.setLoginTime(new Date()); log.setAddress(address); logMapper.insert(log); return ServerResponse.createBySuccess(token); } // 生成指定长度的随机 token public static String generateToken() { SecureRandom secureRandom = new SecureRandom(); byte[] token = new byte[16]; secureRandom.nextBytes(token); return Base64.getUrlEncoder().withoutPadding().encodeToString(token); } } websocketSerivce/src/main/java/org/example/controller/UserController.java
New file @@ -0,0 +1,71 @@ package org.example.controller; import org.example.common.ServerResponse; import org.example.dao.ConfigCurrencyMapper; import org.example.pojo.ConfigCurrency; import org.example.pojo.User; import org.example.pojo.vo.SaveConfigVo; import org.example.server.impl.UserServiceImpl; import org.example.util.MD5Util; import org.springframework.beans.factory.annotation.Autowired; 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; /** * @program: demo * @description: * @create: 2024-07-29 10:47 **/ @RestController @RequestMapping("/user") public class UserController { @Autowired private ConfigCurrencyMapper currencyMapper; @Autowired private UserServiceImpl userService; @PostMapping("/saveConfig") public ServerResponse saveConfig(SaveConfigVo saveConfigVo) { saveConfigVo.getCurrencyList().forEach(f->{ ConfigCurrency currency = new ConfigCurrency(); currency.setUserId(saveConfigVo.getUserId()); currency.setCurrency(f.getCurrency()); currency.setBuy(f.getBuy()); currency.setSell(f.getSell()); currencyMapper.insert(currency); }); return ServerResponse.createBySuccess(); } @PostMapping("/saveUser") public ServerResponse saveUser(User user) { user.setPassword(MD5Util.encrypt(user.getPassword())); userService.save(user); return ServerResponse.createBySuccess(); } @PostMapping("/deleteUser") public ServerResponse deleteUser(@RequestParam("id") int id) { User byId = userService.getById(id); if(null == byId){ return ServerResponse.createByErrorMsg("用户不存在"); } userService.removeById(id); return ServerResponse.createBySuccess(); } @PostMapping("/updateUser") public ServerResponse deleteUser(User user) { User byId = userService.getById(user.getId()); if(null == byId){ return ServerResponse.createByErrorMsg("用户不存在"); } user.setPassword(MD5Util.encrypt(user.getPassword())); userService.updateById(user); return ServerResponse.createBySuccess(); } } websocketSerivce/src/main/java/org/example/dao/ConfigCurrencyMapper.java
New file @@ -0,0 +1,16 @@ package org.example.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.example.pojo.ConfigCurrency; import org.example.pojo.vo.SaveConfigVo; /** * @program: demo * @description: * @create: 2024-07-29 10:46 **/ @Mapper public interface ConfigCurrencyMapper extends BaseMapper<ConfigCurrency> { } websocketSerivce/src/main/java/org/example/dao/LogMapper.java
New file @@ -0,0 +1,16 @@ package org.example.dao; /** * @program: demo * @description: * @create: 2024-07-29 11:46 **/ import com.baomidou.mybatisplus.core.mapper.BaseMapper; import lombok.Data; import org.apache.ibatis.annotations.Mapper; import org.example.pojo.Log; @Mapper public interface LogMapper extends BaseMapper<Log> { } websocketSerivce/src/main/java/org/example/dao/serServiceMapper.java
New file @@ -0,0 +1,10 @@ package org.example.dao; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.example.pojo.User; @Mapper public interface serServiceMapper extends BaseMapper<User> { } websocketSerivce/src/main/java/org/example/pojo/ConfigCurrency.java
New file @@ -0,0 +1,23 @@ package org.example.pojo; import lombok.Data; import org.example.pojo.vo.SaveConfigVo; import java.util.List; /** * @program: demo * @description: * @create: 2024-07-29 10:46 **/ @Data public class ConfigCurrency { private Integer userId; private String currency; private String buy; private String sell; } websocketSerivce/src/main/java/org/example/pojo/Log.java
New file @@ -0,0 +1,27 @@ package org.example.pojo; import lombok.Data; import java.util.Date; /** * @program: demo * @description: * @create: 2024-07-29 11:45 **/ @Data public class Log { private Integer id; private String account; private Date loginTime; private String ip; private String address; } websocketSerivce/src/main/java/org/example/pojo/User.java
New file @@ -0,0 +1,63 @@ package org.example.pojo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import lombok.Data; import java.sql.Date; /** * @program: demo * @description: * @create: 2024-07-29 11:06 **/ @Data public class User { @TableId(type = IdType.AUTO) /** * id */ private Integer id; /** * 账号 */ private String account; /** * 密码 */ private String password; /** * 是否管理员 0 否 1 是 */ private Integer isRoot; /** * 添加时间 */ private Date addTime; /** * 到期时间 */ private Date endTime; /** * 是否显示 0 是 1 否 */ private Integer isShow; /** * 是否锁定 0 否 1 是 */ private Integer isLock; /** * 设备数量 */ private Integer deviceNumber; } websocketSerivce/src/main/java/org/example/pojo/vo/SaveConfigVo.java
New file @@ -0,0 +1,28 @@ package org.example.pojo.vo; import lombok.Data; import org.example.pojo.ConfigCurrency; import java.util.List; /** * @program: demo * @description: * @create: 2024-07-29 10:40 **/ @Data public class SaveConfigVo { private Integer userId; private List<ConfigCurrencyVo> currencyList; @Data public class ConfigCurrencyVo{ private String currency; private String buy; private String sell; } } websocketSerivce/src/main/java/org/example/server/UserService.java
New file @@ -0,0 +1,9 @@ package org.example.server; /** * @program: demo * @description: * @create: 2024-07-29 11:08 **/ public interface UserService { } websocketSerivce/src/main/java/org/example/server/impl/UserServiceImpl.java
New file @@ -0,0 +1,19 @@ package org.example.server.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.example.dao.CurrencyMapper; import org.example.dao.serServiceMapper; import org.example.pojo.Currency; import org.example.pojo.User; import org.example.server.CurrencySerivce; import org.example.server.UserService; import org.springframework.stereotype.Service; /** * @program: demo * @description: * @create: 2024-07-29 11:08 **/ @Service public class UserServiceImpl extends ServiceImpl<serServiceMapper, User> implements UserService { } websocketSerivce/src/main/java/org/example/util/IpAddressUtil.java
New file @@ -0,0 +1,259 @@ package org.example.util; import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.lionsoul.ip2region.xdb.Searcher; import org.springframework.http.HttpHeaders; import org.springframework.http.server.ServerHttpRequest; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeUnit; /** * @program: demo * @description: * @create: 2024-07-29 11:50 **/ @Slf4j public class IpAddressUtil { // ip2region.xdb 文件地址常量(本地xdb文件路径) public static String XDB_PATH = "D:\\项目\\demo\\websocketSerivce\\src\\main\\resources\\ip\\csdn-ip2region.xdb"; /** * 获取IP地址: */ public static String getIpAddress(HttpServletRequest request) { String ipAddress = null; try { ipAddress = request.getHeader("X-Forwarded-For"); if (ipAddress != null && ipAddress.length() != 0 && !"unknown".equalsIgnoreCase(ipAddress)) { // 多次反向代理后会有多个ip值,第一个ip才是真实ip if (ipAddress.contains(",")) { ipAddress = ipAddress.split(",")[0]; } } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("WL-Proxy-Client-IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getHeader("HTTP_CLIENT_IP"); } if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { ipAddress = request.getRemoteAddr(); } } catch (Exception e) { log.error("获取IP地址异常,{}", e.getMessage()); } return ipAddress; } /** * 获取mac地址 */ public static String getMacIpAddress() { try { InetAddress inetAddress = InetAddress.getLocalHost(); byte[] macAddressBytes = NetworkInterface.getByInetAddress(inetAddress).getHardwareAddress(); // 将mac地址拼装成String StringBuilder sb = new StringBuilder(); for (int i = 0; i < macAddressBytes.length; i++) { if (i != 0) { sb.append("-"); } // mac[i] & 0xFF 是为了把byte转化为正整数 String s = Integer.toHexString(macAddressBytes[i] & 0xFF); sb.append(s.length() == 1 ? 0 + s : s); } return sb.toString().trim().toUpperCase(); } catch (Exception e) { log.error("Mac获取IP地址异常,{}", e.getMessage()); } return ""; } /** * 方法一:完全基于ip2region.xdb文件,对用户ip地址进行转换 * 注:并发调用时,每个线程需创建一个独立的searcher对象 单独使用。 */ public static String getIpPossessionByFile(String ip) { if (StringUtils.isNotEmpty(ip)) { try { // 1、创建 searcher 对象 Searcher searcher = Searcher.newWithFileOnly(XDB_PATH); // 2、查询 long sTime = System.nanoTime(); String region = searcher.search(ip); long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime); region = region.replace("|0", ""); //log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("获取IP地址异常:{} ", e.getMessage()); throw new RuntimeException("获取IP地址异常"); } } return "未知"; } /** * 方法二:缓存 VectorIndex 索引,对用户ip地址进行转换 * 注:每个线程需要单独创建一个独立的 Searcher 对象,但是都共享全局变量 vIndex 缓存。 */ public static String getCityInfoByVectorIndex(String ip) { if (StringUtils.isNotEmpty(ip)) { try { // 1、从 XDB_PATH 中预先加载 VectorIndex 缓存,并且作为全局变量,后续反复使用。 byte[] vIndex = Searcher.loadVectorIndexFromFile(XDB_PATH); // 2、使用全局的 vIndex 创建带 VectorIndex 缓存的查询对象。 Searcher searcher = Searcher.newWithVectorIndex(XDB_PATH, vIndex); // 3、查询 long sTime = System.nanoTime(); String region = searcher.search(ip); long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime); region = region.replace("|0", ""); //log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("获取IP地址异常:{} ", e.getMessage()); throw new RuntimeException("获取IP地址异常"); } } return "未知"; } /** * 方法三:缓存整个 xdb 数据,对用户ip地址进行转换 * 注:并发使用时,用整个 xdb 数据缓存创建的查询对象可以安全的用于并发,也就是你可以把这个 searcher 对象做成全局对象去跨线程访问。 */ public static String getCityInfoByMemorySearch(String ip) { if (StringUtils.isNotEmpty(ip)) { try { // 1、从 XDB_PATH 加载整个 xdb 到内存。 byte[] cBuff = Searcher.loadContentFromFile(XDB_PATH); // 2、使用上述的 cBuff 创建一个完全基于内存的查询对象。 Searcher searcher = Searcher.newWithBuffer(cBuff); // 3、查询 long sTime = System.nanoTime(); String region = searcher.search(ip); long cost = TimeUnit.NANOSECONDS.toMicros(System.nanoTime() - sTime); region = region.replace("|0", ""); //log.info("{地区: {}, IO操作数: {}, 耗时: {} μs}", region, searcher.getIOCount(), cost); return region; } catch (Exception e) { log.error("获取IP地址异常:{} ", e.getMessage()); throw new RuntimeException("获取IP地址异常"); } } return "未知"; } /** * 方法四:在线获取IP地址 * 注:通过别人或者官网提供的API接口去实现查询的功能,弊端就是特别依赖别人的服务器,一旦服务器宕机就无法访问了。 */ public static String getIpAddressByOnline(String ip) { try { //1、创建 URLConnction URL url = new URL("http://ip-api.com/json/" + ip + "?lang=zh-CN"); //2、设置connection的属性 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setRequestMethod("GET"); connection.setConnectTimeout(20000); connection.setReadTimeout(20000); connection.setRequestProperty("content-type", "application/json; charset=utf-8"); //3.连接 connection.connect(); //4.获取内容 InputStream inputStream = connection.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); String line; StringBuilder sb = new StringBuilder(); while ((line = br.readLine()) != null) { sb.append(line); } br.close(); //System.out.println(sb); String str = sb.toString(); if (StringUtils.isNotEmpty(str)) { // string转map Gson gson = new Gson(); Map<String, Object> map = new HashMap<>(); map = gson.fromJson(str, map.getClass()); String country = (String) map.get("country"); String city = (String) map.get("city"); String regionName = (String) map.get("regionName"); //log.info("【国家】{},【城市】{},【地区】{}", country, city, regionName); return country + "|" + city + "|" + regionName; } } catch (Exception e) { log.error("在线查询IP地址异常,{}", e.getMessage()); throw new RuntimeException("在线查询IP地址异常"); } return null; } /** * 根据IP地址 获取归属地 */ public static String getIpPossession(String ipAddress) { if (StringUtils.isNotEmpty(ipAddress)) { ipAddress = ipAddress.replace("|", " "); String[] cityList = ipAddress.split(" "); if (cityList.length > 0) { // 国内的显示到具体的省 if ("中国".equals(cityList[0])) { if (cityList.length > 1) { return cityList[1]; } } // 国外显示到国家 return cityList[0]; } } return "未知"; } public static void main(String[] args) { String ip = "183.162.252.0";// 国内IP String abroadIp = "48.119.248.100"; // 国外IP System.out.println("方法一(国内):" + getIpPossessionByFile(ip)); System.out.println("方法二(国内):" + getCityInfoByVectorIndex(ip)); System.out.println("方法三(国内):" + getCityInfoByMemorySearch(ip)); System.out.println("方法四(国内):" + getIpAddressByOnline(ip)); System.out.println("方法一(国外):" + getIpPossessionByFile(abroadIp)); System.out.println("方法二(国外):" + getCityInfoByVectorIndex(abroadIp)); System.out.println("方法三(国外):" + getCityInfoByMemorySearch(abroadIp)); System.out.println("方法四(国外):" + getIpAddressByOnline(abroadIp)); //System.out.println("归属地(国内):" + getIpPossession(getCityInfoByVectorIndex(ip))); //System.out.println("归属地(国外):" + getIpPossession(getCityInfoByVectorIndex(abroadIp))); } } websocketSerivce/src/main/java/org/example/util/LoginInterceptor.java
New file @@ -0,0 +1,43 @@ package org.example.util; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; /** * @program: demo * @description: * @create: 2024-07-29 11:15 **/ public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); // 假设登录状态信息保存在 session 中,这里简单示例,实际项目中可能会有自己的登录状态判断逻辑 if (session.getAttribute("loggedInUser") != null) { // 已登录,允许请求继续 return true; } else { // 未登录,返回未授权错误码或重定向到登录页 response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); // 返回 401 未授权状态码 return false; } } @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { } } websocketSerivce/src/main/java/org/example/util/MD5Util.java
New file @@ -0,0 +1,50 @@ package org.example.util; import java.math.BigInteger; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; /** * @program: demo * @description: * @create: 2024-07-29 11:22 **/ public class MD5Util { // 使用MD5算法对密码进行加密 public static String encrypt(String password) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] messageDigest = md.digest(password.getBytes()); BigInteger no = new BigInteger(1, messageDigest); StringBuilder hashText = new StringBuilder(no.toString(16)); while (hashText.length() < 32) { hashText.insert(0, "0"); } return hashText.toString(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); return null; } } // 校验输入的密码和加密后的密码是否匹配 public static boolean verify(String inputPassword, String hashedPassword) { String hashedInputPassword = encrypt(inputPassword); return hashedInputPassword.equals(hashedPassword); } // 示例用法 public static void main(String[] args) { String originalPassword = "myPassword123"; String hashedPassword = encrypt(originalPassword); // 模拟校验过程 String inputPassword = "myPassword123"; if (verify(inputPassword, hashedPassword)) { System.out.println("Password Matched!"); } else { System.out.println("Password Not Matched!"); } } } websocketSerivce/src/main/java/org/example/util/RedisUtil.java
@@ -90,6 +90,24 @@ } } // 设置 Token 的过期时间 public void setTokenExpiration(String token, int expirationInSeconds) { try (Jedis jedis = jedisPool.getResource()) { jedis.expire(token, expirationInSeconds); } } // 更新 Token 的过期时间 public void updateTokenExpiration(String token, int expirationInSeconds) { try (Jedis jedis = jedisPool.getResource()) { if (jedis.exists(token)) { jedis.expire(token, expirationInSeconds); } } } public static void disconnect() { jedisPool.close(); } websocketSerivce/src/main/java/org/example/util/WebMvcConfig.java
New file @@ -0,0 +1,21 @@ package org.example.util; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * @program: demo * @description: * @create: 2024-07-29 11:15 **/ @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") // 拦截所有请求 .excludePathPatterns("/login", "/admin/login","/logout"); // 排除登录页和登出操作,防止死循环 } } websocketSerivce/src/main/java/org/example/websocket/controller/ReceptionController.java
@@ -2,6 +2,7 @@ import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import org.example.pojo.vo.SaveConfigVo; import org.example.websocket.server.WsServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; websocketSerivce/src/main/resources/ip/csdn-ip2region.xdbBinary files differ