电商指标项目-上报服务系统开发(详细源码)

在这里插入图片描述

上报系统:java工程,SpringBoot框架

1.Spring Boot简介

Spring Boot是一个基于Spring之上的快速应用构建框架。使用Spring Boot可以快速开发出基于Spring的应用。Spring Boot主要解决两方面的问题。

  • 依赖太多问题

    • 轻量级JavaEE开发,需要导入大量的依赖

    • 依赖之间还存在版本冲突

  • 配置太多问题

    • 大量的XML配置

Spring Boot内部整合了大量的依赖,而且经过大量测试,选择的依赖都是没有版本冲突的。Spring Boot简化了大量的配置,通过少量的配置,就可以让程序工作。

开发Spring Boot程序的基本步骤

  • 导入Spring Boot依赖(起步依赖)
  • 编写application.properties配置文件
  • 编写Application入口程序

2.导入Maven依赖

主要导入以下依赖:

  1. 导入Spring Boot依赖
  2. 操作JSON导入FastJSON依赖
  3. 导入Kafka依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.13.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.xu.flinkpyg</groupId>
    <artifactId>report</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>report</name>
    <description>Demo project for Spring Boot</description>

    <modelVersion>4.0.0</modelVersion>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>


        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.kafka</groupId>
            <artifactId>spring-kafka</artifactId>
            <version>1.0.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>http-client</artifactId>
            <version>2.0.1.RELEASE</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

3. 创建项目包结构

包名说明
com.xu.report.controller存放Spring MVC的controller
com.xu.report.bean存放相关的Java Bean实体类
com.xu.report.util用来存放相关的工具类

4.验证Spring Boot工程是否创建成功

步骤

在这里插入图片描述

  1. 创建SpringBoot入口程序Application
package com.xu.report;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

// 添加@SpringBootApplication注解,表示该类是个启动类
@SpringBootApplication
public class ReportApplication {
    public static void main(String[] args) {
        SpringApplication.run(ReportApplication.class,args);
    }
}

  1. 创建application.properties配置文件
properties server.port=8080
  1. 编写一个简单Spring MVC Controller/Handler,接收浏览器请求参数并打印回显
package com.xu.report.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

// 表示这是一个Controller,并且其中的所有的方法都是带有@ResponseBody的注解
@RestController
public class TestController {

    @RequestMapping
    public String test(String json) {
        System.out.println(json);
        return json;
    }
}

  1. 打开浏览器测试
    http://127.0.0.1:8080/test?json=1212

结果:
在这里插入图片描述

五 安装Kafka-Manager

Kafka-manager是Yahoo!开源的一款Kafka监控管理工具。

资料:
链接:https://pan.baidu.com/s/18iC5OsAloqeu_6X7-cPcTQ
提取码:n40p

安装步骤

  1. 上传资料\软件包中的kafka-manager-1.3.3.7.tar.gz

  2. 解压到/export/servers

    tar -zxf kafka-manager-1.3.3.7.tar.gz -C /export/servers/
    
  3. 修改conf/application.conf

    kafka-manager.zkhosts="node01:2181,node02:2181,node03:2181"
    
  4. 启动zookeeper

    cd  /export/servers/zookeeper-3.4.5-cdh5.14.0
    bin/zkServer.sh start
    
  5. 启动kafka

    # 启动3台机器的Kafka
    cd /export/servers/kafka_2.11-0.10.1.0
    bin/kafka-server-start.sh config/server.properties > /dev/null 2>&1 &
    
  6. 直接运行bin/kafka-manager

    cd /export/servers/kafka-manager-1.3.3.7
    nohup bin/kafka-manager 2>&1 &
    
  7. 浏览器中使用node01:9000访问即可

默认kafka-manager的端口号为9000,如果该端口被占用,请使用下面的命令修改端口

bin/kafka-manager -Dconfig.file=/export/servers/kafka-manager-1.3.3.7/conf/application.conf -Dhttp.port=10086

6 编写Kafka生产者配置工具类

由于我们项目要操作Kafka, 我们先来构建出KafkaTemplate, 这是一个Kafka的模板对象, 通过它我们可以很方便的发送消息到Kafka.

