├── README.md ├── pom.xml └── src ├── main ├── java │ ├── META-INF │ │ └── MANIFEST.MF │ └── com │ │ └── ufan0 │ │ └── whereishe │ │ ├── WhereIsHeApplication.java │ │ ├── config │ │ └── WebSocketConfig.java │ │ ├── controller │ │ ├── ErrorController.java │ │ └── IndexController.java │ │ └── util │ │ └── IpUtil.java └── resources │ ├── application.yml │ └── templates │ └── index.html └── test └── java └── com └── ufan0 └── whereishe └── WhereIsHeApplicationTests.java /README.md: -------------------------------------------------------------------------------- 1 | - # where-is-he 2 | 我的他她它在哪儿? 3 | 4 | 我的他她它在哪儿?在干嘛?大半夜的说在家其实是去和别人约会了? 5 | 6 | 本服务基于Spring Boot构建,旨在作为中间者,为提供用户一个临时地址,获取访问者信息给用户。 7 | 8 | 在线Demo: ~~https://overflow.fun/mask/where-is-he~~ 9 | 10 | 通知方式已由邮箱替换成页面提示,基于WebSocket实现。 11 | 12 | 待完成: 13 | 14 | - 改善地址加密算法 15 | - 整合进IP查询接口 16 | - 完善地址页面伪装 17 | - 生成可发布jar 18 | - 获取GPS信息 19 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.2.5.RELEASE 9 | 10 | 11 | com.ufan0 12 | where-is-he 13 | 0.0.1-SNAPSHOT 14 | where-is-he 15 | where is he? 16 | 17 | 18 | 11 19 | 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-thymeleaf 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-mail 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-tomcat 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-undertow 44 | 45 | 46 | org.springframework.boot 47 | spring-boot-starter-test 48 | test 49 | 50 | 51 | org.junit.vintage 52 | junit-vintage-engine 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-websocket 60 | 61 | 62 | org.webjars 63 | webjars-locator-core 64 | 65 | 66 | org.webjars 67 | sockjs-client 68 | 1.0.2 69 | 70 | 71 | org.webjars 72 | stomp-websocket 73 | 2.3.3 74 | 75 | 76 | org.webjars 77 | bootstrap 78 | 5.0.0 79 | 80 | 81 | org.webjars 82 | jquery 83 | 3.1.1-1 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | org.springframework.boot 92 | spring-boot-maven-plugin 93 | 94 | 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /src/main/java/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Main-Class: com.ufan0.whereishe.WhereIsHeApplication 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/com/ufan0/whereishe/WhereIsHeApplication.java: -------------------------------------------------------------------------------- 1 | package com.ufan0.whereishe; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.web.servlet.config.annotation.InterceptorRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @SpringBootApplication 9 | public class WhereIsHeApplication{ 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(WhereIsHeApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/ufan0/whereishe/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.ufan0.whereishe.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 6 | import org.springframework.web.socket.WebSocketHandler; 7 | import org.springframework.web.socket.config.annotation.*; 8 | 9 | @Configuration 10 | // 启用WebSocket 11 | @EnableWebSocketMessageBroker 12 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 13 | 14 | @Override 15 | public void configureMessageBroker(MessageBrokerRegistry config) { 16 | config.enableSimpleBroker("/topic"); 17 | config.setApplicationDestinationPrefixes("/app"); 18 | } 19 | 20 | @Override 21 | public void registerStompEndpoints(StompEndpointRegistry registry) { 22 | // 4.2.5. Allowed Origins https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#websocket 23 | registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/ufan0/whereishe/controller/ErrorController.java: -------------------------------------------------------------------------------- 1 | package com.ufan0.whereishe.controller; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.bind.annotation.ExceptionHandler; 9 | import org.springframework.web.bind.annotation.ResponseBody; 10 | import org.springframework.web.bind.annotation.ResponseStatus; 11 | 12 | @ControllerAdvice 13 | public class ErrorController { 14 | 15 | private static Logger logger = LoggerFactory.getLogger(ErrorController.class); 16 | 17 | @ResponseBody 18 | @ExceptionHandler(Throwable.class) 19 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 20 | public String exception(final Throwable throwable, final Model model) { 21 | logger.error("Exception during execution of SpringSecurity application", throwable); 22 | 23 | return "要天天开心噢!"; 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/main/java/com/ufan0/whereishe/controller/IndexController.java: -------------------------------------------------------------------------------- 1 | package com.ufan0.whereishe.controller; 2 | 3 | import com.ufan0.whereishe.util.IpUtil; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.messaging.simp.SimpMessageSendingOperations; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.util.Base64Utils; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.PostMapping; 14 | import org.springframework.web.bind.annotation.ResponseBody; 15 | import org.springframework.web.servlet.ModelAndView; 16 | import org.springframework.web.servlet.view.RedirectView; 17 | 18 | import javax.servlet.http.HttpServletRequest; 19 | import java.nio.charset.StandardCharsets; 20 | 21 | @Controller 22 | public class IndexController { 23 | 24 | private final String MARK = "where-is-he"; 25 | 26 | private static Logger logger = LoggerFactory.getLogger(IndexController.class); 27 | 28 | @Autowired 29 | private IpUtil ipUtil; 30 | 31 | @Autowired 32 | private SimpMessageSendingOperations simpMessageSendingOperations; 33 | 34 | /** 35 | * @return 对页面进行伪装,防止猎奇心理用户发现 36 | */ 37 | @ResponseBody 38 | @GetMapping({""}) 39 | public String index() { 40 | return "对不起,页面发生错误。"; 41 | } 42 | 43 | /** 44 | * @param encode 访问路径 45 | * @param request 获取IP地址必须 46 | * @return 重定向到伪装地址 47 | * 处理GET请求,判断请求类型 1-访问主页面、2-访问伪装地址、3-无效访问 48 | * 2、3类型都将重定向到伪装地址 49 | */ 50 | @GetMapping("/mask/{encode}") 51 | public Object getUserInfo(@PathVariable String encode, HttpServletRequest request) throws InterruptedException { 52 | 53 | // 功能页面入口 54 | if (encode.equals("where-is-he")) { 55 | return "index"; 56 | } 57 | // 非正确访问的话,站点主要功能当然不能暴露出来啦!跳转到其他网址,伪装一下 58 | else { 59 | String[] vars = null; 60 | // 解析传来的encode参数 61 | if (encode != null && !encode.equals("")) { 62 | // 拿到访问者IP 63 | String ip = ipUtil.getIpAddr(request); 64 | 65 | // 进行BASE64解码 66 | // String encodeReverse = String.valueOf(new StringBuilder(encode).reverse()); 67 | String decode = new String(Base64Utils.decodeFromUrlSafeString(encode), StandardCharsets.UTF_8); 68 | 69 | // 字符串切割得到vars[0]为标记MARK,vars[1]为用户伪装url,vars[2]为时间戳 70 | vars = decode.split("#"); 71 | 72 | // 利用MARK标记和时间戳进行地址有效校验,decode[0] == MARK且时差3分钟内才是有效链接 73 | if (vars[0].equals(MARK) && System.currentTimeMillis() - Long.parseLong(vars[2]) < 1000 * 60 * 3) { 74 | Thread.sleep(1000); 75 | // 传递访问者ip至监控页面 76 | simpMessageSendingOperations.convertAndSend( 77 | "/topic/user/" + new StringBuilder(encode).reverse(), ip 78 | ); 79 | } 80 | } 81 | assert vars != null; 82 | // 当用户伪装地址不为空时返回用户伪装地址,否则跳转到百度 83 | return new ModelAndView(new RedirectView( 84 | (vars[1] == null || vars[1].equals("")) 85 | ? "https://baidu.com/" 86 | : vars[1]) 87 | ); 88 | } 89 | } 90 | 91 | /** 92 | * @param url 用户传来的自定义伪装地址 93 | * @return 返回反转后的encode作为用户查看ip信息的接口地址 94 | */ 95 | @ResponseBody 96 | @PostMapping("/mask/where-is-he") 97 | public String sentInfo(String url) { 98 | 99 | // 伪装地址日志 100 | logger.info(url); 101 | 102 | // 当前时间戳 103 | String now = String.valueOf(System.currentTimeMillis()); 104 | 105 | // 生成自定义地址 106 | // BASE64("mark(判断合法地址)" + "#" + "用户定义的伪装url" + "#" + "时间戳(校验连接是否有效)") 107 | return Base64Utils.encodeToUrlSafeString((MARK + "#" + url + "#" + now).getBytes()); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/ufan0/whereishe/util/IpUtil.java: -------------------------------------------------------------------------------- 1 | package com.ufan0.whereishe.util; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import java.net.InetAddress; 7 | import java.net.UnknownHostException; 8 | 9 | @Component 10 | public class IpUtil { 11 | public String getIpAddr(HttpServletRequest request) { 12 | String ipAddress = null; 13 | try { 14 | ipAddress = request.getHeader("x-forwarded-for"); 15 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 16 | ipAddress = request.getHeader("Proxy-Client-IP"); 17 | } 18 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 19 | ipAddress = request.getHeader("WL-Proxy-Client-IP"); 20 | } 21 | if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) { 22 | ipAddress = request.getRemoteAddr(); 23 | if (ipAddress.equals("127.0.0.1")) { 24 | // 根据网卡取本机配置的IP 25 | InetAddress inet = null; 26 | try { 27 | inet = InetAddress.getLocalHost(); 28 | } catch (UnknownHostException e) { 29 | e.printStackTrace(); 30 | } 31 | ipAddress = inet.getHostAddress(); 32 | } 33 | } 34 | // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割 35 | if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length() 36 | // = 15 37 | if (ipAddress.indexOf(",") > 0) { 38 | ipAddress = ipAddress.substring(0, ipAddress.indexOf(",")); 39 | } 40 | } 41 | } catch (Exception e) { 42 | ipAddress=""; 43 | } 44 | return ipAddress; 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | mail: 3 | host: smtp.sina.cn 4 | username: 1223334444qwe@sina.cn 5 | password: 8c7b8617907edbe7 6 | default-encoding: UTF-8 7 | 8 | mail: 9 | fromMail: 10 | addr: 1223334444qwe@sina.cn -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 我的她他它在哪儿? 7 | 8 | 9 | 10 | God bless! 11 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 28 |
29 |

