Actual combat of Niuke backend project: website statistics

  • UV(Unique Visitor)
    • For independent visitors, you need to remove the weight statistics through the user's IP
    • Statistics shall be made for each visit
    • Hyperlog has good performance and small storage space
  • DAU(Dail Active User)
    • For daily active users, it is necessary to remove the weight statistics through the user ID
    • Once visited, it is considered active (the definition can be different)
    • Bitmap has good performance and can count accurate results

Rediskey

RedisKeyUtil.java

    // Single day UV
    public static String getUVKey(String date) {
        return PREFIX_UV + SPLIT + date;
    }

    // Interval UV
    public static String getUVKey(String startDate, String endDate) {
        return PREFIX_UV + SPLIT + startDate + SPLIT + endDate;
    }

    // Single day active users
    public static String getDAUKey(String date) {
        return PREFIX_DAU + SPLIT + date;
    }

    // Interval active users
    public static String getDAUKey(String startDate, String endDate) {
        return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;
    }

Service

Because Redis is used to store data, you don't need to access the DAO layer, just drop the data in the Service layer.
DataService.java

UV(Unique Visitor)

1. Counts the specified IP into the UV

@Service
public class DataService {

    @Autowired
    private RedisTemplate redisTemplate;

    private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");

    // Counts the specified IP into the UV
    public void recordUV(String ip) {
        String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
        redisTemplate.opsForHyperLogLog().add(redisKey, ip);
    }

1) private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd") specify the date format first

2. Statistics: counts UV s within a specified date range

    // Counts UV s within the specified date range
    public long calculateUV(Date start, Date end) {
        if (start == null || end == null) {
            throw new IllegalArgumentException("Parameter cannot be empty!");
        }

        // Sort out the key s within the date range
        List<String> keyList = new ArrayList<>();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(start);
        while (!calendar.getTime().after(end)) {
            String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));  // Single day UV
            keyList.add(key);
            calendar.add(Calendar.DATE, 1);                      // Date + 1
        }

        // Merge these data
        String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));  // key for generating interval UV s
        redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());

        // Returns the result of the statistics
        return redisTemplate.opsForHyperLogLog().size(redisKey);
    }

1) The date parameter passed in is the Data class
2) To count the data within the date range, you need to form a group of Rediskey: List < string > keylist, in which the Calendar class is used to cycle the date
3) Merge data
4) Call redistemplate opsForHyperLogLog(). Size() get statistics

DAU(Dail Active User)

1. Count the specified user into DAU according to userId

    public void recordDAU(int userId) {
        String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));
        redisTemplate.opsForValue().setBit(redisKey, userId, true);
    }

2. Counts daus within the specified date range
Similar to the calculateUV() method, the difference is that the OR operation is performed on the data within the interval, and the connection Bitop() requires the Byte array of RedisKey to be passed in

    public long calculateDAU(Date start, Date end) {
        if (start == null || end == null) {
            throw new IllegalArgumentException("Parameter cannot be empty!");
        }

        // Sort out the key s within the date range
        List<byte[]> keyList = new ArrayList<>();	// Convert RedisKey to byte array
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(start);
        while (!calendar.getTime().after(end)) {
            String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));
            keyList.add(key.getBytes());   
            calendar.add(Calendar.DATE, 1);
        }

        // Perform OR operation
        return (long) redisTemplate.execute(new RedisCallback() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));
                connection.bitOp(RedisStringCommands.BitOperation.OR,     // OR operation
                        redisKey.getBytes(), keyList.toArray(new byte[0][0]));
                return connection.bitCount(redisKey.getBytes());                 // The number of statistics digits is true
            }
        });
    }
}

Controller

@Controller
public class DataController {

    @Autowired
    private DataService dataService;

    // Statistics page
    @RequestMapping(path = "/data", method = {RequestMethod.GET, RequestMethod.POST})
    public String getDataPage() {
        return "/site/admin/data";
    }

    // Statistics website UV
    @RequestMapping(path = "/data/uv", method = RequestMethod.POST)
    public String getUV(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,
                        @DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model) {
        long uv = dataService.calculateUV(start, end);
        model.addAttribute("uvResult", uv);
        model.addAttribute("uvStartDate", start); // Put the date parameter into the Model so that the page can display the default value
        model.addAttribute("uvEndDate", end);
        return "forward:/data";
    }