开发步骤

  1. 编写Kafka生产者配置
  2. 编写Kafka生产者SpringBoot配置工具类KafkaProducerConfig,构建KafkaTemplate

实现

  1. 导入Kafka生产者配置文件

    将下面的代码拷贝到application.properties

   #
   # kakfa
   #
   #kafka的服务器地址
   kafka.bootstrap_servers_config=node01:9092,node02:9092,node03:9092
   #如果出现发送失败的情况,允许重试的次数
   kafka.retries_config=0
   #每个批次发送多大的数据
   kafka.batch_size_config=4096
   #定时发送,达到1ms发送
   kafka.linger_ms_config=1
   #缓存的大小
   kafka.buffer_memory_config=40960
   #TOPIC的名字
   kafka.topic=pyg
>**定时定量**
>
>1. kafka生产者发送一批数据的大小:kafka.producer.batch.size=4096  (单位:字节)
>
>	实际环境可以调大一些,提高效率
>
>2. 定时发送:kafka.producer.linger=1
>
>	达到一毫秒后发送
  1. 编写KafkaProducerConfig,主要创建KafkaTemplate,用于发送Kafka消息

    • 使用@Value("${配置项}")来读取配置
    • 构建DefaultKafkaProducerFactory
    • 构建KafkaTemplate
    @Configuration
    public class KafkaConfig {
    
        @Value("${kafka.bootstrap_servers_config}")
        private String bootstrap_servers_config;
    
        @Value("${kafka.retries_config}")
        private int retries_config;
    
        @Value("${kafka.batch_size_config}")
        private int batch_size_config;
    
        @Value("${kafka.linger_ms_config}")
        private int linger_ms_config;
    
        @Value("${kafka.buffer_memory_config}")
        private int buffer_memory_config;
    
        @Bean
        public KafkaTemplate<String, String> kafkaTemplate() {
            Map<String, Object> configs = new HashMap<>();
            configs.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrap_servers_config);
            configs.put(ProducerConfig.RETRIES_CONFIG, retries_config);
            configs.put(ProducerConfig.BATCH_SIZE_CONFIG, batch_size_config);
            configs.put(ProducerConfig.LINGER_MS_CONFIG, linger_ms_config);
            configs.put(ProducerConfig.BUFFER_MEMORY_CONFIG, buffer_memory_config);
            configs.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, RoundRobinPartitioner.class);
            configs.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
            configs.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
    
            ProducerFactory<String, String> producerFactory = new DefaultKafkaProducerFactory<>(configs);
    
            return new KafkaTemplate<String, String>(producerFactory);
        }
    }
    
    
    
  2. test测试源码中创建一个Junit测试用例

    • 整合Spring Boot Test

    • 注入KafkaTemplate

    • 测试发送100条消息到testtopic

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class KafkTest {
        @Autowired
        private KafkaTemplate kafkaTemplate;
    
        @Test
        public void sendTest01() {
            for(int i = 0; i < 100; i++) {
                kafkaTemplate.send("test", "key", "test msg!");
            }
        }
    }
    
  3. 在KafkaManager创建testtopic,三个分区、两个副本

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

注意:如果发现 kafka-manager中新建的Cluster,Brokers的个数为0:
原因:查看zookeeper和kafka集群是否已经启动,如果已经启动,那么就是上面配置的问题:
在这里插入图片描述

node01:2181,node02:2181,node03:2181/kafka

这里后缀kafka,默认是没有的,我这边里有,是因为配置kafka集群的时候,加上的后缀,这个配置在kafka的 config/server.properties中的zookeeper.connect

在这里插入图片描述

创建topic,3个分区,2个副本
在这里插入图片描述

  1. 启动kafka-console-consumer

      bin/kafka-console-consumer.sh --zookeeper node01:2181/kafka --from-beginning --topic test
    

启动SpringBoot的程序:

可能出现的错误:

spring boot Unable to find a @SpringBootConfiguration, you need to use @ContextConfigur

在这里插入图片描述
包的目录,需要保持一致!!!

  1. 打开kafka-manager的consumer监控页面,查看对应的logsize参数,消息是否均匀的分布在不同的分区中

