完善论坛首页以及天气接口对接
This commit is contained in:
parent
ca442c7e67
commit
1ae63df5de
@ -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();
|
||||
|
@ -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, "获取地理位置信息失败!");
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package com.example.service;
|
||||
|
||||
import com.example.entity.vo.response.WeatherVO;
|
||||
|
||||
public interface WeatherService {
|
||||
WeatherVO fetchWeather(double latitude, double longitude);
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -43,3 +43,5 @@ spring:
|
||||
endpoint: 'http://localhost:9000'
|
||||
username: 'minio'
|
||||
password: 'password'
|
||||
weather:
|
||||
key: 7abbc24d3b4443b597a3b3c676e0f221
|
||||
|
@ -47,3 +47,5 @@ spring:
|
||||
endpoint: 'http://localhost:9000'
|
||||
username: 'minio'
|
||||
password: 'password'
|
||||
weather:
|
||||
key: 7abbc24d3b4443b597a3b3c676e0f221
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user