1.建立项目
生成项目:https://start.spring.io
上面的网站有时候会很慢,进不去可以使用阿里的网站:https://start.aliyun.com/
使用idea生成项目 File——New——Project,可以看到也是从这个网站下载的模板
点击Next在这个页面配置相关的环境
点击Next给项目命名,点击Finish完成
出现问题:
原因是springboot 和 jdk的版本不同
网站上下载的项目jdk是1.8,idea中使用的jdk是11,将idea中的jdk换成1.8就行
菜单栏 File —— Project Structure
如果出现大量找不到相关方法的错误,很可能是gradle的依赖没有下载好,建议使用国内阿里的镜像下载
打开build.gradle文件,设置好后重新构建项目
repositories {
//mavenCentral() 注释掉这一行,这是从国外maven库下载
maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }
maven { url 'https://maven.aliyun.com/repository/google' }
maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' }
maven { url 'https://maven.aliyun.com/repository/jcenter'}
}
相关依赖
天气请求服务:有两种方式,一种是根据城市的名称,一种是根据城市的id,若没有该城市天气信息返回 {“status”:1002,“desc”:“no data”} json字符串
http://wthrcdn.etouch.cn/weather_mini?city=北京
http://wthrcdn.etouch.cn/weather_mini?citykey=101010100
根据返回的json字符串,建立json的实例对象,注意名字要和json相同,不然后面会转化出错
{
"data":{
"yesterday":{
"date":"12日星期日",
"high":"高温 22℃",
"fx":"东北风",
"low":"低温 16℃",
"fl":"<![CDATA[3-4级]]>",
"type":"多云"
},
"city":"北京",
"forecast":[
{"date":"13日星期一",
"high":"高温 24℃",
"fengli":"<![CDATA[<3级]]>",
"low":"低温 15℃",
"fengxiang":"无持续风向",
"type":"多云"},
{"date":"14日星期二",
"high":"高温 23℃",
"fengli":"<![CDATA[<3级]]>",
"low":"低温 16℃",
"fengxiang":"无持续风向",
"type":"阴"},
{"date":"15日星期三",
"high":"高温 24℃",
"fengli":"<![CDATA[<3级]]>",
"low":"低温 18℃",
"fengxiang":"无持续风向",
"type":"阴"},
{"date":"16日星期四",
"high":"高温 26℃",
"fengli":"<![CDATA[<3级]]>",
"low":"低温 20℃",
"fengxiang":"无持续风向",
"type":"多云"},
{"date":"17日星期五",
"high":"高温 27℃",
"fengli":"<![CDATA[<3级]]>",
"low":"低温 21℃",
"fengxiang":"无持续风向",
"type":"多云"}],
"ganmao":"各项气象条件适宜,无明显降温过程,发生感冒机率较低。",
"wendu":"19"
},
"status":1000,
"desc":"OK"
}
接下来就是需要的一些依赖包,在gradle中配置:
implementation 'org.springframework.boot:spring-boot-starter-web'
//Httpclient
implementation 'org.apache.httpcomponents:httpclient:4.5.12'
// redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
//Quartz定时器
implementation 'org.springframework.boot:spring-boot-starter-quartz'
//Spring Boot Thymeleaf Starter依赖
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
//测试模块
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
implementation 'junit:junit:4.12'
Model层
WeatherResponse .java
package com.zwt.weather.model;
import java.io.Serializable;
public class WeatherResponse implements Serializable {
private Weather data;
private String status;
private String desc;
// 省略Getter、Setter和toString方法
}
Weather.java
package com.zwt.weather.model;
import java.io.Serializable;
import java.util.List;
public class weather implements Serializable {
private Yesterday yesterday;
private String city;
private List<Forecast> forecast;
private String ganmao;
private String wendu;
// 省略Getter、Setter和toString方法
}
Yesterday.java
package com.zwt.weather.model;
import java.io.Serializable;
public class Yesterday implements Serializable {
private String date;
private String high;
private String fx;
private String low;
private String fl;
private String type;
// 省略Getter、Setter和toString方法
}
Forecast.java
package com.zwt.weather.model;
import java.io.Serializable;
public class Forecast implements Serializable {
private String date;
private String high;
private String fengli;
private String low;
private String fengxiang;
private String type;
// 省略Getter、Setter和toString方法
}
Service层
之后就是根据外部api获取数据,属于service层的内容
定义接口WeatherDataService.java
package com.zwt.weather.service;
import com.zwt.weather.model.WeatherResponse;
public interface WeatherDataService {
/**
* 根据城市的id查询天气数据
* @param cityId
* @return com.zwt.weather.model.WeatherResponse
*/
WeatherResponse getDataByCityId(String cityId);
/**
* 根据城市的名称查询天气数据
* @param cityName
* @return com.zwt.weather.model.WeatherResponse
*/
WeatherResponse getDataByCityName(String cityName);
}
定义接口实现类
package com.zwt.weather.service;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zwt.weather.model.WeatherResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.concurrent.TimeUnit;
/**
* WeatherDataService实现类
* @since 2020-4-13
* @author zwt
*/
@Service
public class WeatherDataServiceImpl implements WeatherDataService{
private final static Logger logger = LoggerFactory.getLogger(WeatherDataServiceImpl.class);
private static final String WEATHHER_UIL = "http://wthrcdn.etouch.cn/weather_mini?";
@Autowired
private RestTemplate restTemplate; // 注入Httpclient客户端,用于http请求
@Autowired
private StringRedisTemplate stringRedisTemplate; // 注入操作Redis的类
private static final long TIME_OUT = 10800L; //超时时间设置为3个小时(中国天气网是三个小时一次更新)
@Override
public WeatherResponse getDataByCityId(String cityId) {
/***
* @Description:
* 根据城市编号查询天气情况
* @Return: com.zwt.weather.model.WeatherResponse (json字符串的对象)
* @Author: zwt
* @param cityId: 城市的id
* @Date: 2020-04-13
*/
//拼接http请求url
String url = WEATHHER_UIL + "citykey=" + cityId;
WeatherResponse resp = this.GetWeather(url);
if (resp == null){
//TODO
return null;
}
return resp;
}
@Override
public WeatherResponse getDataByCityName(String cityName) {
/**
* @Description:
* 根据城市名称查询天气情况
* @Return: com.zwt.weather.model.WeatherResponse
* @Author: zwt
* @param cityName: 城市的名称
* @Date: 2020-04-13
*/
String url = WEATHHER_UIL + "city=" + cityName;
WeatherResponse resp = this.GetWeather(url);
if (resp == null){
//TODO
return null;
}
return resp;
}
private WeatherResponse GetWeather(String url){
/***
* @Description:
* 根据url地址请求天气数据,并将json字符串转换成对象
* @Return: com.zwt.weather.model.WeatherResponse
* @Author: zwt
* @param url: 需要请求的url地址
* @Date: 2020-04-13
*/
WeatherResponse resp = null;
//获取到的json字符串
String strBody = null;
//Redis管理
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
//先查找缓存,缓存有数据就从缓存中取出
if(stringRedisTemplate.hasKey(url)){
logger.info("Redis has data,从Redis获取数据");
strBody = ops.get(url);
}else{
logger.info("Redis has not data,使用http请求数据");
//没有缓存
//使用客户端去请求数据
ResponseEntity<String> respString = restTemplate.getForEntity(url, String.class);
//判断状态是否请求成功
if(respString.getStatusCodeValue() == 200){
strBody = respString.getBody();
//判断返回的json字符串是否有数据,中华天气网返回json字符串中的status为1000时为正确请求,否则是错误的请求
if (strBody.indexOf("\"status\":1000")==-1){
logger.info("城市查询数据失败");
return null;
}
//请求成功后写入Redis缓存
logger.info("数据请求成功写入Redis缓存");
ops.set(url, strBody, TIME_OUT, TimeUnit.SECONDS);
}else {
logger.info("数据请求失败");
return null;
}
}
ObjectMapper mapper = new ObjectMapper();
//将json字符串转换成对象
try{
resp = mapper.readValue(strBody, WeatherResponse.class);
} catch (Exception e) {
e.printStackTrace();
}
return resp;
}
}
这里对于Httpclient RestTemplate需要做配置
package com.zwt.weather.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* Rest配置类
* @since 2020-4-13
* @author zwt
*/
@Configuration
public class RestConfiguration {
@Autowired
private RestTemplateBuilder builder;
@Bean
public RestTemplate restTemplate(){
return builder.build();
}
}
出现问题:Redis Windows启动失败
控制台移动到redis文件夹下
输入:redis-server.exe redis.windows.conf
若出现 [10484] 14 Apr 12:17:59.811 # Creating Server TCP listening socket 127.0.0.1:6379: bind: No error
则是启动失败
输入命令:
redis-cli.exe
shutdown
exit
再次输入 redis-server.exe redis.windows.conf
启动成功
Controller 层
WeatherReportController.java
package com.zwt.weather.controller;
import com.zwt.weather.model.WeatherResponse;
import com.zwt.weather.service.WeatherDataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
@Controller
@RequestMapping("/report")
public class WeatherReportController {
@Autowired
private WeatherDataService weatherDataService;
@GetMapping("/cityId/{cityId}")
public ModelAndView getReportByCityId(@PathVariable("cityId") String cityId, Model model){
WeatherResponse cityWeather = weatherDataService.getDataByCityId(cityId);
if (cityWeather==null){
return new ModelAndView("weather/error", "reportModel", model);
}
model.addAttribute("title", "天气预报");
model.addAttribute("cityId", cityId);
model.addAttribute("citiyList", "");
model.addAttribute("report", cityWeather.getData());
return new ModelAndView("weather/report", "reportModel", model);
}
}
定时器Quartz(Utils)
用户请求的时候再去第三方提供的接口拉取,第一个访问的用户会等待较长的时间,不妨做一个定时器每隔一段时间去同步一下数据,用户访问的时候直接从本地拿取就行
gradle配置文件中添加依赖:
//Quartz定时器
implementation ‘org.springframework.boot:spring-boot-starter-quartz’
Quartz分为几大模块:
1、Job
表示一个工作,要执行的具体内容。此接口中只有一个方法
void execute(JobExecutionContext context)
这里使用QuartzJobBean继承于Job,唯一的方法是executeInternal
2、JobDetail
JobDetail表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。
这里通过JobBuilder建立 .withIdentity(“weatherDataSyncJobDetail”)为这个JobDetail起个名字
3、Trigger代表一个调度参数的配置,什么时候去调。
4、Scheduler代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了。
从boot2.0开始,增加了对Quartz的自动装配,以前需要自己处理。从boot2.0开始,增加了对Quartz的自动装配,以前需要自己处理。
package com.zwt.weather.utils;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.quartz.QuartzJobBean;
/**
* 天气数据同步
* @since 2020-4-13
* @author zwt
*/
public class WeatherDataSyncjob extends QuartzJobBean {
private static org.slf4j.Logger logger = LoggerFactory.getLogger(WeatherDataSyncjob.class);
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
//免费的第三方接口有限制每天的访问量,如果过多访问可能会封ip,所以这里只打印输出
Loggers.printInfo(WeatherDataSyncjob.class, "同步天气数据");
//这里所做的就是service中的http请求在同步到Redis数据库中
//城市id数据 链接:https://pan.baidu.com/s/15dMmSuh4bGFtUfAw0wRjLQ 提取码:jqw8
}
}
package com.zwt.weather.config;
import com.zwt.weather.utils.WeatherDataSyncjob;
import org.quartz.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Quartz定时器配置类
* @since 2020-4-13
* @author zwt
*/
@Configuration
public class QuartzConfiguration {
private static final int TIME_OUT = 10800;
// private static final int TIME_OUT = 5;
// 定义JobDetail, 把Job的class类型传入。Spring自动处理
@Bean
public JobDetail weatherDataSyncJobDetail(){
return JobBuilder.newJob(WeatherDataSyncjob.class)
.withIdentity("weatherDataSyncJobDetail")
.storeDurably()
.build();
}
//定义Trigger, 通过Key关联JobDetail。Spring 自动处理。
@Bean
public Trigger weatherDataSyncJonTrigger(){
SimpleScheduleBuilder schedBuilder = SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(TIME_OUT)
.repeatForever();
return TriggerBuilder.newTrigger()
.forJob(weatherDataSyncJobDetail())
.withIdentity("weatherDataSyncJobTrigger")
.withSchedule(schedBuilder)
.build();
}
}
网页使用的是springboot推荐的Thymeleaf
HTML
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- 上述3个meta标签*必须*放在最前面,任何其他内容都*必须*跟随其后! -->
<title>Title</title>
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="row">
<h3 th:text="${reportModel.title}"></h3>
</div>
<div class="row">
<h1 th:text="${reportModel.report.city}"></h1>
</div>
<div class="row">
<p>
当前温度:<span th:text="${reportModel.report.wendu}"></span>
</p>
</div>
<div class="row">
<p>
温馨提示:<span th:text="${reportModel.report.ganmao}"></span>
</p>
</div>
<div class="row">
<div class="col-md-1"></div>
<div class="col-md-2 card" th:each="forecast:${reportModel.report.forecast}">
<div class="card-body">
<p class="card-text" th:text="${forecast.date}"></p>
<p class="card-text" th:text="${forecast.high}"></p>
<p class="card-text" th:text="${forecast.fengli}"></p>
<p class="card-text" th:text="${forecast.low}"></p>
<p class="card-text" th:text="${forecast.fengxiang}"></p>
<p class="card-text" th:text="${forecast.type}"></p>
</div>
</div>
<div class="col-md-1"></div>
</div>
</div>
<!-- jQuery (Bootstrap 的所有 JavaScript 插件都依赖 jQuery,所以必须放在前边) -->
<script src="https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js"></script>
<!-- 加载 Bootstrap 的所有 JavaScript 插件。你也可以根据需要只加载单个插件。 -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/js/bootstrap.min.js"></script>
</body>
</html>
mybatis配置
依赖build.gradle:
//Spring Boot Mybatis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.2'
//mysql驱动
implementation 'mysql:mysql-connector-java:8.0.19'
测试我就用了以前已经创建的数据库来测试
表结构很简单就只有两个字段,id自动增长
application.properties
# 数据库配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# jdbc:mysql://【数据库ip地址】:【数据库端口】/【数据库名字】?serverTimezone=Asia/Shanghai
spring.datasource.url=jdbc:mysql://localhost:3306/worktest?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root
注意:
出现这个警告,说明的很清楚,原来驱动包com.mysql.jdbc.Driver被弃用,使用新的com.mysql.cj.jdbc.Driver就可以
报了一个时区的错误,原因是使用了Mysql Connector/J 6.x以上的版本 MySQL jdbc 6.0 版本以上必须配置 serverTimezone=UTC 参数,但是只有这样配置用java代码插入到数据库时间的时候会出现问题:时间与现在时区的时间不匹配,原因是UTC代表的是全球标准时间 ,但是我们使用的时间是北京时区也就是东八区,领先UTC八个小时,配置成下面的就行
serverTimezone=Asia/Shanghai
需要文件
一个model、mapper、service、controller
model:一个与数据库表对应的实例
public class Educational {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
mapper:定义查询语句(注解的方式)
import com.zwt.weather.model.Educational;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface EducationalMapper {
@Select("SELECT * FROM t_educational WHERE id = #{id}")
Educational selectEducational(int id);
}
service:注入mapper
@Service
public class EducationalServiceImpl implements EducationalService {
@Autowired
private EducationalMapper educationalMapper;
@Override
public Educational getEducationalById(int id) {
return educationalMapper.selectEducational(id);
}
}
controller:定义访问的路径
@RestController
@RequestMapping("/edu")
public class EducationalController {
@Autowired
private EducationalService educationalService;
@GetMapping("/id/{id}")
public String getEducational(@PathVariable("id") String id){
Educational educational = educationalService.getEducationalById(Integer.parseInt(id));
return "hello" + educational.getId() + "==" + educational.getName();
}
}
注意在
Application.java中需要配置mybatis的扫描路径
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@MapperScan("com.zwt.weather.mapper") // 配置mybatis的扫描路径
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
访问地址就能看到结果:http://localhost:8080/edu/id/3