完善论坛首页以及天气接口对接

This commit is contained in:
柏码の讲师 2023-10-06 13:37:54 +08:00
parent ca442c7e67
commit 1ae63df5de
10 changed files with 190 additions and 41 deletions

View File

@ -4,6 +4,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
@ -12,6 +13,11 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();

View File

@ -0,0 +1,25 @@
package com.example.controller;
import com.example.entity.RestBean;
import com.example.entity.vo.response.WeatherVO;
import com.example.service.WeatherService;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
//论坛相关接口都在这里
@RestController
@RequestMapping("/api/forum")
public class ForumController {
@Resource
WeatherService service;
@GetMapping("/weather")
public RestBean<WeatherVO> weather(double latitude, double longitude){
WeatherVO weatherVO = service.fetchWeather(latitude, longitude);
return weatherVO != null ?
RestBean.success(weatherVO) : RestBean.failure(400, "获取地理位置信息失败!");
}
}

View File

@ -0,0 +1,12 @@
package com.example.entity.vo.response;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
@Data
public class WeatherVO {
JSONObject now;
JSONObject location;
JSONArray hourly;
}

View File

@ -0,0 +1,7 @@
package com.example.service;
import com.example.entity.vo.response.WeatherVO;
public interface WeatherService {
WeatherVO fetchWeather(double latitude, double longitude);
}

View File

@ -0,0 +1,84 @@
package com.example.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.example.entity.vo.response.WeatherVO;
import com.example.service.WeatherService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
@Slf4j
@Service
public class WeatherServiceImpl implements WeatherService {
@Resource
StringRedisTemplate template;
@Resource
RestTemplate rest;
@Value("${spring.weather.key}")
String key;
@Override
public WeatherVO fetchWeather(double latitude, double longitude) {
return fetchFromCache(latitude, longitude);
}
private WeatherVO fetchFromCache(double latitude, double longitude) {
JSONObject geo = this.decompressStringToJSON(rest.getForObject(
"https://geoapi.qweather.com/v2/city/lookup?location="+longitude+","+latitude+"&key="+key, byte[].class));
if(geo == null) return null;
JSONObject location = geo.getJSONArray("location").getJSONObject(0);
int id = location.getInteger("id");
String key = "weather:"+id;
String cache = template.opsForValue().get(key);
if(cache != null)
return JSONObject.parseObject(cache).to(WeatherVO.class);
WeatherVO weather = this.fetchFromAPI(id, location);
if(weather == null)
return null;
template.opsForValue().set(key, JSONObject.from(weather).toJSONString(), 1, TimeUnit.HOURS);
return weather;
}
private WeatherVO fetchFromAPI(int id, JSONObject location) {
WeatherVO vo = new WeatherVO();
vo.setLocation(location);
JSONObject now = this.decompressStringToJSON(rest.getForObject(
"https://devapi.qweather.com/v7/weather/now?location="+id+"&key="+key, byte[].class));
if(now == null) return null;
vo.setNow(now.getJSONObject("now"));
JSONObject hourly = this.decompressStringToJSON(rest.getForObject(
"https://devapi.qweather.com/v7/weather/24h?location="+id+"&key="+key, byte[].class));
if(hourly == null) return null;
vo.setHourly(new JSONArray(hourly.getJSONArray("hourly").stream().limit(5).toList()));
return vo;
}
private JSONObject decompressStringToJSON(byte[] data) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
try {
GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(data));
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = gis.read(buffer)) != -1)
stream.write(buffer, 0, bytesRead);
gis.close();
stream.close();
} catch (IOException ignored) {
return null;
}
return JSONObject.parseObject(stream.toString());
}
}

View File

@ -43,3 +43,5 @@ spring:
endpoint: 'http://localhost:9000'
username: 'minio'
password: 'password'
weather:
key: 7abbc24d3b4443b597a3b3c676e0f221

View File

@ -47,3 +47,5 @@ spring:
endpoint: 'http://localhost:9000'
username: 'minio'
password: 'password'
weather:
key: 7abbc24d3b4443b597a3b3c676e0f221

View File