30 |
31 |
32 | 33 |
34 |

我的他她它在哪儿?

35 |
36 |
37 |
38 |

39 | 最近认识一个姑娘,说她家在塔克拉玛干,那边阳光充足, 40 | 并且家里在那儿种有茶叶,长势喜人,质量贼高! 41 | 但是因为疫情无法销售出去,让我帮帮她。 42 | 我这好心肠哪能弃之不顾啊,但是为了防止被骗, 43 | 我便设计了本工具,去拿到她的IP地址以定位, 44 | 确定她是不是真的在塔克拉玛干。 45 |

46 |
47 |
48 |
49 |
50 |

使用说明

51 | 52 |
Tips: 服务器端不存储任何数据,源码已经公布在Github,请点击本消息查看;
53 |
54 |
55 |
  • 56 | 自定义跳转地址以生成临时访问链接,临时访问链接会重定向到用户预先设置的跳转地址,有效期3分钟; 57 |
  • 58 |
  • 59 | 临时访问链接每次被访问,服务器都会将访问者IP发送到到当前页面,请勿刷新页面,否则临时链接失效; 60 |
  • 61 |
  • 62 | 若正确操作,未出现预期结果,很可能是服务器宕掉,请自行获取源码搭建使用; 63 |
  • 64 |
  • 65 | 为了保证本页面能够正常演示,请爱惜服务器资源,切勿重复提交数据,谢谢; 66 |
  • 67 |
    68 |
    69 |
    70 |
    71 | 72 | 73 |
    74 |
    75 |
    76 | 77 |
    78 |
    79 |
    80 |
    81 |
    82 |
    83 | 84 | 87 |
    88 |
    89 |
    90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
    IP
    99 |
    100 |
    101 |
    102 |
    103 |
    104 |
    105 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /src/test/java/com/ufan0/whereishe/WhereIsHeApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ufan0.whereishe; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class WhereIsHeApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | --------------------------------------------------------------------------------