实现 PV、UV、IP 日统计
实现PV、UV、IP统计
引用:https://yzhyaa.blog.csdn.net/article/details/113821594?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-113821594-blog-108728655.pc_relevant_downloadblacklistv1&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1-113821594-blog-108728655.pc_relevant_downloadblacklistv1&utm_relevant_index=2
管理端要求统计网站每日、每小时的
PV:浏览次数
UV:独立访客(客户端数量)
IP:访问的 IP 数量(公网 IP) --自己没有使用到该模块,未实现
PS:UV 是客户机数量,IP 是指的公网 IP 数量,一个公网 IP 的局域网内可能有多个主机,所以 IP >= UV。
我们将这三个数据到先存到 redis 中,然后每天落库一次。
public abstract class WebsiteRedisRole {
// PV 的 redis key(String 结构)
public static final String PREFIX_KEY_PV= "website:pv";
// UV 的 redis key(String 结构)
public static final String PREFIX_KEY_UV="website:uv";
// IP 的 redis key(Set 结构,保存所有 IP)
public static final String PREFIX_KEY_IPS = "website:ips";
}
1.统计 PV
统计 PV 其实很简单,写一个拦截器,只要有任何请求进来,就将 redis 中 保存的 PV++
@Component
public class PvInterceptor extends HandlerInterceptorAdapter {
@Autowired
private WebsiteService websiteService;
// 只要有请求进来,就将 PV++
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,Object handler){
// redis key 为 website:pv
this.websiteService.insertVisit(WebsiteRedisRole.PREFIX_KEY_PV);
return true;
}
}
WebsiteService#insertVisit()
package com.craftsman.modules.statistical;
import com.alibaba.druid.util.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.craftsman.modules.website.dao.WebsiteDao;
import com.craftsman.modules.website.entity.Website;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@Component
public class WebsiteService {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private WebsiteDao websiteDao;
public void insertVisit(String key){
// 判断 redis 中是否存在当前 key
if(!redisTemplate.hasKey(key)){
// 不存在就初始化为 0
redisTemplate.opsForValue().set(key,"0");
}
// count++,然后重新放回 redis
Integer count = Integer.parseInt(this.redisTemplate.opsForValue().get(key));
count++;
this.redisTemplate.opsForValue().set(key,count.toString());
}
public void insertAllToDb(Set<String> keys) {
// 将 redis 这些 key 的保存的数据保存到 map 中
Map<String,Integer> map = new HashMap<>();
keys.forEach(k ->{
// 如果 key 是 website:ips,即保存所有 ip 的 Set
// 那么,落库时保存的是 Set 的 size
if(StringUtils.equals(WebsiteRedisRole.PREFIX_KEY_IPS,k)){
map.put(k,this.redisTemplate.opsForSet().size(k).intValue());
// 其余 key 保存的是 String,则取出值并转 int 就行
}else {
map.put(k,Integer.parseInt(redisTemplate.opsForValue().get(k)));
}
});
// 构建 WebSite 对象
Website website = new Website();
website.setWebPv(map.get(WebsiteRedisRole.PREFIX_KEY_PV));
website.setWebUv(map.get(WebsiteRedisRole.PREFIX_KEY_UV));
website.setWebIp(map.get(WebsiteRedisRole.PREFIX_KEY_IPS));
website.setWebId(IdWorker.getId());
// 设置当前当前数据的时间
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
String format = simpleDateFormat.format(new Date());
try {
website.setSchedule(simpleDateFormat.parse(format));
} catch (ParseException e) {
e.printStackTrace();
}
// 落库
// this.websiteDao.insertSelective(website);
this.websiteDao.insert(website);
}
}
拦截器定义好,不能够直接生效,需要注册拦截器,另外注意拦截器的顺序以及拦截器的过滤规则
package com.craftsman.common.config;
import com.craftsman.common.aspect.RedisSessionInterceptor;
import com.craftsman.modules.statistical.PvInterceptor;
import com.craftsman.modules.statistical.UvInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
*
* @Description:拦截器配置
* @version 1.0
*/
@SuppressWarnings("deprecation")
@Configuration
public class WebSecurityConfig extends WebMvcConfigurerAdapter {
@Bean
public PvInterceptor getPvInterceptor() {
return new PvInterceptor();
}
@Bean
public UvInterceptor getUvInterceptor() {
return new UvInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(getUvInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/error/**")
.excludePathPatterns("/statics/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/sys/login")
.excludePathPatterns("/meteorology/**")
.excludePathPatterns("/emer/openApi/**")
.excludePathPatterns("/favicon.ico");
super.addInterceptors(registry);
registry.addInterceptor(getPvInterceptor()).addPathPatterns("/**")
.excludePathPatterns("/error/**")
.excludePathPatterns("/statics/**")
.excludePathPatterns("/login.html")
.excludePathPatterns("/sys/login")
.excludePathPatterns("/meteorology/**")
.excludePathPatterns("/emer/openApi/**")
.excludePathPatterns("/favicon.ico");
super.addInterceptors(registry);
}
}
2.统计 UV
统计 UV 相较 PV 更复杂一点,为了标识每一个主机,我们通过 cookie 给每个主机都发放一个唯一 ID
注意事项:①VISIT_COOKIE_NAME根据自己实际业务场景填入自己业务的名称
②cookieValue根据自己的情况,写入符合自己规则的ID
package com.craftsman.modules.statistical;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class UvInterceptor extends HandlerInterceptorAdapter {
public static final String VISIT_COOKIE_NAME = "zj_gov_session"; // cookie 名
@Autowired
private WebsiteService websiteService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
// 获取请求中携带的 visitId
String visitId = CookieUtils.getCookieValue(request, VISIT_COOKIE_NAME);
// 如果用户机中已经有 visitId 了,那么说明今天已经记录过该客户机的访问了
if(StringUtils.isNotBlank(visitId)){
// THREAD_LOCAL.set(CookieUtils.getCookieValue(request, VISIT_COOKIE_NAME));
return true;
}
// 如果该用户机还没有 visitId
// 那么,通过 UUID 生成一个随机 IDD
// String cookieValue = UUIDUtils.getUUID32();
String cookieValue = IdWorker.get32UUID();
// 将 visitId 通过 cookie 发送到用户机
// 注:由于我们是每日都统计 UV,所以这个 cookie 的过期时间应该是当日的 23:59:59
CookieUtils.setCookie(request,response,VISIT_COOKIE_NAME,cookieValue,
CookieDeathUtils.getCookieDeath());
// UV++(redis key 为 website:uv)
this.websiteService.insertVisit(WebsiteRedisRole.PREFIX_KEY_UV);
return true;
}
}
CookieUtils类的实现:
```java
package com.craftsman.modules.statistical;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
public final class CookieUtils {
final static Logger logger = LoggerFactory.getLogger(CookieUtils.class);
/**
*
* @Description: 得到Cookie的值, 不编码
* @param request
* @param cookieName
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName) {
return getCookieValue(request, cookieName, false);
}
/**
*
* @Description: 得到Cookie的值
* @param request
* @param cookieName
* @param isDecoder
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null) {
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
if (isDecoder) {
retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");
} else {
retValue = cookieList[i].getValue();
}
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/**
*
* @Description: 得到Cookie的值
* @param request
* @param cookieName
* @param encodeString
* @return
*/
public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {
Cookie[] cookieList = request.getCookies();
if (cookieList == null || cookieName == null) {
return null;
}
String retValue = null;
try {
for (int i = 0; i < cookieList.length; i++) {
if (cookieList[i].getName().equals(cookieName)) {
retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);
break;
}
}
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return retValue;
}
/**
*
* @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码
* @param request
* @param response
* @param cookieName
* @param cookieValue
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue) {
setCookie(request, response, cookieName, cookieValue, -1);
}
/**
*
* @Description: 设置Cookie的值 在指定时间内生效,但不编码
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param cookieMaxage
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage) {
setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);
}
/**
*
* @Description: 设置Cookie的值 不设置生效时间,但编码
* 在服务器被创建,返回给客户端,并且保存客户端
* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中
* 如果没有设置,会默认把cookie保存在浏览器的内存中
* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param isEncode
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, boolean isEncode) {
setCookie(request, response, cookieName, cookieValue, -1, isEncode);
}
/**
*
* @Description: 设置Cookie的值 在指定时间内生效, 编码参数
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param cookieMaxage
* @param isEncode
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, boolean isEncode) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);
}
/**
*
* @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param cookieMaxage
* @param encodeString
*/
public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,
String cookieValue, int cookieMaxage, String encodeString) {
doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);
}
/**
*
* @Description: 删除Cookie带cookie域名
* @param request
* @param response
* @param cookieName
*/
public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName) {
doSetCookie(request, response, cookieName, null, -1, false);
// doSetCookie(request, response, cookieName, "", -1, false);
}
/**
*
* @Description: 设置Cookie的值,并使其在指定时间内生效
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param cookieMaxage cookie生效的最大秒数
* @param isEncode
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {
try {
if (cookieValue == null) {
cookieValue = "";
} else if (isEncode) {
cookieValue = URLEncoder.encode(cookieValue, "utf-8");
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request) {// 设置域名的cookie
String domainName = getDomainName(request);
logger.info("========== domainName: {} ==========", domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* @Description: 设置Cookie的值,并使其在指定时间内生效
* @param request
* @param response
* @param cookieName
* @param cookieValue
* @param cookieMaxage cookie生效的最大秒数
* @param encodeString
*/
private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,
String cookieName, String cookieValue, int cookieMaxage, String encodeString) {
try {
if (cookieValue == null) {
cookieValue = "";
} else {
cookieValue = URLEncoder.encode(cookieValue, encodeString);
}
Cookie cookie = new Cookie(cookieName, cookieValue);
if (cookieMaxage > 0)
cookie.setMaxAge(cookieMaxage);
if (null != request) {// 设置域名的cookie
String domainName = getDomainName(request);
logger.info("========== domainName: {} ==========", domainName);
if (!"localhost".equals(domainName)) {
cookie.setDomain(domainName);
}
}
cookie.setPath("/");
response.addCookie(cookie);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*
* @Description: 得到cookie的域名
* @return
*/
private static final String getDomainName(HttpServletRequest request) {
String domainName = null;
String serverName = request.getRequestURL().toString();
if (serverName == null || serverName.equals("")) {
domainName = "";
} else {
serverName = serverName.toLowerCase();
serverName = serverName.substring(7);
final int end = serverName.indexOf("/");
serverName = serverName.substring(0, end);
if (serverName.indexOf(":") > 0) {
String[] ary = serverName.split("\\:");
serverName = ary[0];
}
final String[] domains = serverName.split("\\.");
int len = domains.length;
if (len > 3 && !isIp(serverName)) {
// www.xxx.com.cn
domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];
} else if (len <= 3 && len > 1) {
// xxx.com or xxx.cn
domainName = "." + domains[len - 2] + "." + domains[len - 1];
} else {
domainName = serverName;
}
}
return domainName;
}
public static String trimSpaces(String IP){//去掉IP字符串前后所有的空格
while(IP.startsWith(" ")){
IP= IP.substring(1,IP.length()).trim();
}
while(IP.endsWith(" ")){
IP= IP.substring(0,IP.length()-1).trim();
}
return IP;
}
public static boolean isIp(String IP){//判断是否是一个IP
boolean b = false;
IP = trimSpaces(IP);
if(IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")){
String s[] = IP.split("\\.");
if(Integer.parseInt(s[0])<255)
if(Integer.parseInt(s[1])<255)
if(Integer.parseInt(s[2])<255)
if(Integer.parseInt(s[3])<255)
b = true;
}
return b;
}
}
Service 的方法跟 PV 一样,只不过 redis Key 为 website:uv
@Override
public void insertVisit(String key){
if(!redisTemplate.hasKey(key)){
redisTemplate.opsForValue().set(key,"0");
}
Integer count = Integer.parseInt(this.redisTemplate.opsForValue().get(key));
count++;
this.redisTemplate.opsForValue().set(key,count.toString());
}
注意
上面也说了所有用户机保存 visitId 的 cookie 的过期时间应该为 24:59:59,所以我单独写了一个工具类来计算过期时间
package com.craftsman.modules.statistical;
import java.util.Calendar;
public class CookieDeathUtils {
public static int getCookieDeath(){
// 获取当前时间戳
long now = Calendar.getInstance().getTimeInMillis();
// 通过 Calendar 手动设置时间
Calendar calendar = Calendar.getInstance();
calendar.set(Calendar.HOUR_OF_DAY,23);
calendar.set(Calendar.MINUTE,59);
calendar.set(Calendar.SECOND,59);
// 获取当日 23:59:59 的时间戳
long death = calendar.getTimeInMillis();
// 计算过期时间
// 注意:cookie 过期时间的单位为秒
int cookieMaxAge = (int) ((death-now)/1000);
return cookieMaxAge;
}
}
3.统计IP
也是写一个 拦截器,获取每次请求的 IP
@Component
public class IpInterceptor extends HandlerInterceptorAdapter {
@Autowired
private WebsiteService websiteService;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception{
// 获取请求的 IP
String remoteAddr = IpAddrUtils.getIpAddr(request);
if(StringUtils.isNotBlank(remoteAddr)){
// 如果 redis 中已经保存了该 ip,那么就不记录
if( !websiteService.hasRomteIp(remoteAddr)){
// 如果 redis 中还没记录该 IP,将该 IP 放入 redis 的 Set 中
this.websiteService.insertRomteIp(remoteAddr);
}
}
return true;
}
}
Service 中的方法
@Override
public Boolean hasRomteIp(String addr) {
// 判断 key 为 website:ips 的 Set 中是否已经有该 IP
return redisTemplate.opsForSet().isMember(WebsiteRedisRole.PREFIX_KEY_IPS,addr);
}
@Override
public void insertRomteIp(String addr) {
// 将 ip 放入 key 为 website:ips 的 Set 中
redisTemplate.opsForSet().add(WebsiteRedisRole.PREFIX_KEY_IPS,addr);
}
注意
这里再特别注意一点,服务在部署到云服务器后一般会使用 nginx 来做反向代理,所以,直接 request.getRemoteAddr() 是无法拿到远程客户机的 IP 的。解决方案如下:
1)配置 nginx
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- Host:保存客户端真实的域名和端口号
- X-Real-IP:保存客户端真实的IP
- X-Forwarded-For:这个 Header 和 X-Real-IP 类似,但它在多层代理时会包含真实客户端及中间每个代理服务器的 IP
PS:关于 XFF 头可以参考这篇文章
2)通过 XFF 头去获取真实 IP
```java
public class IpAddrUtils {
public static String getIpAddr(HttpServletRequest request){
String ipAddress = request.getHeader("x-forwarded-for");
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.getRemoteAddr();
if(ipAddress.equals("127.0.0.1") || ipAddress.equals("0:0:0:0:0:0:0:1")){
//根据网卡取本机配置的IP
InetAddress inet=null;
try {
inet = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
e.printStackTrace();
}
ipAddress= inet.getHostAddress();
}
}
// 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
if(ipAddress!=null && ipAddress.length()>15){ //"***.***.***.***".length() = 15
if(ipAddress.indexOf(",")>0){
ipAddress = ipAddress.substring(0,ipAddress.indexOf(","));
}
}
return ipAddress;
}
}
按日落库
上面说了 PV、UV、IP 变化的逻辑,当一天完了后,我们需要将 redis 中的数据保存到数据库。
WebsiteTask实现类:
package com.craftsman.modules.statistical;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Set;
@Component
public class WebsiteTask {
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private WebsiteService websiteService;
private static final String KEY = "website:*";
protected void executeInternal(){
// 模糊匹配,将所有 website:* 的 key 都查找出来
Set<String> keys = this.redisTemplate.keys(KEY);
if(CollectionUtils.isEmpty(keys)){
return;
}
// 将 redis 中 这些 key 对应的数据落库
this.websiteService.insertAllToDb(keys);
// 删除当前这些 key
keys.forEach(k-> this.redisTemplate.delete(k));
}
}
定时任务的执行入口:JobExe类
package com.craftsman.modules.statistical;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class JobExe {
@Autowired
private WebsiteTask websiteTask;
@Scheduled(cron = "0 59 23 * * ? *")
public void test1(){
websiteTask.executeInternal();
}
}
此时数据应该存储到数据库中,展示就对应地开发相应的接口:
可以根据路径进行细化到各模块的访问统计,实现方式通过路径映射对应的业务模块,至于具体实现自己实现(很简单),获取路径方式如下: (在拦截器中标注红色的部分,对应地做相应的处理)
最后也可以实现这样的页面效果:
至此,可以实现对 PV、UV、IP 日统计统计了。
补充:好像忘记添加两个类了Website .java、WebsiteDao.java;
package com.craftsman.modules.website.entity;
import java.util.Date;
import java.io.Serializable;
/**
* 统计PV UV数据(Website)实体类
*/
public class Website implements Serializable {
private static final Long serialVersionUID = 654590859980231033L;
private Long webId;
private Integer webPv;
private Integer webUv;
private Integer webIp;
private Date schedule;
private Date createTime;
private Integer type;
public Long getWebId() {
return webId;
}
public void setWebId(Long webId) {
this.webId = webId;
}
public Integer getWebPv() {
return webPv;
}
public void setWebPv(Integer webPv) {
this.webPv = webPv;
}
public Integer getWebUv() {
return webUv;
}
public void setWebUv(Integer webUv) {
this.webUv = webUv;
}
public Integer getWebIp() {
return webIp;
}
public void setWebIp(Integer webIp) {
this.webIp = webIp;
}
public Date getSchedule() {
return schedule;
}
public void setSchedule(Date schedule) {
this.schedule = schedule;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
}
package com.craftsman.modules.website.dao;
import com.craftsman.modules.website.entity.Website;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 统计PV UV数据(Website)表数据库访问层
*/
@Mapper
public interface WebsiteDao {
/**
* 通过ID查询单条数据
*
* @param webId 主键
* @return 实例对象
*/
Website queryById(Long webId);
/**
* 查询指定行数据
*
* @param offset 查询起始位置
* @param limit 查询条数
* @return 对象列表
*/
List<Website> queryAllByLimit(@Param("offset") int offset, @Param("limit") int limit);
/**
* 通过实体作为筛选条件查询
*
* @param website 实例对象
* @return 对象列表
*/
List<Website> queryAll(Website website);
/**
* 新增数据
*
* @param website 实例对象
* @return 影响行数
*/
int insert(Website website);
/**
* 修改数据
*
* @param website 实例对象
* @return 影响行数
*/
int update(Website website);
/**
* 通过主键删除数据
*
* @param webId 主键
* @return 影响行数
*/
int deleteById(Integer webId);
}
参考原作者实现时遇到2个问题:
1.在UvInterceptor.java看、拦截器中,他使用的THREAD_LOCAL.set(CookieUtils.getCookieValue(request, VISIT_COOKIE_NAME));
不知道为什么,在我的实现中我已经删除了,对我没有影响。
2.原作者使用的是 Quartz 来实现定时任务,我最初用的就是按照他所实现进行写的,但是不知道为什么没有触发,所以更换了实现方式。
感谢原作者博文
更多推荐
所有评论(0)