我们提供安全,免费的手游软件下载!
⭐️基础链接导航⭐️
服务器 → ☁️ 阿里云活动地址
看样例 → ? 摸鱼小网站地址
学代码 → ? 源码库地址
大家好呀,我是summo,最近发生了些事情(被裁员了,在找工作中)导致断更了,非常抱歉。刚被裁的时候还是有些难受,而且我还有房贷要还,有些压力,不过休息了一段时间,心态也平复了一些,打算一边找工作一边写文,如果有和我一样经历的同学,大家共勉!
《花100块做个摸鱼小网站! 》这个系列的前六篇已经大概把整体的流程写完了,从这篇起我会补充一些细节和组件,让我们的小网站更加丰富一些。这一篇呢我会介绍如何将用户的访问记录留下来,看着自己做的网站被别人访问是一件很有意思和很有成就感的事情。
对应的组件也就是我用红框标出来的那个,如下图:
解释下PV和UV的意思,如下:
用于表明一个用户身份最好的做法是做登录注册,但是一旦加了这样的逻辑,就会有很多麻烦的问题要处理,比如如何做人机验证啦、接口防刷啦,等等,这些问题不处理的话网站很容易被攻击。像我们这样的小网站,我觉得这个功能没必要,我们只需要知道有多少人访问过我们的网站就可以了。
对于这样的需求,最简单的做法是根据用户的访问IP作为标识,然后根据IP解析一下地域信息,这样就已经很不错了。而目前最常用的IP解析工具就是: ip2region ,如何使用这个组件我之前写过一篇文章进行介绍了,文章链接: SpringBoot整合ip2region实现使用ip监控用户访问城市 。
核心代码是这段:
package com.example.springbootip.util;
import org.apache.commons.io.FileUtils;
import org.lionsoul.ip2region.xdb.Searcher;
import java.io.File;
import java.text.MessageFormat;
import java.util.Objects;
public class AddressUtil {
/**
* 当前记录地址的本地DB
*/
private static final String TEMP_FILE_DIR = "/home/admin/app/";
/**
* 根据IP地址查询登录来源
*
* @param ip
* @return
*/
public static String getCityInfo(String ip) {
try {
// 获取当前记录地址位置的文件
String dbPath = Objects.requireNonNull(AddressUtil.class.getResource("/ip2region/ip2region.xdb")).getPath();
File file = new File(dbPath);
//如果当前文件不存在,则从缓存中复制一份
if (!file.exists()) {
dbPath = TEMP_FILE_DIR + "ip.db";
System.out.println(MessageFormat.format("当前目录为:[{0}]", dbPath));
file = new File(dbPath);
FileUtils.copyInputStreamToFile(Objects.requireNonNull(AddressUtil.class.getClassLoader().getResourceAsStream("classpath:ip2region/ip2region.xdb")), file);
}
//创建查询对象
Searcher searcher = Searcher.newWithFileOnly(dbPath);
//开始查询
return searcher.searchByStr(ip);
} catch (Exception e) {
e.printStackTrace();
}
//默认返回空字符串
return "";
}
public static void main(String[] args) {
System.out.println(getCityInfo("1.2.3.4"));
}
}
为了解耦逻辑,我使用了一个注解:
@VisitLog
,只要将该注解放在IndexController的index方法上即可。同时为了统计用户的访问数据,我们需要设计一张访问记录表将数据存下来,并设计一个小组件用来展示这些数据,具体流程如下文。
-- `summo-sbmy`.t_sbmy_visit_log definition
CREATE TABLE `t_sbmy_visit_log` (
`id` bigint(20) unsigned zerofill NOT NULL AUTO_INCREMENT COMMENT '物理主键',
`device_type` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '设备类型,手机还是电脑',
`ip` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '访问',
`address` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT 'IP地址',
`time` int DEFAULT NULL COMMENT '耗时',
`method` varchar(2048) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '调用方法',
`params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci COMMENT '参数',
`gmt_create` datetime DEFAULT NULL COMMENT '创建时间',
`gmt_modified` datetime DEFAULT NULL COMMENT '更新时间',
`creator_id` bigint DEFAULT NULL COMMENT '创建人',
`modifier_id` bigint DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=oDEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
还记得我在第三篇介绍的那个DO生成插件吗,在config.properties改下表名和DO名,双击mybatis-generator:generate就可以生成对应的DO、Mapper、xml了。
package com.summo.sbmy.aspect;
/**
* 访问标识注解
*/
public @interface VisitLog {
}
package com.summo.sbmy.aspect.visit;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.summo.sbmy.common.util.AddressUtil;
import com.summo.sbmy.common.util.HttpContextUtil;
import com.summo.sbmy.common.util.IpUtil;
import com.summo.sbmy.dao.entity.SbmyVisitLogDO;
import com.summo.sbmy.dao.repository.SbmyVisitLogRepository;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import static com.summo.sbmy.common.util.DeviceUtil.isFromMobile;
@Slf4j
@Aspect
@Component
public class VisitLogAspect {
@Autowired
private SbmyVisitLogRepository sbmyVisitLogRepository;
@Pointcut("@annotation(com.summo.sbmy.aspect.visit.Log)")
public void pointcut() {
// do nothing
}
@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
//获取request
HttpServletRequest request = HttpContextUtil.getHttpServletRequest();
// 请求的类名
MethodSignature signature = (MethodSignature)joinPoint.getSignature();
Method method = signature.getMethod();
String className = joinPoint.getTarget().getClass().getName();
// 请求的方法名
String methodName = signature.getName();
String ip = IpUtil.getIpAddr(request);
String address = AddressUtil.getAddress(ip);
SbmyVisitLogDO sbmyVisitLogDO = SbmyVisitLogDO.builder().deviceType(isFromMobile(request) ? "手机" : "电脑").method(
className + "." + methodName + "()").ip(ip).address(AddressUtil.getAddress(address)).build();
// 请求的方法参数值
Object[] args = joinPoint.getArgs();
// 请求的方法参数名称
LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
String[] paramNames = u.getParameterNames(method);
if (args != null && paramNames != null) {
// 创建 key-value 映射用于生成 JSON 字符串
Map paramMap = new LinkedHashMap<>();
for (int i = 0; i < paramNames.length; i++) {
if (args[i] instanceof HttpServletRequest || args[i] instanceof HttpServletResponse) {
continue;
}
paramMap.put(paramNames[i], args[i]);
}
// 使用 Fastjson 将参数映射转换为 JSON 字符串
String paramsJson = JSON.toJSONString(paramMap);
sbmyVisitLogDO.setParams(paramsJson);
}
long beginTime = System.currentTimeMillis();
Object proceed = joinPoint.proceed();
long end = System.currentTimeMillis();
sbmyVisitLogDO.setTime((int)(end - beginTime));
sbmyVisitLogRepository.save(sbmyVisitLogDO);
return proceed;
}
/**
* 参数构造器¬
*
* @param params
* @param args
* @param paramNames
* @return
* @throws JsonProcessingException
*/
private StringBuilder handleParams(StringBuilder params, Object[] args, List paramNames)
throws JsonProcessingException {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Map) {
Set set = ((Map)args[i]).keySet();
List
这里使用到了一些工具类,代码我已经上传到仓库了,大家直接down下来就行。
在IndexController.java中加入该注解即可
package com.summo.sbmy.web.controller;
import com.summo.sbmy.aspect.visit.VisitLog;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class IndexController {
@GetMapping("/")
@VisitLog
public String index(){
return "index";
}
}
代码如下:
{{ statsData.todayPv }}
今日 PV
{{ statsData.todayUv }}
今日 UV
{{ statsData.allPv }}
总 PV
{{ statsData.allUv }}
总 UV
在App.vue组件中引入VisitorLog组件,顺便将布局重新分一下,代码如下:
咱们做出来的效果就是这样的,如下:
这个小组件做起来还是简单的,主要就是监控了别人的访问IP,然后通过IP反解析出所属地域,最后将其存到数据库中。
搜狗的热搜接口返回的一串JSON格式数据,这就很简单了,省的我们去解析dom,访问链接是:
https://go.ie.sogou.com/hot_ranks
SougouHotSearchJob.java
package com.summo.sbmy.job.sougou;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.summo.sbmy.common.model.dto.HotSearchDetailDTO;
import com.summo.sbmy.dao.entity.SbmyHotSearchDO;
import com.summo.sbmy.service.SbmyHotSearchService;
import com.summo.sbmy.service.convert.HotSearchConvert;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.stream.Collectors;
import static com.summo.sbmy.common.cache.SbmyHotSearchCache.CACHE_MAP;
import static com.summo.sbmy.common.enums.HotSearchEnum.DOUYIN;
import static com.summo.sbmy.common.enums.HotSearchEnum.SOUGOU;
/**
* @author summo
* @version SougouHotSearchJob.java, 1.0.0
* @description 搜狗热搜Java爬虫代码
* @date 2024年08月09
*/
@Component
@Slf4j
public class SougouHotSearchJob {
@Autowired
private SbmyHotSearchService sbmyHotSearchService;
@XxlJob("sougouHotSearchJob")
public ReturnT hotSearch(String param) throws IOException {
log.info("搜狗热搜爬虫任务开始");
try {
//查询搜狗热搜数据
OkHttpClient client = new OkHttpClient().newBuilder().build();
Request request = new Request.Builder().url("https://go.ie.sogou.com/hot_ranks").method("GET", null)
.build();
Response response = client.newCall(request).execute();
JSONObject jsonObject = JSONObject.parseObject(response.body().string());
JSONArray array = jsonObject.getJSONArray("data");
List sbmyHotSearchDOList = Lists.newArrayList();
for (int i = 0, len = array.size(); i < len; i++) {
//获取知乎热搜信息
JSONObject object = (JSONObject)array.get(i);
//构建热搜信息榜
SbmyHotSearchDO sbmyHotSearchDO = SbmyHotSearchDO.builder().hotSearchResource(SOUGOU.getCode()).build();
//设置知乎三方ID
sbmyHotSearchDO.setHotSearchId(object.getString("id"));
//设置文章标题
sbmyHotSearchDO.setHotSearchTitle(object.getJSONObject("attributes").getString("title"));
//设置文章连接
sbmyHotSearchDO.setHotSearchUrl(
"https://www.sogou.com/web?ie=utf8&query=" + sbmyHotSearchDO.getHotSearchTitle());
//设置热搜热度
sbmyHotSearchDO.setHotSearchHeat(object.getJSONObject("attributes").getString("num"));
//按顺序排名
sbmyHotSearchDO.setHotSearchOrder(i + 1);
sbmyHotSearchDOList.add(sbmyHotSearchDO);
}
if (CollectionUtils.isEmpty(sbmyHotSearchDOList)) {
return ReturnT.SUCCESS;
}
//数据加到缓存中
CACHE_MAP.put(SOUGOU.getCode(), HotSearchDetailDTO.builder()
//热搜数据
.hotSearchDTOList(
sbmyHotSearchDOList.stream().map(HotSearchConvert::toDTOWhenQuery).collect(Collectors.toList()))
//更新时间
.updateTime(Calendar.getInstance().getTime()).build());
//数据持久化
sbmyHotSearchService.saveCache2DB(sbmyHotSearchDOList);
log.info("搜狗热搜爬虫任务结束");
} catch (IOException e) {
log.error("获取搜狗数据异常", e);
}
return ReturnT.SUCCESS;
}
}
热门资讯