@ -10,6 +10,10 @@
<style>
body {
margin: 0;
font-family: system-ui, -apple-system, "Segoe UI",
Roboto, "Helvetica Neue", Arial, "Noto Sans",
"Liberation Sans", sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
}
</style>
</head>

View File

@ -1,59 +1,34 @@
<script setup>
const data = {
location: '中国 四川省',
city: '成都市',
temperature: 22,
icon: '101',
text: '多云',
hourly: [
{
icon: '100',
temperature: 22,
time: '1时'
}, {
icon: '102',
temperature: 30,
time: '2时'
}, {
icon: '101',
temperature: 21,
time: '3时'
}, {
icon: '103',
temperature: 25,
time: '4时'
}, {
icon: '104',
temperature: 24,
time: '5时'
}
]
}
defineProps({
data: Object
})
</script>
<template>
<div>
<div style="display: flex;justify-content: space-between;margin: 10px 20px">
<div style="height: 160px" v-loading="!data.success"
element-loading-text="正在加载天气信息...">
<div style="display: flex;justify-content: space-between;margin: 10px 20px"
v-if="data.success">
<div style="font-size: 45px">
<i :class="`qi-${data.icon}-fill`"></i>
<i :class="`qi-${data.now.icon}-fill`"></i>
</div>
<div style="font-weight: bold;text-align: center">
<div style="font-size: 25px">{{data.temperature}}</div>
<div style="font-size: 15px">{{data.text}}</div>
<div style="font-size: 25px">{{data.now.temp}}</div>
<div style="font-size: 15px">{{data.now.text}}</div>
</div>
<div style="margin-top: 13px">
<div style="font-size: 15px">{{data.city}}</div>
<div style="font-size: 14px;color: grey">{{data.location}}</div>
<div style="font-size: 15px">{{data.location.country + ' ' + data.location.adm1}}</div>
<div style="font-size: 14px;color: grey">{{ `${data.location.adm2} ${data.location.name}` }}</div>
</div>
</div>
<el-divider style="margin: 10px 0"/>
<div style="display: grid;grid-template-columns: repeat(5, 1fr);text-align: center">
<div v-for="item in data.hourly">
<div style="font-size: 13px">{{item.time}}</div>
<div style="font-size: 13px">{{new Date(item.fxTime).getHours() + ' 时'}}</div>
<div style="font-size: 23px">
<i :class="`qi-${item.icon}-fill`"></i>
</div>
<div style="font-size: 12px">{{item.temperature}}</div>
<div style="font-size: 12px">{{item.temp}}</div>
</div>
</div>
</div>

View File

@ -1,13 +1,41 @@
<script setup>
import LightCard from "@/components/LightCard.vue";
import {Calendar, CollectionTag, EditPen, Link} from "@element-plus/icons-vue";
import {computed} from "vue"
import {computed, reactive} from "vue"
import Weather from "@/components/Weather.vue";
import {get} from "@/net";
import {ElMessage} from "element-plus";
const weather = reactive({
now: {},
location: {},
hourly: [],
success: false
})
const today = computed(() => {
const date = new Date()
return `${date.getFullYear()}${date.getMonth()}${date.getDay()}`
})
navigator.geolocation.getCurrentPosition(position => {
const latitude = position.coords.latitude
const longitude = position.coords.longitude
get(`/api/forum/weather?latitude=${latitude}&longitude=${longitude}`, data => {
Object.assign(weather, data)
weather.success = true
})
}, error => {
console.info(error)
ElMessage.warning('获取位置信息超时,请检测浏览器设置')
get(`/api/forum/weather?latitude=39.90499&longitude=116.40529`, data => {
Object.assign(weather, data)
weather.success = true
})
}, {
timeout: 3000,
enableHighAccuracy: true
})
</script>
<template>
@ -38,7 +66,7 @@ const today = computed(() => {
天气信息
</div>
<el-divider style="margin: 10px 0"/>
<weather/>
<weather :data="weather"/>
</light-card>
<light-card style="margin-top: 10px">
<div style="display: flex;justify-content: space-between;color: grey;font-size: 14px">
@ -91,4 +119,8 @@ const today = computed(() => {
cursor: pointer;
}
}
.dark .edit-topic{
background-color: #282828;
}
</style>