控制台结果:
在这里插入图片描述

在这里插入图片描述

从上面可知,虽然有三个分区,但是消息发送到了一个!!

7 均匀分区

编写RoundRobbinPartitioner,实现Partitioner接口,确保消息能够发送到Kafka的每个分区

  • 实现Partitioner接口的partition方法
  • 创建一个AtomicInteger变量,用来保存当前的计数器,每次生产一条消息加1
  • 使用计数器变量模除以分区数量得到当前消息需要发送的分区号

在这里插入图片描述



八 上报系统,正式开发

上报服务系统要能够接收http请求,并将http请求中的数据写入到kafka

在这里插入图片描述

步骤

  1. 创建Message实体类对象

    所有的点击流消息都会封装到Message实体类中

  2. 设计一个Controller来接收http请求

  3. 将http请求发送的消息封装到一个Message实体类对象

  4. 使用FastJSONMessage实体类对象转换为JSON字符串

  5. 将JSON字符串使用kafkaTemplate写入到kafka

  6. 返回给客户端一个写入结果JSON串

在这里插入图片描述

实现

  1. 创建Message实体类
  • 包含以下字段:消息次数(count)、消息时间(timeStamp)、消息体(message)
  • 生成getter/setter、toString方法

Message.java

package com.xu.report.bean;

public class Message {
    // 消息次数
    private int count;
    // 消息的时间戳
    private long timeStamp;
    // 消息体
    private String message;

    public int getCount() {
        return count;
    }
    public void setCount(int count) {
        this.count = count;
    }
    public long getTimeStamp() {
        return timeStamp;
    }
    public void setTimeStamp(long timeStamp) {
        this.timeStamp = timeStamp;
    }
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    @Override
    public String toString() {
        return "Message{" +
                "count=" + count +
                ", timeStamp=" + timeStamp +
                ", message='" + message + '\'' +
                '}';
    }
}

  1. com.xu.report.controller包下创建ReportController

    • 编写receiveDataHandler接收从客户端JSON数据,并将响应结果封装到Map结构中,返回给客户端
      注意:接收JSON数据要在参数前面添加@RequestBody注解
    • 将接收的参数封装到Message实体类
    • 使用FastJSONMessage实体类对象转换为JSON字符串
    • 将JSON字符串发送到Kafka的pygtopic
    • 将响应结果封装到Map结构中,返回给客户端

    注意:

    1. 在ReportController类上要添加@RestController注解
    2. 需要添加@AutoWired注解来注入KafkaTemplate
    3. 请求参数上要加上@RequestBody注解

ReportController.java

package com.xu.report.controller;

import com.alibaba.fastjson.JSON;
import com.xu.report.bean.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
public class ReportController {

    @Autowired
    KafkaTemplate kafkaTemplate;

    @RequestMapping("/receive")
    public Map<String, String> receive(@RequestBody String json) {

        Map<String, String> map = new HashMap<>();
        try {
            //构建Message
            Message msg = new Message();
            msg.setMessage(json);
            msg.setCount(1);
            msg.setTimeStamp(System.currentTimeMillis());

            String msgJson = JSON.toJSONString(msg);

            //发送Message到kafka
            kafkaTemplate.send("pyg", msgJson);
            map.put("success", "true");
        } catch (Exception ex) {
            ex.printStackTrace();
            map.put("success", "false");
        }

        return map;
    }
}

9 模拟生产点击流日志消息到Kafka

为了方便进行测试,我们可以使用一个消息生成工具来生成点击流日志,然后发送给上报服务系统。该消息生成工具可以一次生成100条ClickLog信息,并转换成JSON,通过HttpClient把消息内容发送到我们编写好的ReportController。

在这里插入图片描述

步骤

  1. 编写ClickLog实体类(ClickLog.java)
  2. 编写点击流日志生成器(ClickLogGenerator.java)
