【web开发】spring security配合验证码,java获取地理位置(绘制百度地图)、天气

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或者打包后的其中的文件, 这样就可以访问成功

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("&nbsp;&nbsp;&nbsp;&nbsp;" + yesterday.date);
                    $("#weatherArea").append(str);
                }

最终可以在前台看到效果:

在这里插入图片描述

当然在具体的业务需求中,需要设计更加精美的样式,这里只是提供设计思路🖤

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
完整全套资源下载地址:https://download.csdn.net/download/qq_27595745/70761177 【完整课程列表】 完整版Java web开发教程PPT课件 Java开发进阶教程 第01章 JavaScript简介以及基本语法(共15页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第02章 Dom模型,控制客户端元素(共15页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第03章 表单验证,常用函数(共15页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第04章 自定义web服务器(共14页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第06章 SERVLET(共15页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第07章 JSP语法及运行过程(共13页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第08章 JSP内置对象(共22页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第09章 jstl、el、java bean(共18页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第10章 过滤器、监听器、自定义标签(共19页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第11章 AJAX实现(共11页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第12章 自定义mvc框架(共11页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第13章 spring ioc aop(共18页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第14章 spring mvc介绍,原理以及搭建(共15页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第15章 spring mvc核心对象拦截器(共26页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第17章 自定义orm框架(共11页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第18章 mybatis框架的搭建以及应用(共13页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第19章 mybatis高级应用(共21页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第20章 数据库连接池,缓存(共15页).pptx 完整版Java web开发教程PPT课件 Java开发进阶教程 第22章 常用框架的介绍以及环境搭建(共16页).pptx JS课程案例ebookHTML\网上书店需求文档.doc

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值