SpringBoot
内容管理
项目碎碎念 ---- 验证码、地图、IP、天气等零碎服务
不积跬步无以至千里,一个完美的项目总是不断增加功能,升级迭代,优化设计产生的,目前的基础的SpringBoot开发是很easy的,但是很多其他的细节就是后期需要优化的部分,诸如缓存、持久化存储、高并发,多线程、异常等 【Nginx负载均衡、CDN缓存(js等静态)、限流(IP,变灰)、削峰、redis缓存【分布式🔒】、亿级数据Mycat分库分表】
后面cfeng将走上分布式和性能优化之路,fighting… 这之前先记录一些常规操纵【使用了还是需要记录下来以防忘记】: 结合security和redis进行图形验证码登录、IP的锁定、地图等
important: filter level在controller level之前,如果security过滤器配置了认证,controller层面不能permittAll
验证码放在什么位置?验证码是有过期时间的,按照knowledge来说应该存储在redis中,因为其expire完美的适应这个功能,之前在Security部分提过,分布式的服务中,为了保证nginx分发后各机器的状态一致,保证其中一台服务器宕机后用户保持登录状态,就需要使用spring Session, spirng session就是利用外部存储(redis等)作为session存储用户信息,可以存储token,权限和token 验证码等信息
要使用Spring Seesion,需要引入Redis的依赖,同时在security配置文件中注册外部的session告知framework使用
redis:
port: 6379
host: localhost #连接远程redis
database: 1
timeout: 1000
password:
jedis:
pool:
max-active: 10
max-idle: 8
min-idle: 1
max-wait: 1
##配置spring session,自动配置的,注入属性后创建filter过滤器,将HttpSessin转换为Spring session
session:
timeout: 300 #会话超时时间,默认后缀为秒
store-type: redis #设置session的外部容器为redis
redis:
flush-mode: on_save #ON SAVE 还是IMMEDIATE 刷新策略
namespace: spring:session #存储session的命名空间
------- config---
.and()
.sessionManagement()
.maximumSessions(1) //并发上限1
.maxSessionsPreventsLogin(false) //true为阻止,false为提出旧的
.expiredSessionStrategy(new SessionInfoExiredListener(objectMapper)) //session失效监听的处理程序
.sessionRegistry(sessionRegistry()); //添加注册器
这里的Session配置可能出现不生效的情况? 解决办法:
并发的管理就是查找该用户的所有的session,超出配置的数量就会踢出,但是在同一个浏览器是可以开多个页面登录的,不同的浏览器不能同时登录
可能的原因是没有设置expireUrl或者相关的处理strategy
Session共享就是在新的机器上面部署redis,同时要开启EnableSessionRedis的注解,同时就可以实现session共享
集成Kaptcha实现验证码
为了防止机器的恶意注册,现在的验证方式已经越来越丰富,比如滑块验证码等等,we可以借助Kaptcha这个验证码生成工具
可以配置多样化的验证码,并且以图片形式显示,不能进行复制和粘贴,进一步保证安全
引入kaptcha依赖
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
</dependency>
编写一个常量类,定义验证码存储的key等信息
public interface KaptchaConstant {
//定义captcha存储session中的key
String CAPTCHA_SESSION_KEY = "captcha_key";
String SMS_SESSION_KEY = "sms_key";
}
验证码放在session中管理,过期时间封装在code中,使用LocalDateTime进行判断
* 验证码是作为一个info放在用户session中,可以直接放在Redis中,但是这里直接设置手动存储
*/
public class CaptchaCodeVo {
//验证码
private String code;
//过期时间s
private LocalDateTime expireTime;
public CaptchaCodeVo(String code,int expireAfterSeconds) {
this.code = code;
//过期时间为创建之后60s
this.expireTime = LocalDateTime.now().plusSeconds(expireAfterSeconds);
}
//是否过期,晚于过期时间旧过期
public boolean isExpired() {
return LocalDateTime.now().isAfter(expireTime);
}
public String getCode() {
return this.code;
}
}
编写主要的处理器类和过滤器类装载
* 主要就是借助Kaptcha工具类生成动态的图片验证码,配合security进行验证登录,验证码放在redis的session中
*/
@RestController
@RequiredArgsConstructor
public class KaptchaController {
//配置的kaptcha工具类
private final DefaultKaptcha defaultKaptcha;
/**
* 前台请求验证码,返回资源,使用httpResponse返回
*/
@GetMapping("/kaptcha")
public void getKaptchaImage(HttpSession httpSession, HttpServletResponse response) throws IOException {
//设置相应的格式和类型
response.setDateHeader("Expires",0);
response.setHeader("Cache-control","no-store, no-cache, must-revalidate");
response.addHeader("Cache-Control", "post-check=0, pre-check=0");
response.setHeader("Pragma", "no-cache");
response.setContentType("image/jpeg");
//创建验证码
String capText = defaultKaptcha.createText();
//将验证码放入session,会自动由Spring Session受理
httpSession.setAttribute(KaptchaConstant.CAPTCHA_SESSION_KEY,new CaptchaCodeVo(capText,60)); //60s
//返回响应
BufferedImage bufferedImage = defaultKaptcha.createImage(capText);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(bufferedImage,"jpg",out);
try {
out.flush();
} finally {
out.close();
}
}
}
@Component //这里不使用普通的过滤器注解,直接component即可
public class KaptchaCodeFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
String uri = request.getServletPath();
//post登录提交的位置
if("/authentication/form".equals(uri) && request.getMethod().equalsIgnoreCase("post")) {
//用户输入的验证码
String captchaInRequest = request.getParameter("captchaCode").trim();
//session中的
CaptchaCodeVo captchaInSession = (CaptchaCodeVo)request.getSession().getAttribute(KaptchaConstant.CAPTCHA_SESSION_KEY);
if(StringUtils.isEmpty(captchaInRequest)) {
throw new SessionAuthenticationException("验证码不能为空");
}
if(captchaInSession == null) {
throw new SessionAuthenticationException("验证码不存在");
}
if(captchaInSession.isExpired()) {
//从用户session中删除
request.getSession().removeAttribute(KaptchaConstant.CAPTCHA_SESSION_KEY);
throw new SessionAuthenticationException("验证码已过期");
}
if(! captchaInRequest.equalsIgnoreCase(captchaInSession.getCode())) {
throw new SessionAuthenticationException("验证码错误");
}
}
filterChain.doFilter(request,response);
}
}
之后就是简单配置一下config,和前端的代码, 放行此路径
http
//添加过滤器
.addFilterBefore(kaptchaCodeFilter, UsernamePasswordAuthenticationFilter.class)
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().antMatchers("/js/**", "/img/**","/css/**","/editorMd-master/**","/login.html","/register.html","/login/**","/layui/**","/kaptcha");
在前台表单处添加一个img和验证码提交的input即可
//js中给出点击刷新 --- 重新获取验证码的方法
<div class="item">
<input type="text" name="captchaCode" id="captchaCode"/>
<img src="/kaptcha" id="kaptcha" width="110px" height="40px"/>
<label>验证码</label>
</div>
//点击刷新
window.onload = function () {
var kaptchaImg = document.getElementById("kaptcha");
kaptchaImg.onclick = function () {
kaptchaImg.src = "/kaptcha?" + Math.floor(Math.random() * 100)
}
};
前端audio音乐播放 ⚠
这里提一下前端的播放,使用的audio标签,首页的背景音乐 【 autoplay自动 loop循环 controls按钮控制 muted静音 preload预加载】
需要注意一般采用外网播放,这里的src如果直接链接static下面的MP3失败,因为编译的时候会破环文件不能播放; 比如在static下面同时放置img和music,访问img成功,但是music失败,这是因为项目都是访问的编译后的资源, 在编译到target之后,音频文件遭到破环,导致访问失败, 解决办法 :
将原来的音乐视频资源文件替换target或者打包后的其中的文件, 这样就可以访问成功
- 推荐使用直链的方式:设置src为音乐的具体的链接,网址查询: 音乐直链搜索|音乐在线试听 - by 刘志进实验室 (liuzhijin.cn) ; 直接搜索放置在src
JS控制音乐的播放和暂停直接获取到audio元素,调用其pause()和play()方法即可
其余的解决办法就是替换target中的文件,或者后台进行流转换再使用
集成ip2region根据IP获取位置
在cfeng的项目构建中,需要根据前台用户访问的ip获取用户的具体的位置,为了不占用网络资源【在线访问会耗费】,所以就集成ip2fregion工具,安装其离线ip对照包进行位置的获取
<!-- ip to region -->
<dependency>
<groupId>org.lionsoul</groupId>
<artifactId>ip2region</artifactId>
<version>1.7.2</version>
</dependency>
离线数据db保存,ip2region将已知的ip和城市信息的对应关系保存在数据库中,文件为ip2region.db,能够满足基本的项目需求,将其添加到项目的static文件夹下面以供使用【下载去github上面拉取data即可】
IP查询基本的工具类RegionUtil
需要注意jar读取文件,创建一个临时目录,读取不到时加上临时目录,借助apache的commons-io完成输入流到文件的转化
package com.Cfeng.XiaohuanChat.util;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.lionsoul.ip2region.DataBlock;
import org.lionsoul.ip2region.DbConfig;
import org.lionsoul.ip2region.DbSearcher;
import org.lionsoul.ip2region.Util;
import org.springframework.core.io.ClassPathResource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Objects;
/**
* @author Cfeng
* @date 2022/8/21
*
* 根据IP离线查询地址, 调用IpRegion2Util进行查询
*/
@Slf4j
public class RegionUtil {
//jar临时文件夹
private static final String JAVA_TEMP_DIR = "java.io.tmpdir";
static DbConfig config = null;
static DbSearcher searcher = null;
//使用一个静态代码块让第一次加载的时候就读取资源,使用class.getResouce
static {
//项目中直接在static下面 即/ip2region.db
//jar无法读取文件,需要复制创建临时文件,绝对path
try {
String path = RegionUtil.class.getResource("/static/ip2region.db").getPath();
File file = new File(path);
//文件不存在就加上jar的临时目录重新读取到file中
if(!file.exists()) {
String tmpDir = System.getProperties().getProperty(JAVA_TEMP_DIR);
path = tmpDir + "ip2region.db";
file = new File(path);
ClassPathResource classPathResource = new ClassPathResource("static" + File.separator + "ip2region.db");
//通过流获取File
InputStream resourceStream = classPathResource.getInputStream();
if(resourceStream != null) {
FileUtils.copyInputStreamToFile(resourceStream,file);
}
}
config = new DbConfig(); //region的config
searcher = new DbSearcher(config,path); //创建seacher
log.info("bean create success, {},{}",config,searcher);
} catch (Exception e) {
log.error("init ip region error {}",e);
}
}
/**
* 解析IP地址
*/
public static String getRegion(String ip) {
try {
if(Objects.isNull(searcher) || StringUtils.isNullOrEmpty(ip)) {
log.error("Dbsearcher is null");
return "";
}
long startTime = System.currentTimeMillis();
// 查询算法,使用反射动态选择方法
int algorithm = DbSearcher.MEMORY_ALGORITYM;
Method method = null;
switch (algorithm)
{
case DbSearcher.BTREE_ALGORITHM:
method = searcher.getClass().getMethod("btreeSearch", String.class);
break;
case DbSearcher.BINARY_ALGORITHM:
method = searcher.getClass().getMethod("binarySearch", String.class);
break;
case DbSearcher.MEMORY_ALGORITYM:
method = searcher.getClass().getMethod("memorySearch", String.class);
break;
}
DataBlock dataBlock = null;
if (Util.isIpAddress(ip) == false)
{
log.warn("warning: Invalid ip address");
}
dataBlock = (DataBlock) method.invoke(searcher, ip);
String result = dataBlock.getRegion();
long endTime = System.currentTimeMillis();
log.debug("regionSearch use time[{}] result[{}]", endTime - startTime, result);
return result;
} catch (Exception e) {
log.error("error:{}",e);
}
return "";
}
}
注意文件的获取方法: XXX.class.getResource(); 这里是从编译后的target目录开始寻找,/代表target一级,所以这里就是/static/xxx
使用这个工具类就可以查询出最原始的数据了: 0|0|0|内网IP|内网IP; 比如这样子
接下来为了更加方便的操作地址,对操作的地址进一步封装,创建AddressUtils
package com.Cfeng.XiaohuanChat.util;
import com.mysql.cj.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
/**
* @author Cfeng
* @date 2022/8/21
* 地址查询类, 内网地址不详细,外网地址进一步处理
*/
@Slf4j
public class AddressUtil {
//未知地址
public static final String UNKNOWN = "XX XX";
/**
* 根据IP获取真实地址
*/
public static String getRealAddressByIP(String ip)
{
String address = UNKNOWN;
// 内网不查询
if (IpUtils.internalIp(ip))
{
return "内网IP";
}else {
try
{
String rspStr = RegionUtil.getRegion(ip);
if (StringUtils.isNullOrEmpty(rspStr))
{
log.error("获取地理位置异常 {}", ip);
return UNKNOWN;
}
String[] obj = rspStr.split("\\|");
String region = obj[2];
String city = obj[3];
//地区 城市
return String.format("%s %s", region, city);
}
catch (Exception e)
{
log.error("获取地理位置异常 {}", e);
}
}
return address;
}
}
这里面还是使用到了更具体的IpUtil,这里简单给一部分,包括获取ip,判断是否内网IP
* 该工具类可以从请求中提取ip,
*/
public class IpUtils {
/**
* 获取客户端IP
* 从请求头中提取IP
* @param request 请求对象
* @return IP地址
*/
public static String getIpAddr(HttpServletRequest request)
{
if (request == null)
{
return "unknown";
}
String ip = request.getHeader("x-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Forwarded-For");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getHeader("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
{
ip = request.getRemoteAddr();
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
}
/**
* 检查是否为内部IP地址
*
* @param ip IP地址
* @return 结果
*/
public static boolean internalIp(String ip)
{
byte[] addr = textToNumericFormatV4(ip);
return internalIp(addr) || "127.0.0.1".equals(ip);
}
使用最上面的RegionUtil就可以查询出最原始的数据,通过addressUtil就可以查询到国内的 身份 + 城市
IpUtil主要提供的就是对于IP的操作
上面就是后台的操作,获取IP就是提取请求头中的参数,如果没有对应的参数,那就直接getRemoteAdr即可
前端的地址获取
之前cfeng介绍了很多前端js,包括editormd、stomp、websocket、socketJs、live2d等
而前端的地理位置的获取使用的是navgitor.geolocation插件
- 使用其getCurrentPosition就可以获取用户当前的地理位置信息 (successhandler,errorXX,options); success中包括两个属性coords和timestamp ; coords包括精确的accuracy,经度longitude,纬度latitude,海拔altitude,海报高度精确度altitudeAccuracy,朝向heading、速度speed等; 失败的回调包括错误码和错误信息,而positionOptions为JSON格式的餐宿,设置maximumAge【重新获取位置的间隔时间】 timeout 超时时间,enableHighcuracy启用高精度
- 除此之外还可以使用watchCurrentPostion和clearWatch配合使用; 新版本的浏览器一般都是支持geolocation的【胖客户端】
//获取当前经纬度 if(navigator.geolocation)就可以判断是否支持
showLocation = function(position) {
//成功的回调函数
var longitude = position.coords.longitude;
var latitude = position.coords.latitude;
alert(longitude + ":" + latitude);
$.get("/sys/getAddress",{latitude:latitude,longitude:longitude},function (response) {
if(response.code == 0) {
var address = response.data;
$("#position").text(address);
}
});
}
errorHandler = function(error) {
if(error.code == 1) {
alert("access denied");
} else if(error.code == 2) {
alert("position is not avaliable");
}
}
getLocation = function() {
//navigator 最新插件 geolocation是否可以使用,可以才自动定位
if(navigator.geolocation) {
var options = {timeout: "6000"};
//使用插件获取位置
navigator.geolocation.getCurrentPosition(showLocation,errorHandler,options);
} else {
aler("对不起,你的浏览器不支持geolocation插件")
}
}
geolocation是保护用户隐私的,只有用户允许才会获取位置信息
获取经纬度传递给后台由后台的百度的进行解析即可获得数据,处理只是将其转为String类型
使用百度API将经纬度转为地理位置
获取到经纬度之后,可以直接将经纬度提交到后台,之后由后台将借助百度地图进行快捷的经纬度查询
首先我们需要在百度地图注册成为开发者,创建个人应用,通过应用的ak来进行服务的调用,申请入口: 百度地图开放平台 | 百度地图API SDK | 地图开发 (baidu.com) ; 创建之后就可以按照规范对该地址进行请求, 主要就是ak、location(经纬度)和经纬度类型
@Slf4j
public class BaiduMapGeoUtil {
/**
* 百度地图调用
*/
public final static String BAIDU_MAP_AK = "9XjkjlhhgkajgF7VmYyGMjjgkajgkahgsawvhaC0hGHt"; //这里就是申请的ak
/**
* 根据经纬度得出位置
* @param longitude 经度
* @param latitude 纬度
* @return 位置 String, 封装的json字符串addressInfo
*/
public static String getAddressInfoByLngAndLat(String longitude, String latitude) {
String position = ""; //返回的结果对象JSon字符串
String location = latitude + "," + longitude;
//百度URL 相关的参数coordtype :bd09ll(百度经纬度坐标)、bd09mc(百度米制坐标)、gcj02ll(国测局经纬度坐标,仅限中国)、wgs84ll( GPS经纬度); ak就是访问
String url ="http://api.map.baidu.com/reverse_geocoding/v3/?ak="+BAIDU_MAP_AK+"&output=json&coordtype=wgs84ll&location="+location;
//访问baidu的服务,传入ak、coordtype、location
try {
String result = loadUrl(url);
//使用fastJSON进行对象转化JSON
JSONObject res = JSONObject.parseObject(result);
log.info("具体的位置信息: {}" ,res.toString());
//该对象的status属性为状态码,0为成功,result为数据
if(Objects.equals("0",String.valueOf(res.get("status")))) {
//数据对象
JSONObject data = JSONObject.parseObject(String.valueOf(res.get("result")));
//AddressComponent对象就是最终封装的位置信息
position = String.valueOf(data.get("addressComponent"));
}
} catch (Exception e) {
log.error("未能找到相匹配的经纬度,请检查");
}
return position;
}
//通过url获取结果String
private static String loadUrl(String url) {
StringBuilder stringBuilder = new StringBuilder();
try {
//创建链接地址
URL region = new URL(url);
//访问链接URL地址
URLConnection connection = region.openConnection();
//读取url缓冲字符流
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(),"UTF-8"));
//读取流写入字符串
String inputLine = null;
//读取到末尾
while((inputLine = reader.readLine()) != null) {
stringBuilder.append(inputLine);
}
reader.close();
} catch (Exception e) {
log.error("出错了:{}",e.getMessage());
}
return stringBuilder.toString();
}
}
loadURL的作用就是加载URL访问之后将结果封装为一个String对象,借用缓冲的字符流来进行结果的读取,保存在StringBuilder中,最后再变为String; StringBuilder线程不安全,但是构建迅速
之后就可以配合前台传入的经纬度进行地理位置的较为精确的定位【IP定位不够精确】
需要注意,开发时对于不同的端需要不同的应用,服务端的就是创建server供后台的接口进行访问,而前端浏览器需要创建浏览器应用进行访问; 创建浏览器后也要设置白名单同时将ak引入script
前台调用百度地图实现地图展示【google退出,需要代理】
简单提一下谷歌地图的使用:
如果调用谷歌地图,需要使用其API,首先也是需要注册一个API密钥ak,但是需要注意:
- 加入API页面没有发布,本地使用,可以不使用密钥,随便使用字符串代替即可【上面百度】
- API密钥只是对当前网站目录或者域有效,不同的网站需要不同的ak
其次就是页面利用script引入谷歌地图: http://ditu.google.com/maps?file=api&hl=zh-CN&v=2&key=abcdefg
- ditu.google.com: .cn也可以; 需要在地图上显示中国之外区域,使用.com
- file=api: 请求js的固定格式
- hl = zh-CN: 设定地图除了地图图片之外的各种声明的文本的语言版本language,默认是英语
- v=2: 引入的类库的版本号,由.s 和.x 还有缺省和具体四种,.s最慢最稳定, 这里2介于.s和.x之间【当前主版本】
- key=abcdefg: 注册的ak, 这里开发环境随意
设置地图类型的方法:
- enableDragging(): 设置地图可以拖动 disalbeDragging draggingEnabled—返回是否
- enableInfoWindow: 设置地图信息的窗口可弹出 ~~
- enableDoubleClickZoom: 设置可以双击缩放地图 ~~
- enableContinuousZoom: 设置可以连续平滑缩放
- enableScrollWheelZoom: 鼠标滚轮控制缩放
- isLoaded 如果已经被setCenter()初始化,,返回true
显示个人位置
- 首先在页面创建一个map的div用来防止地图
- 将Google Maps API添加到项目中,如果想要gps定位,那么就要sensor为true,http://maps.google.com/maps/api/js?sensor=false获取js
- 加载之后创建一个google.maps.LatLng实例,保存在postion中,传入经纬度,当前的位置
- 设置缩放级别zoom,地图中心位置【LatLng】,地图显示方式google.maps.MapTypeId,包括ROAD公路路线,TERRAIN公路名称和地势,HYBRID:卫星地图和公路路线叠加,SATELLITE卫星地图
百度地图,首先就是申请密钥,上面已经提过,不再赘述
- 首先引入百度地图的script
<script type="text/javascript" src="http://api.map.bai
du.com/api?v=3.0&ak=xxxxxxx"></script>
- 创建map的div容器,作为展现
- 创建地图实例
//使用的对象为BMap【goole为google.maps.Map】
var map = new BMap('container'); //容器id
new BMap.Point() 设置中心点
map.centerAndZoom(point,15); 初始化地图并且设置展示的级别3-19
map.enableScrollWheelZoom(true); 鼠标滚轮缩放 其他的参数上面google提过,类似
//设置参数,包括平移缩放组件,缩略地图,比例尺,地图类型,都是通过addControl添加
- Marker: 地图添加标记点
var marker = new Bmap.Marker(point); //创建标注
map.addOverlay(marker);
//点击marker事件
marker.addEventListener('click',funtion() {
//点击marker时,进入地图移动了页面,那么信息窗口的中央就不是当前位置了
map.openInfoWindow(infoWindow,point);
})
- 信息窗口 infoWindow
//窗口配置信息options
var options = {
width: 250,
height: 100,
title: "标题"
}
//信息窗口的数据
var content = "<div>你好</div>"
//创建窗口
var infoWindow = new BMap.InfoWindow(content,options);
//默认进入打开信息窗口
map.openInfoWindow(infoWindow,map.getCenter); //地图中心打开
- 地址解析和逆地址解析
//地址解析服务 new BMap.Geocoder()
利用Geocoder的getPoint方法可以解析一个具体的位置,解析成功进入回调函数success
var myGeo = new BMap.Geocoder();
myGeo.getPoitn("北京市海淀区上地10街3号",function(point) {
if(point) {
//point为解析出的经纬度
}
})
//逆地址解析
使用Geocoder的getLocation服务, new BMap.Point(), 解析成功后进入回调
myGeo.getLocation(new BMap.point(sfsj,sfs),function(result) {
if(result) {
alert(result)
}
})
上面的利用经纬度获取地址就是逆地址解析,只是将服务放在了后台,在胖客户端下,可以将该服务直接放前台【当时放后台更加安全】
综合demo
showLocation = function(position) {
//当前经纬度
var longtitude = position.coords.longitude;
var latitude = position.coords.latitude;
alert(longtitude + ":" + latitude);
//构建地图
map = new BMap.Map("map");
//创建中心点
var point = new BMap.Point(longtitude,latitude);
//地图初始化
map.centerAndZoom(point,15);
//鼠标滚轮缩放
map.enableScrollWheelZoom(true);
//平移缩放控件
map.addControl(new BMap.NavigationControl());
//缩略地图
map.addControl(new BMap.OverviewMapControl());
//比列尺
map.addControl(new BMap.ScaleControl());
//地图类型
map.addControl(new BMap.MapTypeControl());
//控件的位置
// var options = {anchor:BMAP_ANCHAOR_BOTTOM_RIGHT};
// map.addControl(new BMap.NavigationControl(options));
//为我的位置创建标注
var marker = new BMap.Marker(point);
//将标注放入地图
map.addOverlay(marker);
var options = {
width:250,
height:100,
title: '我的位置'
}
var content = "<div><font color = 'pink'>我的家在这里</font></div>"
var infoWindow = new BMap.InfoWindow(content,options);
//监听标注,点击后显示信息窗口
marker.addEventListener('click',function() {
//在标记点打开窗口,如果是中央map.getCenter()
map.openInfoWindow(infoWindow,point);
});
}
errorHandler = function(error) {
if(error.code == 1) {
alert("access denied");
} else if(error.code == 2) {
alert("position is not avaliable");
}
}
getLocation = function() {
//navigator 最新插件 geolocation是否可以使用,可以才自动定位
if(navigator.geolocation) {
var options = {timeout: "6000"};
//使用插件获取位置
navigator.geolocation.getCurrentPosition(showLocation,errorHandler,options);
} else {
aler("对不起,你的浏览器不支持geolocation插件")
}
}
在项目中引用时可能出现报错:
百度地图引用时 报出A Parser-blocking, cross site (i.e. different eTLD+1) script
因为页面渲染完成之后使用document.wirte(),不被允许,所以可以将url中的api改为getscript
<script type="text/javascript" src="http://api.map.baidu.com/getscript?v=3.0&ak=NUvQmHyvEMWTO53
同时需要注意的是script标签应该放在body后,这样页面加载完成才能够正确获取容器进行创建
<div id="map" style="width: 800px;height: 800px;margin-top: 100px">
</div>
<script src="/js/JQuerym.js"></script>
<script type="text/javascript" src="http://api.map.baidu.com/getscript?v=3.0&ak=NUvQmHyvEMWTO534b5GcCGUM4eGuGXwT"></script>
<script type="text/javascript">
$(function() {
//发起请求获取错误信息
$.get("/sys/getErrorMessage",{},function(response) {
if(response.code == 0) {
var error = response.data;
alert(error.message);
}
})
//发起请求,后台根据ip分析出大概位置
$.get("/sys/getRegion",{},function(response) {
if(response.code == -1) {
$("#ipMsg").text(response.message);
} else {
$("#ipMsg").text(response.data);
}
})
})
//根据现在的经纬度添加Map
addMap = function(longitude,latitude) {
//根据百度地图绘制位置
//构建地图 在某个具体的div中
map = new BMap.Map("map");
//创建中心点
var point = new BMap.Point(longitude,latitude);
//地图初始化
map.centerAndZoom(point,15);
//鼠标滚轮缩放
map.enableScrollWheelZoom(true);
//平移缩放控件
这样就可以按照百度地图的规范进行地图的绘制显示
显示天气信息http://wthrcdn.etouch.cn/weather_mini?city= X
要显示天气信息,去网站直接爬取是不推荐的,可以访问几个免费的天气信息的提供方,这里cfeng采用http://wthrcdn.etouch.cn/weather_mini?city= X; 请求该接口就可以返回当地的数据
java后台访问并进行数据处理
之前的baidu的后天的经纬度转化也就是访问URL,之后使用缓冲流读入字符串,显示,这里也是类似的
需要注意这里读取到的是压缩流,需要使用GZIPInputStream进行读取
package com.Cfeng.XiaohuanChat.util;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.GZIPInputStream;
/**
* @author Cfeng
* @date 2022/8/22
* 访问http://wthrcdn.etouch.cn/获取天气信息
*/
@Slf4j
public class WeatherUtil {
//获取天气信息,返回JSON字符串
public static String getWheatherByCity(String city) {
String url = "http://wthrcdn.etouch.cn/weather_mini?city="+city;
//解析获得数据
String res = loadUrl(url);
// JSONObject res = JSONObject.parseObject(result);
log.info("天气信息:{}",res.toString());
return res.toString();
}
//将url返回的数据写入字符串
private static String loadUrl(String url) {
StringBuilder stringBuilder = new StringBuilder();
try {
//创建链接地址
URL region = new URL(url);
//访问链接URL地址
URLConnection connection = region.openConnection();
//读取url缓冲,这里需要使用GZIPInputStream压缩输入流读取连接的流
BufferedReader reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(connection.getInputStream()),"utf-8"));
//读取流写入字符串
String inputLine = null;
//读取到末尾
while((inputLine = reader.readLine()) != null) {
stringBuilder.append(inputLine);
}
reader.close();
} catch (Exception e) {
log.error("出错了:{}",e.getMessage());
}
return stringBuilder.toString();
}
}
这里直接将封装的数据返回到前台,由前台进行读取【也可直接在前台访问】
//对返回的数据进行处理
if(response.code == 0) {
var address = JSON.parse(response.data);
$("#position").text(address.data.city);
//处理返回的JSON数据
var yesterday = address.data.yesterday;
var forecast = address.data.forecast;
//主体
var str = "<td align='center' valign='top'><div class='dayWeather'><img width='100' height='100' src='images/ic_cloudy.png' alt=''><p><font size='21'>" + address.data.wendu;
str +=" ℃</font></p><p>" + yesterday.low + "~" + yesterday.high ;
str += "</p><p>" + yesterday.type;
str += "</p><p>" + yesterday.fx + yesterday.fl;
str += "</p><div class='juxing'>良</div></div></td>";
//预报
for(var i = 0; i < forecast.length; i++) {
var day = forecast[i];
str += "<td align='center' valign='top'><div class='dayWeather'><p>" + day.date;
str += "</p><img width='60' height='60' src='images/ic_rainstorm.png' alt=''><p>" + day.low + "~" + day.high + "</p>"
str += "<p>" + day.type;
str += "</p><p>" + day.fengxiang + day.fengli;
str += "</p><div class=\"juxing\">良</div></div></td>";
}
$("#today").html(" " + yesterday.date);
$("#weatherArea").append(str);
}
最终可以在前台看到效果:
当然在具体的业务需求中,需要设计更加精美的样式,这里只是提供设计思路🖤