package com.xu.report.bean;
public class ClickLog {
    //频道ID
    private long channelID ;
    //产品的类别ID
    private long categoryID ;
    //产品ID
    private long produceID ;
    //用户的ID
    private long userID ;
    //国家
    private String country ;
    //省份
    private String province ;
    //城市
    private String city ;
    //网络方式
    private String network ;
    //来源方式
    private String source ;
    //浏览器类型
    private String browserType;
    //进入网站时间
    private Long entryTime ;
    //离开网站时间
    private long leaveTime ;

    public long getChannelID() {
        return channelID;
    }

    public void setChannelID(long channelID) {
        this.channelID = channelID;
    }

    public long getCategoryID() {
        return categoryID;
    }

    public void setCategoryID(long categoryID) {
        this.categoryID = categoryID;
    }

    public long getProduceID() {
        return produceID;
    }

    public void setProduceID(long produceID) {
        this.produceID = produceID;
    }

    public String getCountry() {
        return country;
    }

    public void setCountry(String country) {
        this.country = country;
    }

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public String getNetwork() {
        return network;
    }

    public void setNetwork(String network) {
        this.network = network;
    }

    public String getSource() {
        return source;
    }

    public void setSource(String source) {
        this.source = source;
    }

    public String getBrowserType() {
        return browserType;
    }

    public void setBrowserType(String browserType) {
        this.browserType = browserType;
    }

    public Long getEntryTime() {
        return entryTime;
    }

    public void setEntryTime(Long entryTime) {
        this.entryTime = entryTime;
    }

    public long getLeaveTime() {
        return leaveTime;
    }

    public void setLeaveTime(long leaveTime) {
        this.leaveTime = leaveTime;
    }

    public long getUserID() {
        return userID;
    }

    public void setUserID(long userID) {
        this.userID = userID;
    }

    @Override
    public String toString() {
        return "ClickLog{" +
                "channelID=" + channelID +
                ", categoryID=" + categoryID +
                ", produceID=" + produceID +
                ", country='" + country + '\'' +
                ", province='" + province + '\'' +
                ", city='" + city + '\'' +
                ", network='" + network + '\'' +
                ", source='" + source + '\'' +
                ", browserType='" + browserType + '\'' +
                ", entryTime=" + entryTime +
                ", leaveTime=" + leaveTime +
                ", userID=" + userID +
                '}';
    }
}

package com.xu.report.util;

import com.alibaba.fastjson.JSONObject;
import com.xu.report.bean.ClickLog;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.junit.Test;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

/**
 * 点击流日志模拟器
 */
public class ClickLogGenerator {
    private static Long[] channelID = new Long[]{1l,2l,3l,4l,5l,6l,7l,8l,9l,10l,11l,12l,13l,14l,15l,16l,17l,18l,19l,20l};//频道id集合
    private static Long[] categoryID = new Long[]{1l,2l,3l,4l,5l,6l,7l,8l,9l,10l,11l,12l,13l,14l,15l,16l,17l,18l,19l,20l};//产品类别id集合
    private static Long[] produceID = new Long[]{1l,2l,3l,4l,5l,6l,7l,8l,9l,10l,11l,12l,13l,14l,15l,16l,17l,18l,19l,20l};//产品id集合
    private static Long[] userID = new Long[]{1l,2l,3l,4l,5l,6l,7l,8l,9l,10l,11l,12l,13l,14l,15l,16l,17l,18l,19l,20l};//用户id集合

    /**
     * 地区
     */
    private static String[] contrys = new String[]{"china"};//地区-国家集合
    private static String[] provinces = new String[]{"HeNan","HeBei"};//地区-省集合
    private static String[] citys = new String[]{"ShiJiaZhuang","ZhengZhou", "LuoYang"};//地区-市集合

    /**
     *网络方式
     */
    private static String[] networks = new String[]{"电信","移动","联通"};

    /**
     * 来源方式
     */
    private static String[] sources = new String[]{"直接输入","百度跳转","360搜索跳转","必应跳转"};

    /**
     * 浏览器
     */
    private static String[] browser = new String[]{"火狐","qq浏览器","360浏览器","谷歌浏览器"};

