项目效果概览:
项目架构:
获取文件模板,前端就会展示问卷的列表,我们就可以选择选项,结果就会同步到我们的后端,通过后端把统计结果发生到前端,完成整个流程.
git仓库地址:
目录标题
项目效果概览:项目架构:git仓库地址:1.基础准备工作1.1lombok插件1.2maven依赖配置1.3 接口设计1.4模块式开发1.5common公共类1.5.1BaseResponseVO:实现的是公共返回值15.2CorsFilter:解决跨域问题
1.6utils工具类1.7conf配置读取类1.7.1配置application.yml文件1.7.2做一个测试的模板
2.微信调查问卷模板配置3.业务层实现(业务层实现在service包下)3.1接口WechatTemplateService,主要作用是提供controller进行调取的,真正实现方法在接口的实现类中3.2接口WechatTemplateService的实现类,通过此类实现接口中定义的方法
4.表现层controller实现5.程序业务测试6.Kafka Producer集成7.HTTPS的支持(因为微信小程序要求必须是HTTPS)7.1CA证书申请7.2域名绑定
8.集成SSL证书9.阿里云部署后端10.编译部署11.完整项目代码git仓库地址12.项目后续扩展12.1BigData实现
1.基础准备工作
1.1lombok插件
lomlok的作用主要是简化代码开发
1.2maven依赖配置
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion
>4.0.0</modelVersion
>
<parent
>
<groupId
>org
.springframework
.boot
</groupId
>
<artifactId
>spring
-boot
-starter
-parent
</artifactId
>
<version
>2.2.5.RELEASE
</version
>
<relativePath
/> <!-- lookup parent from repository
-->
</parent
>
<groupId
>com
.imooc
.jiangzh
</groupId
>
<artifactId
>kafka
-study
</artifactId
>
<version
>0.0.1-SNAPSHOT
</version
>
<name
>kafka
-study
</name
>
<description
>微信调查问卷
</description
>
<properties
>
<java
.version
>1.8</java
.version
>
</properties
>
<dependencies
>
<dependency
>
<!--springBoot依赖两个
--->
<groupId
>org
.springframework
.boot
</groupId
>
<artifactId
>spring
-boot
-starter
</artifactId
>
</dependency
>
<dependency
>
<groupId
>org
.springframework
.boot
</groupId
>
<artifactId
>spring
-boot
-starter
-web
</artifactId
>
</dependency
>
<dependency
>
<groupId
>cn
.hutool
</groupId
>
<artifactId
>hutool
-all
</artifactId
>
<version
>4.1.1</version
>
</dependency
>
<!--kafka相关依赖两个
-->
<dependency
>
<groupId
>org
.apache
.kafka
</groupId
>
<artifactId
>kafka
-clients
</artifactId
>
<version
>2.4.0</version
>
</dependency
>
<dependency
>
<groupId
>org
.apache
.kafka
</groupId
>
<artifactId
>kafka
-streams
</artifactId
>
<version
>2.4.0</version
>
</dependency
>
<!-- lombok依赖
-->
<dependency
>
<groupId
>org
.projectlombok
</groupId
>
<artifactId
>lombok
</artifactId
>
<version
>1.18.12</version
>
<scope
>provided
</scope
>
</dependency
>
<dependency
>
<groupId
>com
.google
.guava
</groupId
>
<artifactId
>guava
</artifactId
>
<version
>28.2-jre
</version
>
</dependency
>
<!-- fastjson依赖
-->
<dependency
>
<groupId
>com
.alibaba
</groupId
>
<artifactId
>fastjson
</artifactId
>
<version
>1.2.68</version
>
</dependency
>
</dependencies
>
<build
>
<plugins
>
<plugin
>
<groupId
>org
.springframework
.boot
</groupId
>
<artifactId
>spring
-boot
-maven
-plugin
</artifactId
>
</plugin
>
</plugins
>
</build
>
</project
>
1.3 接口设计
1.4模块式开发
成品样式:
1.5common公共类
1.5.1BaseResponseVO:实现的是公共返回值
用泛型去做枚举,当需要真实返回值的时候按照枚举类型进行传入就可以,当不需返回值的时候,只有RequestId
package com
.yuge
.wechat
.questionnaire
.common
;
import java
.util
.UUID
;
import lombok
.Data
;
@Data
public class BaseResponseVO<M> {
private String requestId
;
private M result
;
public static<M> BaseResponseVO
success(){
BaseResponseVO baseResponseVO
= new BaseResponseVO();
baseResponseVO
.setRequestId(getRequestId());
return baseResponseVO
;
}
public static<M> BaseResponseVO
success(M result
){
BaseResponseVO baseResponseVO
= new BaseResponseVO();
baseResponseVO
.setRequestId(getRequestId());
baseResponseVO
.setResult(result
);
return baseResponseVO
;
}
private static String
getRequestId(){
return UUID
.randomUUID().toString();
}
}
15.2CorsFilter:解决跨域问题
package com
.yuge
.wechat
.questionnaire
.common
;
import java
.io
.IOException
;
import javax
.servlet
.Filter
;
import javax
.servlet
.FilterChain
;
import javax
.servlet
.ServletException
;
import javax
.servlet
.ServletRequest
;
import javax
.servlet
.ServletResponse
;
import javax
.servlet
.annotation
.WebFilter
;
import javax
.servlet
.http
.HttpServletResponse
;
import org
.springframework
.context
.annotation
.Configuration
;
@WebFilter(filterName
= "CorsFilter")
@Configuration
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req
, ServletResponse res
, FilterChain chain
) throws IOException
, ServletException
{
HttpServletResponse response
= (HttpServletResponse
) res
;
response
.setHeader("Access-Control-Allow-Origin","*");
response
.setHeader("Access-Control-Allow-Credentials", "true");
response
.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
response
.setHeader("Access-Control-Max-Age", "3600");
response
.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain
.doFilter(req
, res
);
}
}
1.6utils工具类
package com
.yuge
.wechat
.questionnaire
.utils
;
import com
.alibaba
.fastjson
.JSON
;
import com
.alibaba
.fastjson
.JSONArray
;
import com
.alibaba
.fastjson
.JSONObject
;
import java
.io
.BufferedReader
;
import java
.io
.File
;
import java
.io
.FileNotFoundException
;
import java
.io
.FileReader
;
import java
.io
.IOException
;
import java
.io
.Reader
;
import java
.util
.Optional
;
import lombok
.Cleanup
;
import lombok
.extern
.slf4j
.Slf4j
;
@Slf4j
public class FileUtils {
public static String
readFile(String filePath
) throws IOException
{
@Cleanup
BufferedReader reader
= new BufferedReader(
new FileReader(new File(filePath
))
);
String lineStr
= "";
StringBuffer stringBuffer
= new StringBuffer();
while ((lineStr
= reader
.readLine()) != null
) {
stringBuffer
.append(lineStr
);
}
return stringBuffer
.toString();
}
public static Optional
<JSONObject> readFile2JsonObject(String filePath
){
try {
String fileContent
= readFile(filePath
);
log
.info("readFile2Json fileContent: [{}]" , fileContent
);
return Optional
.ofNullable(JSON
.parseObject(fileContent
));
} catch (IOException e
) {
e
.printStackTrace();
}
return Optional
.empty();
}
public static Optional
<JSONArray> readFile2JsonArray(String filePath
){
try {
String fileContent
= readFile(filePath
);
log
.info("readFile2Json fileContent: [{}]" , fileContent
);
return Optional
.ofNullable(JSON
.parseArray(fileContent
));
} catch (IOException e
) {
e
.printStackTrace();
}
return Optional
.empty();
}
}
1.7conf配置读取类
package com
.yuge
.wechat
.questionnaire
.conf
;
import lombok
.Data
;
import org
.springframework
.boot
.context
.properties
.ConfigurationProperties
;
import org
.springframework
.context
.annotation
.Configuration
;
import java
.util
.List
;
@Data
@Configuration
@ConfigurationProperties(prefix
= "template")
public class WechatTemplateProperties {
private List
<WetchTemplate> templates
;
private int templateResultType
;
private String templateResultFilePath
;
@Data
public static class WetchTemplate{
private String templateId
;
private String templateFilePath
;
private String active
;
}
}
1.7.1配置application.yml文件
注意:conf类下定义的变量是驼峰命名templateResultType到application需要转成tempalte_result_type
server
:
port
: 8080
template
:
templates
:
- {"templateId":"1","templateFilePath":"C:/user/MyCode/wechat-questionnaire/src/main/resources/template/template.json","active":true}
- {"templateId":"2","templateFilePath":"C:/user/MyCode/wechat-questionnaire/src/main/resources/template/template.json","active":false}
template_result_type
: 0
template_result_filePath
: "C:/user/MyCode/wechat-questionnaire/src/main/resources/template/templateRestlt.json"
注意事项:路径是用反斜杠的,而反斜杠在java中是转义符所有把把反斜杠换成/才可以
这一个json是由多个数组组成的,一个数组就一个文件的题目和选项 前端展示的时候他会通过接口调这个文件的内容,文件是什么内容他就会展示什么内容
1.7.2做一个测试的模板
[{ "questionId": "1",
"question": "今天几号",
"answer": "",
"options": [
{"label": "1 号", "value": "A"},
{"label": "2 号", "value": "B"},
{"label": "3 号", "value": "C"},
{"label": "4 号", "value": "D"}
]},
{"questionId": "2",
"question": "你喜爱的颜色",
"answer": "",
"options": [
{"label": "红色", "value": "A"},
{"label": "黄色", "value": "B"},
{"label": "绿色", "value": "C"},
{"label": "紫色", "value": "D"}
]}
]
{ "templateId": "001",
"totalNumber": "102",
"statistics": [ {
"questionId": "1",
"question": "今天几号",
"answers": [
{"label": "A", "value": 10},
{"label": "B", "value": 50},
{"label": "C", "value": 12},
{"label": "D", "value": 17}
]
}, {
"questionId": "2",
"question": "你喜爱的颜色",
"answers": [
{"label": "A", "value": 12},
{"label": "B", "value": 52},
{"label": "C", "value": 17},
{"label": "D", "value": 17} ]
}
]
}
一个存储模板一个存储模板的结果
2.微信调查问卷模板配置
3.业务层实现(业务层实现在service包下)
3.1接口WechatTemplateService,主要作用是提供controller进行调取的,真正实现方法在接口的实现类中
package com
.yuge
.wechat
.questionnaire
.service
;
import com
.alibaba
.fastjson
.JSONObject
;
import com
.yuge
.wechat
.questionnaire
.conf
.WechatTemplateProperties
;
public interface WechatTemplateservice {
WechatTemplateProperties
.WetchTemplate
getWeChatQuestionnaireTemplate();
void reportTheResultsOfTheQuestionnaire(JSONObject resultsData
);
JSONObject
questionnaireStatistics(String templateId
);
}
3.2接口WechatTemplateService的实现类,通过此类实现接口中定义的方法
package com
.yuge
.wechat
.questionnaire
.service
;
import com
.alibaba
.fastjson
.JSONObject
;
import com
.yuge
.wechat
.questionnaire
.conf
.WechatTemplateProperties
;
import com
.yuge
.wechat
.questionnaire
.utils
.FileUtils
;
import lombok
.extern
.slf4j
.Slf4j
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.stereotype
.Service
;
import java
.util
.List
;
import java
.util
.Optional
;
@Slf4j
@Service
public class WechatTemplateServiceImpl implements WechatTemplateservice {
@Autowired
WechatTemplateProperties properties
;
@Override
public WechatTemplateProperties
.WetchTemplate
getWeChatQuestionnaireTemplate() {
List
<WechatTemplateProperties.WetchTemplate> templates
= properties
.getTemplates();
Optional
<WechatTemplateProperties.WetchTemplate> wetchTemplate
= templates
.stream().filter((template
) -> template
.isActive()).findFirst();
return wetchTemplate
.isPresent()? wetchTemplate
.get():null
;
}
@Override
public void reportTheResultsOfTheQuestionnaire(JSONObject resultsData
) {
log
.info("reportTheResultsOfTheQuestionnaire:[{}]",resultsData
);
}
@Override
public JSONObject
questionnaireStatistics(String templateId
) {
if (templateId
=="0"){
return FileUtils
.readFile2JsonObject(properties
.getTemplateResultFilePath()).get();
}else{
return null
;
}
}
}
4.表现层controller实现
实现的是三个接口逻辑
package com
.yuge
.wechat
.questionnaire
.controller
;
import com
.alibaba
.fastjson
.JSON
;
import com
.alibaba
.fastjson
.JSONArray
;
import com
.alibaba
.fastjson
.JSONObject
;
import com
.yuge
.wechat
.questionnaire
.common
.BaseResponseVO
;
import com
.yuge
.wechat
.questionnaire
.conf
.WechatTemplateProperties
;
import com
.yuge
.wechat
.questionnaire
.service
.WechatTemplateservice
;
import com
.yuge
.wechat
.questionnaire
.utils
.FileUtils
;
import org
.apache
.kafka
.common
.protocol
.types
.Field
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.stereotype
.Controller
;
import org
.springframework
.web
.bind
.annotation
.*
;
import java
.util
.HashMap
;
@Controller
@RequestMapping("/v1")
public class WechatTemplateController {
@Autowired
WechatTemplateservice wechatTemplateservice
;
@Autowired
WechatTemplateProperties properties
;
@RequestMapping(value
= "/getTheQuestionnaireTemplate",method
= RequestMethod
.GET
)
public BaseResponseVO
getTheQuestionnaireTemplate(){
WechatTemplateProperties
.WetchTemplate wetchTemplate
= wechatTemplateservice
.getWeChatQuestionnaireTemplate();
HashMap
<String, Object> map
= new HashMap<>();
map
.put("templateId",wetchTemplate
.getTemplateId());
map
.put("template", FileUtils
.readFile2JsonArray(properties
.getTemplateResultFilePath()));
return BaseResponseVO
.success(map
);
}
@RequestMapping(value
= "/returnQuestionnaireResults",method
= RequestMethod
.POST
)
public BaseResponseVO
returnQuestionnaireResults(@RequestBody String resultData
){
wechatTemplateservice
.reportTheResultsOfTheQuestionnaire(JSON
.parseObject(resultData
));
return BaseResponseVO
.success();
}
@RequestMapping(value
= "/getQuestionnaireStatistics",method
= RequestMethod
.GET
)
public BaseResponseVO
getQuestionnaireStatistics(@RequestParam(value
="templateId",required
= false) String templateId
){
JSONObject statistics
= wechatTemplateservice
.questionnaireStatistics(templateId
);
return BaseResponseVO
.success(statistics
);
}
}
5.程序业务测试
-- 查询模板信息
curl
-XGET http
://localhost
:8080/v1
/template
-- 查询模板统计结果
curl
-XGET http
://localhost
:8080/v1
/template
/result
-- 传入调查问卷结果
curl
-XPOST
-H
"Content-Type:application/json; charset=UTF-8" http
://localhost
:8080/v1
/template
/report
-d \
'
{
templateId
:"001",
result
:[
{"questionId":"1","question":"今天几号","answer":"A"},
{"questionId":"2","question":"你喜爱的颜色","answer":"B"}
]
}'
netstat
-ano
| findstr
443
6.Kafka Producer集成
package com
.imooc
.jiangzh
.kafka
.wechat
.conf
;
import lombok
.Data
;
import org
.springframework
.boot
.context
.properties
.ConfigurationProperties
;
import org
.springframework
.context
.annotation
.Configuration
;
@Data
@Configuration
@ConfigurationProperties(prefix
= "wechat.kafka")
public class KafkaProperties {
private String bootstrapServers
;
private String acksConfig
;
private String retriesConfig
;
private String batchSizeConfig
;
private String lingerMsConfig
;
private String bufferMemoryConfig
;
private String keySerializerClassConfig
;
private String valueSerializerClassConfig
;
}
package com
.imooc
.jiangzh
.kafka
.wechat
.conf
;
import org
.apache
.kafka
.clients
.producer
.KafkaProducer
;
import org
.apache
.kafka
.clients
.producer
.Producer
;
import org
.apache
.kafka
.clients
.producer
.ProducerConfig
;
import org
.springframework
.beans
.factory
.annotation
.Autowired
;
import org
.springframework
.context
.annotation
.Bean
;
import org
.springframework
.context
.annotation
.Configuration
;
import java
.util
.Properties
;
@Configuration
public class KafkaConf {
@Autowired
private KafkaProperties kafkaProperties
;
@Bean
public Producer
kafkaProducer(){
Properties properties
= new Properties();
properties
.put(ProducerConfig
.BOOTSTRAP_SERVERS_CONFIG
, kafkaProperties
.getBootstrapServers());
properties
.put(ProducerConfig
.ACKS_CONFIG
, kafkaProperties
.getAcksConfig());
properties
.put(ProducerConfig
.RETRIES_CONFIG
,kafkaProperties
.getRetriesConfig());
properties
.put(ProducerConfig
.BATCH_SIZE_CONFIG
,kafkaProperties
.getBatchSizeConfig());
properties
.put(ProducerConfig
.LINGER_MS_CONFIG
,kafkaProperties
.getLingerMsConfig());
properties
.put(ProducerConfig
.BUFFER_MEMORY_CONFIG
,kafkaProperties
.getBufferMemoryConfig());
properties
.put(ProducerConfig
.KEY_SERIALIZER_CLASS_CONFIG
,kafkaProperties
.getKeySerializerClassConfig());
properties
.put(ProducerConfig
.VALUE_SERIALIZER_CLASS_CONFIG
,kafkaProperties
.getValueSerializerClassConfig());
Producer
<String,String> producer
= new KafkaProducer<>(properties
);
return producer
;
}
}
7.HTTPS的支持(因为微信小程序要求必须是HTTPS)
7.1CA证书申请
7.2域名绑定
8.集成SSL证书
9.阿里云部署后端
10.编译部署
11.完整项目代码git仓库地址
12.项目后续扩展
上述只是简单的实现了微信小程序的调查问卷功能: 后期项目迭代增加需求: 1.通过mybaits依赖增加mysql数据库支持 2.增加大数据分析大屏数据展示
12.1BigData实现
我们通过kafkaProducer把数据发送到topic上面,但是数据并未做任何分析和展示,后期我们需要做一个数据大屏,来统计分析文件结果,并直观展示.
架构:kafkaProducer->consumer->hbase->sparkSQL->hbase->Kylin(数据展示)