    // Statistics of active users
    @RequestMapping(path = "/data/dau", method = RequestMethod.POST)
    public String getDAU(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,
                         @DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model) {
        long dau = dataService.calculateDAU(start, end);
        model.addAttribute("dauResult", dau);
        model.addAttribute("dauStartDate", start);
        model.addAttribute("dauEndDate", end);
        return "forward:/data";
    }

}

1. The "/ data" path opens the website statistics page
2. path = "/ data/uv" Statistics UV.
1) @ DateTimeFormat(pattern = "yyyy MM DD"): Date start is the processing of date parameters.
The method parameter in the Controller in spring MVC is of type Date. When you want to limit the time format of the incoming request, you can specify it through @ DateTimeFormat. However, if the incoming request parameter does not match the specified format, an error of 400 will be returned.
Time format annotation @ DateTimeFormat in Spring

2) return "forward:/data": forward requests forwarding. It is declared that this method can only handle half of the processing, and another method is needed to handle it. The request is forwarded to the "/ data" path. Since it is still the same request, the "/ data" path should also support requestmethod Post request
https://blog.csdn.net/u011676300/article/details/79657933
http://www.51gjie.com/javaweb/956.html

Interceptor

Data should be recorded for each request, so interceptors are used here

@Component
public class DataInterceptor implements HandlerInterceptor {

    @Autowired
    private DataService dataService;

    @Autowired
    private HostHolder hostHolder;  // Get current login user

	// Execute before Controller
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // Statistical UV
        String ip = request.getRemoteHost();  // Get IP
        dataService.recordUV(ip);        // Count UV

        // Statistical DAU
        User user = hostHolder.getUser();
        if (user != null) {
            dataService.recordDAU(user.getId());
        }

        return true;   // Request to continue downward execution
    }
}

Configure DataInterceptor to intercept all requests except static resources
WebMvcConfig.java

    @Autowired
    private DataInterceptor dataInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    	...
        registry.addInterceptor(dataInterceptor)
                .excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");
    }

Template

		<!-- content -->
		<div class="main">
			<!-- website UV -->
			<div class="container pl-5 pr-5 pt-3 pb-3 mt-3">
				<h6 class="mt-3"><b class="square"></b> website UV</h6>
				<form class="form-inline mt-3" method="post" th:action="@{/data/uv}"> // Request path
					<input type="date" class="form-control" required name="start" th:value="${#dates. Format (uvStartDate, 'yyyy MM DD')} "/ > / / name =" start "th: value is the formatted date class uvStartDate
					<input type="date" class="form-control ml-3" required name="end" th:value="${#dates.format(uvEndDate,'yyyy-MM-dd')}"/>
					<button type="submit" class="btn btn-primary ml-3">Start statistics</button>
				</form>
				<ul class="list-group mt-3 mb-3">
					<li class="list-group-item d-flex justify-content-between align-items-center">
						Statistical results
						<span class="badge badge-primary badge-danger font-size-14" th:text="${uvResult}">0</span>
					</li>
				</ul>
			</div>

< input type = "date" >: input time form default style
Input time form default style modification (input[type = "date"])

Active user templates are similar

			<!-- Active user -->
			<div class="container pl-5 pr-5 pt-3 pb-3 mt-4">
				<h6 class="mt-3"><b class="square"></b> Active user</h6>
				<form class="form-inline mt-3" method="post" th:action="@{/data/dau}">
					<input type="date" class="form-control" required name="start" th:value="${#dates.format(dauStartDate,'yyyy-MM-dd')}"/>
					<input type="date" class="form-control ml-3" required name="end" th:value="${#dates.format(dauEndDate,'yyyy-MM-dd')}"/>
					<button type="submit" class="btn btn-primary ml-3">Start statistics</button>
				</form>
				<ul class="list-group mt-3 mb-3">
					<li class="list-group-item d-flex justify-content-between align-items-center">
						Statistical results
						<span class="badge badge-primary badge-danger font-size-14" th:text="${dauResult}">0</span>
					</li>
				</ul>
			</div>				
		</div>

Keywords: Java

Added by mikemike on Wed, 02 Feb 2022 18:02:54 +0200