    /**
     * 打开时间 离开时间
     */
    private static List<Long[]> usetimelog = producetimes();
    //获取时间
    public static List<Long[]> producetimes(){
        List<Long[]> usetimelog = new ArrayList<Long[]>();
        for(int i=0;i<100;i++){
            Long [] timesarray = gettimes("2020-07-12 24:60:60:000");
            usetimelog.add(timesarray);
        }
        return usetimelog;
    }

    private static Long [] gettimes(String time){
        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss:SSS");
        try {
            Date date = dateFormat.parse(time);
            long timetemp = date.getTime();
            Random random = new Random();
            int randomint = random.nextInt(10);
            long starttime = timetemp - randomint*3600*1000;
            long endtime = starttime + randomint*3600*1000;
            return new Long[]{starttime,endtime};
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return new Long[]{0l,0l};
    }


    /**
     * 模拟发送Http请求到上报服务系统
     * @param url
     * @param json
     */
    public static void send(String url, String json) {
        try {
            CloseableHttpClient httpClient = HttpClientBuilder.create().build();
            HttpPost post = new HttpPost(url);
            JSONObject response = null;
            try {
                StringEntity s = new StringEntity(json.toString(), "utf-8");
                s.setContentEncoding("utf-8");
                // 发送json数据需要设置contentType
                s.setContentType("application/json");
                post.setEntity(s);

                HttpResponse res = httpClient.execute(post);
                if (res.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
                    // 返回json格式:
                    String result = EntityUtils.toString(res.getEntity());
                    System.out.println(result);
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Random random = new Random();
        for (int i = 0; i < 100; i++) {
            //频道id 类别id 产品id 用户id 打开时间 离开时间 地区 网络方式 来源方式 浏览器
            ClickLog clickLog = new ClickLog();

            clickLog.setChannelID(channelID[random.nextInt(channelID.length)]);
            clickLog.setCategoryID(categoryID[random.nextInt(categoryID.length)]);
            clickLog.setProduceID(produceID[random.nextInt(produceID.length)]);
            clickLog.setUserID(userID[random.nextInt(userID.length)]);
            clickLog.setCountry(contrys[random.nextInt(contrys.length)]);
            clickLog.setProvince(provinces[random.nextInt(provinces.length)]);
            clickLog.setCity(citys[random.nextInt(citys.length)]);
            clickLog.setNetwork(networks[random.nextInt(networks.length)]);
            clickLog.setSource(sources[random.nextInt(sources.length)]);
            clickLog.setBrowserType(browser[random.nextInt(browser.length)]);

            Long[] times = usetimelog.get(random.nextInt(usetimelog.size()));
            clickLog.setEntryTime(times[0]);
            clickLog.setLeaveTime(times[1]);

            String jonstr = JSONObject.toJSONString(clickLog);
            System.out.println(jonstr);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            send("http://localhost:8888/receive", jonstr);
        }
    }
}

点击流日志字段

字段说明
channelID频道ID
categoryID产品的类别ID
produceID产品ID
country国家
province省份
city城市
network网络方式(移动、联通、电信…)
source来源方式
browserType浏览器类型
entryTime进入网站时间
leaveTime离开网站时间
userID用户ID

10 验证测试代码

步骤

  1. 创建Kafka的topic(pyg
  2. 使用kafka-console-consumer.sh消费 topic中的数据
  3. 启动上报服务
  4. 执行ClickLogGenerator的main方法,生成一百条用户浏览消息到Kafka

实现

  1. 创建kafka topic

    bin/kafka-topics.sh --create --zookeeper node01:2181/kafka --replication-factor 2 --partitions 3 --topic pyg
    
  2. 启动kafka消费者

    bin/kafka-console-consumer.sh --zookeeper node01:2181/kafka --from-beginning --topic pyg
    
  3. 执行程序
    启动SpringBoot项目;
    执行ClickLogGenerator中的main方法

结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

注意:

1.程序的端口是否对应;
2.kafka-manager没有正常关闭,启动失败,

cd /export/servers/kafka-manager-1.3.3.7
tail -400 nohup.out

查看一下:
在这里插入图片描述

一般删除 kafka-manager-1.3.3.7目录下的RUNNING_PID,再次启动即可!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值