【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
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值