从零玩转第三方登录之WeChat公众号扫码关注登陆 -wechatgzh

title: 从零玩转第三方登录之WeChat公众号扫码关注登陆 
date: 2022-09-27 22:46:53.362
updated: 2023-03-30 13:28:41.359
url: http://www.yby6.com/archives/wechatgzh
categories: 
- 从零玩转系列
tags: 
- 第三方登录
- 从零玩转系列

前言

由于看见了面试鸭的登陆方式,我也想来整一个.注意: 只能微信认证的公众号才能有二维码扫码的权限,那么我们将使用 微信的测试账户来玩转扫码(沙箱)

1. 大致流程思路:

一、用户打开网页进行登陆/注册 扫码(微信的)
二、用户扫码成功后 微信会根据我们配置的回调地址访问我们的回调并且传递某些参数
三、用户扫码成功并且进行了关注我们的公众号 微信也会访问回调 传递参数
四、++域名使用内网穿透(我这里使用花生壳)++

思路地址: 接收事件推送
在微信用户和公众号产生交互的过程中,用户的某些操作会使得微信服务器通过事件推送的形式通知到开发者在开发者中心处设置的服务器地址,从而开发者可以获取到该信息。其中,某些事件推送在发生后,是允许开发者回复用户的,某些则不允许,详细内容如下:
1、关注/取消关注事件
2、 扫描带参数二维码事件
3、上报地理位置事件
4、自定义菜单事件
5、点击菜单拉取消息时的事件推送
6、点击菜单跳转链接时的事件推送

根据上述六点我们PC端只需要 1、2点即可只是来扫码公众号并且关注后登录

2. 进入测试号页面

微信测试号地址

测试号接口配置

接口信息配置: 将会get方法来进行验签你服务器的请求 和 post来回调推送信息到服务器
参考: 接口信息配置
JS接口安全配置:我们在日常当中经常可以看见js接口安全域名。那么,js接口安全域名是什么?js接口安全域名主要用于微信公众号,如果大家要进行微信的开发,创建公众号是需要填写js接口安全域名的。当我们运用程序的时候,网络是会自动验证安全域名的,它可以解决服务器终端的语言问题,能够让访问正常的运行,只有使用好js接口安全域名,网上的用户才能够访问到网页。
参考:JS接口安全配置

image-1664329653344

3. 介绍

获取 AccessToken

用于请求微信API 需要用到的认证信息
参考: 获取AccessToken

临时二维码

  1. 用户扫描带场景值二维码时,可能推送以下两种事件:
    如果用户还未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
  2. 如果用户已经关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
  3. 获取带参数的二维码的过程包括两步,首先创建二维码ticket,然后凭借 ticket 到指定 URL 换取二维码。
    正确的 Json 返回结果:
    {"ticket":"gQH47joAAAAAAAAAASxodHRwOi8vd2VpeGluLnFxLmNvbS9xL2taZ2Z3TVRtNzJXV1Brb3ZhYmJJAAIEZ23sUwMEmm
    3sUw==","expire_seconds":60,"url":"http://weixin.qq.com/q/kZgfwMTm72WWPkovabbI"}
  4. 参考: 临时二维码

4. 代码操作

编写接口配置以便能修改接口

    /***
     * 微信服务器触发get请求用于检测签名-
     * 如果需要绝对的安全就按照微信来进行验签
     */
    @GetMapping("/weChatScanCodeCallback")
    @ResponseBody
    public String weChatScan(HttpServletRequest request) {
        log.info("验签章:{}", request.getParameterMap());
        return request.getParameter("echostr");
    }

解析微信返回参数
使用DOM4J将微信返回XML格式转换一下


/**
 * @Author yang shuai
 * @Date 2022/9/3
 */
public class XmlUtil {

    /**
     * 读取xml标签内容存放map当中
     */
    public static Map<String,Object> parseXML(InputStream in){
        Map<String,Object> map=new HashMap<>();
        try {
            SAXReader saxReader = new SAXReader();
            Document document = saxReader.read(in);
            Element root = document.getRootElement();
            Iterator iterator = root.elementIterator();
            while (iterator.hasNext()){
                Element element = (Element) iterator.next();
                map.put(element.getName(),element.getStringValue());
            }
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return map;
    }

}

接收微信回调

    /**
     * 接收微信推送事件
     */
    @PostMapping("/weChatScanCodeCallback")
    @ResponseBody
    public String weChatCallback(HttpServletRequest request) {
        try {
            InputStream inputStream = request.getInputStream();
            Map<String, Object> map = XmlUtil.parseXML(inputStream);
            log.info("接收参数:{}", map);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "success";

Last

注入restTemplate请求

/**
 * @Author yang shuai
 * @Date 2022/9/3
 * 注入restTemplate用于http请求
 */
@Configuration
public class RestTemplateConfig {

    @Resource
    private RestTemplateBuilder templateBuilder;

    @Bean
    public RestTemplate restTemplate(){

        return templateBuilder.build();
    }

}

生成微信二维码

/**
 * @Author yang shuai
 * @Date 2022/9/3
 */
public interface WeChatService {
    /**
     * 获取token
     *
     * @return
     */
    String getAccessToken();

    /**
     * 获取生成二维码参数
     *
     * @return
     */
    Map<String, Object> getQrCode();
}

实现


/**
 * @Author yang shuai
 * @Date 2022/9/3
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class WeChatServiceImpl implements WeChatService {

    @Value("${weChat.gzh.appid:''}")
    private String appid;

    @Value("${weChat.gzh.secret:''}")
    private String secret;

    private final RestTemplate restTemplate;

    private final RedisCache redisCacheManager;

    /**
     * 获取token用于操作微信接口
     */
    @Override
    public String getAccessToken() {
        String key = "wx_access_token";
        if (redisCacheManager.hashKey(key)) {
            return redisCacheManager.getCacheObject(key);
        }
        // 获取微信扫码 token
        String url = String.format("http://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s", appid, secret);
        ResponseEntity<String> result = restTemplate.getForEntity(url, String.class);
        if (result.getStatusCode() == HttpStatus.OK) {
            JSONObject jsonObject = JSON.parseObject(result.getBody());
            String access_token = jsonObject.getString("access_token");
            Long expires_in = jsonObject.getLong("expires_in");
            redisCacheManager.setCacheObject(key, access_token, expires_in, TimeUnit.SECONDS);
            return access_token;
        }
        return null;
    }

    /**
     * 获取微信公众号二维码
     */
    @Override
    public Map<String, Object> getQrCode() {
        // 获取临时二维码
        String url = String.format("http://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s", getAccessToken());
        ResponseEntity<String> result = restTemplate.postForEntity(url, "{\"expire_seconds\": 604800, \"action_name\": \"QR_STR_SCENE\", \"action_info\": {\"scene\": {\"scene_str\": \"test\"}}}", String.class);

        log.info("二维码:{}", result.getBody());

        JSONObject jsonObject = JSON.parseObject(result.getBody());
        Map<String, Object> map = new HashMap<>();
        map.put("ticket", jsonObject.getString("ticket"));
        map.put("url", jsonObject.getString("url"));

        return map;
    }
}

5. 改造Controller

新增获取二维码

    /**
     * 获取二维码参数
     *
     * @return
     */
    @GetMapping("/getQrCode")
    @ResponseBody
    public Object getQrCode() {
        return weChatService.getQrCode();
    }

6.编写前段Demo

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登陆</title>
</head>
<body>

<div style="width: 200px;margin: 50px auto">
    <div id="qrcode"></div>
    <div id="msg" style="display: none">
        扫码成功!
    </div>
</div>
<script type='text/javascript' src='http://cdn.staticfile.org/jquery/2.1.1/jquery.min.js'></script>
<script src="http://cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script>
<script>
    $(function () {
        let count = 0;
        //获取二维码参数
            $.get('http://34i33045l8.oicp.vip/weChat/getQrCode', function (res) {
            //生成二维码
            $('#qrcode').qrcode(res.url);
        })
    })

</script>
</body>
</html>

7.启动后端查看效果

1、使用idea打开html挂载一个node
image-1664329692307

2、打开前面要求设置的内网穿透用于接收微信的回调
image-1664329697967

3、进行扫码-查看后台打印参数数据
image-1664329703882

4、扫码后查看控制台
image-1664329719022

推送 XML 数据包示例:

  1. 用户未关注时,进行关注后的事件推送
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[subscribe]]></Event>
  <EventKey><![CDATA[qrscene_123123]]></EventKey>
  <Ticket><![CDATA[TICKET]]></Ticket>
</xml>

示例:
image-1664329728238

在这里大家应该大致的知道下面的该如何实现了!

  1. 微信回调会一直存在 Ticket 字段 用于表示每次二维码的唯一标识
    我们将它进行存储redis当中并且可以看到 Event 我们利用它来区分当前是否为扫码还是关注的推送
  2. 则前段进行段轮训来请求校验当前为什么状态?

参数说明:

参数 描述
ToUserName 开发者微信号
FromUserName 发送方帐号(一个OpenID)
CreateTime 消息创建时间 (整型)
MsgType 消息类型,event
Event 事件类型,subscribe(扫码关注) or SCAN (扫码)
EventKey 事件 KEY 值,qrscene_为前缀,后面为二维码的参数值
Ticket 二维码的ticket,可用来换取二维码图片

8. 改造Controller

新增短轮询检查扫码状态


    /**
     * 用于检测扫码和关注状态
     *
     * @return
     */
    @PostMapping("/checkLogin")
    @ResponseBody
    public Object checkLogin(String ticket) {
        // 存在该信息并且为关注了公众号
        if (redisCache.hashKey(ticket)) {
            if (!redisCache.getCacheObject(ticket).equals("subscribe")) {
                return AjaxResult.error(201, "扫码成功");
            }
            //扫码通过则删除
            redisCache.deleteObject(ticket);
            return AjaxResult.success();

        }
        return AjaxResult.error("无动作");
    }

修改微信回调完善业务


    /**
     * 接收微信推送事件
     *
     * @param request
     * @return
     */
    @PostMapping("/weChatScanCodeCallback")
    @ResponseBody
    public String weChatCallback(HttpServletRequest request) {
        try {
            InputStream inputStream = request.getInputStream();
            Map<String, Object> map = XmlUtil.parseXML(inputStream);
            log.info("接收参数:{}", map);
            String userOpenId = (String) map.get("FromUserName");
            String event = (String) map.get("Event");
           //  自己生成的二维码不管是关注还是扫码都能取到ticket凭证,这里我使用Ticket作为每次二维码的唯一标识
            String ticket = (String) map.get("Ticket");
            if ("subscribe".equals(event)) {
                //  根据openid判断用户是否存在,不存在则获取新增用户
                // 或者根据前段传递手机号或者用户名称来进行openId绑定 看你自己的业务.
                
                
                redisCache.setCacheObject(ticket, "subscribe", (long) (10 * 60), TimeUnit.SECONDS);
                log.info("用户关注:{}", userOpenId);
            } else if ("SCAN".equals(event)) {
                redisCache.setCacheObject(ticket, "scan", (long) (10 * 60), TimeUnit.SECONDS);
                log.info("用户扫码:{}", userOpenId);
            }
        } catch (IOException e) {
              log.error("回调异常:",e);
        }
        return "success";
    }

新增前段短轮询

替换你自己的内网穿透

$(function () {
        let count = 0;
        //获取二维码参数
            $.get('http://34i33045l8.oicp.vip/weChat/getQrCode', function (res) {
            //生成二维码
            $('#qrcode').qrcode(res.url);
            // 轮训获取用户扫码登陆状态
            let task = setInterval(function () {
                $.post('http://34i33045l8.oicp.vip/weChat/checkLogin', {ticket: res.ticket}, function ({code,msg}) {
                    console.log(code);
                    if (code === 200) { // 扫码并且关注成功
                        clearInterval(task)
                        location.href = 'http://yby6.com'
                    } else if (code === 201) { // 扫码成功
                        $("#msg").text(msg);
                        document.querySelector("#msg").style.display = "block"
                    } else {

                    }
                    count ++;
                })
            }, 2000)
        })
    })

最后操作流程

image-1668849184151

注: 前端记得整扫码超时!

你的压力来源于无法自律,只是假装努力,现状跟不上内心欲望,所以你焦虑又恐慌。——杨不易
本文转载于网络 如有侵权请联系删除

相关文章

  • <C++学习笔记>iterator C++

      迭代器是指向数据集合(比如数组、容器)中的元素的数据类型,它可以通过一系列的操作(如自增运算符++,取值运算符*)遍历数据集合中的元素。  迭代器的一种显著形式是指针。指针可以指向数组中的元素,可以通过自增操作“++”遍历数组中的元素。此外迭代器还有其他形式,例如,每一种类型的容器,如vector,都设置有一个特定形式的“iterator”用于高效低遍历其中的元素。  注意,虽然指针是一种典型的迭代器,但并非所有的迭代器都具有与指针相同的功能。指针具有一些迭代器所不需要具有的功能。  迭代器存在五个分类,这五个类别是根据迭代器所执行的功能划分的,五个类别分别是:输入迭代器,输出迭代器,随机访问迭代器,前移迭代器,双向迭代器(注:这几个名称是本人根据C++Reference翻译而来的,可能跟权威译名有异)。见下图:  上图所示的每一类迭代器均具有位于其右边的所有迭代器的所有功能。以下简述。  (1)输入迭代器和输出迭代器:功能最有限的两类迭代器,只能分别进行顺序输入和输出操作。  (2)前移迭代器:具有(1)中两类迭代器的功能,它的局限性是只能沿一个方向遍历数据集合。  (3)双向迭

  • 用 Python 为自己的女神做一个520网站

    先来看一下效果吧,只要有足够的照片素材,捕获女神的心就指日可待下面就一起来完成吧数据准备首先是测试图片的获取,毕竟萝卜哥当前还没有那么多女神的照片 这里我使用如下网站的高清图片,嗯,各个都是大美女http://lab.mkblog.cn/wallpaper/抓取的代码比较简单importrequests importjson defget_pic(): headers={"Accept":"application/json,text/javascript,*/*;q=0.01", "User-Agent":"Mozilla/5.0(Macintosh;IntelMacOSX11_0_1)AppleWebKit/537.36(KHTML,likeGecko)Chrome/89.0.4389.114Safari/537.36", "Cookie":"Hm_lvt_6e8dac14399b608f633394093523542e=1607173561;Hm_lvt_ea4269d

  • 导航系统中里程计研究综述

    文章:ASurveyonOdometryforAutonomousNavigationSystems作者:SHERIFA.S.MOHAMED,MOHAMMAD-HASHEMHAGHBAYAN,TOMIWESTERLUND翻译:particle本文仅做学术分享,如有侵权,请联系删除。 ●论文摘要自主导航系统的开发是构建完全自动驾驶系统的主要挑战之一。完全自主导航不仅要求在GPS信号正常情况下完美工作,而且在GPS不可靠的情况下也能够具有可靠的导航能力。因此,基于里程计系统近年来备受关注。本文对非GPS下的多传感器的里程系统领域的技术现状进行了全面的概述,并确定了未来需要进一步研究的面临的挑战。自给式(就是没有GPS也能计算出车辆自身的位姿)里程计方法分为五种主要类型,即车轮、惯性、激光、雷达和视觉,以上分类主要是依据传感器的类型进行分类的里程计的方法。该领域的研究大多集中在对传感器数据进行全局或局部分析的基础上,来计算车辆的姿态。研究了传感器数据以紧耦合/松耦合方式以及滤波或优化融合方法的不同组合和融合。我们从性能、响应时间、能量效率和准确度等不同的评价指标来分析每种方法的优缺点,为该领

  • C++核心准则​SL.str.3:使用zstring或czstring引用C风格0结尾的字符串序列

    SL.str.3:UsezstringorczstringtorefertoaC-style,zero-terminated,sequenceofcharactersSL.str.3:使用zstring或czstring引用C风格0结尾的字符串序列Reason(原因)Readability.Statementofintent.Aplainchar*canbeapointertoasinglecharacter,apointertoanarrayofcharacters,apointertoaC-style(zero-terminated)string,oreventoasmallinteger.Distinguishingthesealternativespreventsmisunderstandingsandbugs.可读性。表达意图。直接的char*可以是指向单个的字符的指针,指向字符数组的指针,指向C风格(0结尾)字符串的指针,甚至指向小整数的指针。区别这些情况可以防止误解和错误。Example(示例)voidf1(constchar*s);//sisprobablyastrin

  • C++20 最新进展:已完成设计,加入模块和协程

    C++委员会于上周在夏威夷科纳举办了一场官方ISO会议,以确定下一个国际标准C++20的功能集。根据会议报告,C++20的功能设计现已完成。计划于2019年7月在科隆举行的会议上,完成 C++20的规范并发送一份委员会草案以供审查。上周会议确定添加至C++20草案的新特性:Modules!Coroutines!static, thread_local,andlambdacaptureforstructuredbindings.std::polymorphic_allocator<>.std::midpoint and std::lerp.std::execution::unseq executionpolicy.std::ssize() freefunctionthatreturnsasizedsize.std::span usabilityenhancements.Precalculatedhashvaluesinlookup.而以下的特性已在本次会议或之前的会议上获得了C++20批准,但尚未添加到C++20中,因为目前仍在完成规范。它们有望在2019年7月的科隆会议上被

  • go-protobuf, go-grpc-gateway和代码生成

    代码生成代码生成是一种常用的生产效率技术。广义上看,编译器通过高级语言生产出低级语言或者机器码,也可以理解为一种代码生成。这种技术在现代的工程实践里往往比较常见:IDE通常自带了一些常见的单元测试生成工具;根据特定的snippet可以生成比较常用的代码片段;在go语言中,由于目前缺乏对范型对支持,为了节约重复代码,通常实现了类似技术也是使用代码生成。在protobuf生态中,代码生成更为常见,一般来说通过一个proto文件,protoc工具可以生成各个语言的代码,用于搭建一个基于protobuf或者grpc的工具。protoc同时支持以插件等方式,对proto文件进行拓展,生成丰富的代码格式。代码生成通常第一步是分析生成模板或者DSL文件的语法结构,第二步采用字符串拼凑或者模板替换的方式生成代码。golang/protobuf问题: 1.代码生成怎么做? 2.如何实现一个类似的parsergolang/protobuf是golang对protobuf对支持对官方实现,用于从proto文件生成对应对go版本代码文件.入口在protoc-gen-go/main.go,本质是显示protoc

  • 什么是B-Tree

      B-Tree就是我们常说的B树,一定不要读成B减树,否则就很丢人了。B树这种数据结构常常用于实现数据库索引,因为它的查找效率比较高。磁盘IO与预读磁盘读取依靠的是机械运动,分为寻道时间、旋转延迟、传输时间三个部分,这三个部分耗时相加就是一次磁盘IO的时间,大概9ms左右。这个成本是访问内存的十万倍左右;正是由于磁盘IO是非常昂贵的操作,所以计算机操作系统对此做了优化:预读;每一次IO时,不仅仅把当前磁盘地址的数据加载到内存,同时也把相邻数据也加载到内存缓冲区中。因为局部预读原理说明:当访问一个地址数据的时候,与其相邻的数据很快也会被访问到。每次磁盘IO读取的数据我们称之为一页(page)。一页的大小与操作系统有关,一般为4k或者8k。这也就意味着读取一页内数据的时候,实际上发生了一次磁盘IO。B-Tree与二叉查找树的对比  我们知道二叉查找树查询的时间复杂度是O(logN),查找速度最快和比较次数最少,既然性能已经如此优秀,但为什么实现索引是使用B-Tree而不是二叉查找树,关键因素是磁盘IO的次数。数据库索引是存储在磁盘上,当表中的数据量比较大时,索引的大小也跟着增长,达到几个

  • *** glibc detected *** malloc(): memory corruption

    以下错误,发现是由于memset越界写引起的。***glibcdetected***malloc():memorycorruption:0x09eab988***复制在LinuxServer上不好模拟出来:不过若是先malloc,再越界memset,再free此内存块,然后malloc新内存块就会出现类似错误。#include<stdio.h> #include<stdlib.h> #include<string.h> intmain() { char*p1=(char*)malloc(210); if(p1!=NULL) { printf("malloc(210)succeeded\n"); } if(p1==memset(p1,0,300)) { printf("memset(p1,0,300)succeeded\n"); } free(p1); printf("Nowchar*p2=(char*)malloc(210)\n"); char*p2=(char*)malloc(210);

  • 梯度提升树(GBDT)原理小结

    作者:刘建平编辑:龚赛 授权转发自:刘建平《梯度提升树(GBDT)原理小结》地址:https://www.cnblogs.com/pinard/p/6140514.html前言在集成学习之Adaboost算法原理小结中,我们对Boosting家族的Adaboost算法做了总结,本文就对Boosting家族中另一个重要的算法梯度提升树(GradientBoostingDecisonTree,以下简称GBDT)做一个总结。GBDT有很多简称,有GBT(GradientBoostingTree),GTB(GradientTreeBoosting),GBRT(GradientBoostingRegressionTree),MART(MultipleAdditiveRegressionTree),其实都是指的同一种算法,本文统一简称GBDT。GBDT在BAT大厂中也有广泛的应用,假如要选择3个最重要的机器学习算法的话,个人认为GBDT应该占一席之地。章节目录 GBDT概述GBDT的负梯度拟合GBDT回归算法GBDT分类算法GBDT常用损失函数GBDT的正则化GBDT小结 01GBDT概述GBD

  • SpringCloud学习系列之五-----配置中心(Config)和消息总线(Bus)完美使用版

    前言 在上篇中介绍了SpringCloudConfig的使用,本篇则介绍基于SpringCloud(基于SpringBoot2.x,.SpringCloudFinchley版)中的分布式配置中心(SpringCloudConfig)的配置刷新和消息总线(RabbitMQ和Kafka)使用教程。 SpringCloudConfigRefresh 在上一篇中我们介绍了springcloud配置中心的本地使用和Git使用的用法,但是当重新修改配置文件提交后,客户端获取的仍然是修改前的信息,需要客户端重启才可以获取最新的信息。因此我们需要客户端能够动态进行更新,幸好springcloud官方已经给出方案,所以我们只需要使用就行了。 开发准备 开发环境 JDK:1.8 SpringBoot:2.0.6.RELEASE SpringCloud:Finchley.SR2 注:不一定非要用上述的版本,可以根据情况进行相应的调整。需要注意的是SpringBoot2.x以后,jdk的版本必须是1.8以上! 确认了开发环境之后,我们再来添加相关的pom依赖。 <dependencies>

  • 家用电脑架服务器提供web

    要搞一个可以对外的web服务,需要服务器,域名。这些都需要money,但有时,我们只是想自己可以在外面访问,或是提供给朋友看自己的网站有多牛。这时使用家用电脑配置一个可以提供web的服务器,就显得很必要。 要配置对外的web服务器,需要用到的是路由器的转发功能。可以把外网对外部ip的访问转发到自己的电脑上。 对于TP-link,可以这样设置。 智能路由的话,像极路由(从插件里找到超级端口转发) 然后,我们就可以能过host绑定的方法来提供web服务了。  

  • 进阶Java多线程

    一、多线程创建方式 1.1、继承Thread类创建线程类 1.实现步骤 定义一个继承Thread类的子类,并重写该类的run()方法; 创建Thread子类的实例,即创建了线程对象; 调用该线程对象的start()方法启动线程。 2.核心代码 classSomeTheadextendsThraad{ publicvoidrun(){ //dosomethinghere } } publicstaticvoidmain(String[]args){ SomeThreadoneThread=newSomeThread(); //启动线程 oneThread.start(); } 复制 1.2、实现Runnable接口创建线程类 1.实现步骤 定义Runnable接口的实现类,并重写该接口的run()方法; 创建Runnable实现类的实例,并以此实例作为Thread的target对象,即该Thread对象才是真正的线程对象。 2.核心代码 classSomeRunnableimplementsRunnable{ publicvoidrun(){ //dosom

  • Android开发学习之路-LruCache使用和源码分析

    LruCache的Lru指的是LeastRecentlyUsed,也就是近期最少使用算法。也就是说,当我们进行缓存的时候,如果缓存满了,会先淘汰使用的最少的缓存对象。 为什么要用LruCache?其实使用它的原因有很多,例如我们要做一个电子商务App,如果我们不加节制的向服务器请求大量图片,那么对于服务器来说是一个不少的负担,其次,对于用户来说,每次刷新都意味着流量的大量消耗以及长时间等待,所以缓存机制几乎是每个需要联网的App必须做的。 LruCache已经存在于官方的API中,所以无需添加任何依赖即可使用,而这个缓存只是一个内存缓存,并不能进行本地缓存,也就是说,如果内存不足,缓存有可能会失效,而且当App重启的时候,缓存会重新开始生效。如果想要进行本地磁盘缓存,推荐使用DiskLruCache,虽然没包含在官方API中,但是官方推荐我们使用,本文暂不讨论。 使用方法: 使用LruCache其实非常简单,下面以一个图片缓存为例: 创建LruCache对象: privatestaticclassStringBitmapLruCacheextendsLruCache<Strin

  • 【转载】Linux下设备树内容(详细)总结及示例解析

    转载:https://blog.csdn.net/Luckiers/article/details/124772722   参考文章: 韦东山老师设备树视频笔记整理 https://blog.csdn.net/qq_31418645/article/details/100022042   文章目录一、简介二、设备树基础内容2.1设备树文件存放路径2.2DTS、DTB和DTC关系2.3传统驱动代码和使用设备树的对比三、设备树内容属性介绍3.1节点名称3.2compatible3.3model属性3.4status属性3.5#address-cells和#size-cells属性3.6ranges属性3.7aliases节点3.8chosen节点四、设备树文件内容示例解析4.1设备树关键内容解析一、简介设备树是在PowerPC平台最先使用,后来2011年3月份Linux创始人LinusTorvalds在邮件建议ARM社区也使用设备树的方式去描述板级结构。所以设备树其实就是描述开发板上的硬件信息,由于其结构就像现实世界的大树一样,所以就将这种结构叫设备树,如下图所示。 &

  • NPOI心得

    一个Excel文件表示为一个IWookbook,Sheet是ISheet,其它细分为IRow,ICell。 2003和2007版本为IWookbook接口的不同实现:HSSFWookbook和XSSFWookbook 每次修改完需要调用Write方法保存。 日期是以double型的数字存储的,可以使用DateTime.Parse转换

  • User Token简单总结

      1.去中心化的JWTtoken 优点: 1.去中心化,便于分布式系统使用 2.基本信息可以直接放在token中。username,nickname,role 3.功能权限信息可以直接放在token中。用bit位表示用户所具有的功能权限。 缺点:服务端无法主动让token失效   2.中心化的redistoken 优点:服务端可以主动让token失效 缺点:每次都要进行redis查询。占用redis存储空间。 3.优化方案: 3.1.JwtToken中,增加TokenId字段。 3.2.将TokenId字段存储在redis中,用来让服务度可以主动控制token失效 3.3牺牲了JWT去中心化的特点。 3.4可以使用非对称加密。颁发token的认证服务器存储私钥:私钥生成签名。其他业务系统存储公钥:公钥验证签名。

  • CF558 E. A Simple Task

    题目传送门:https://codeforces.com/problemset/problem/558/E 题目大意: 给定一串长度为\(n\)的小写字母串,有\(m\)个操作,每次操作将区间\([l_i,r_i]\)排序成非升(\(k_i=0\))或非降(\(k_i=1\))序列,输出问\(m\)次操作后的字符串 开26个线段树,每次将\([l_i,r_i]\)中所有的字符统计出来,然后再暴力按顺序插回去(区间覆盖) 每次询问是\(O(26\log^2n)\)的,插入也是\(O(26\log^2n)\)的,共计\(m\)组操作 最后询问的时间复杂度为\(O(26n\logn)\) 故总时间复杂度为\(O(26m\log^2n+26n\logn)\)(反正炸不了) /*programfromWolfycz*/ #include<map> #include<cmath> #include<cstdio> #include<vector> #include<cstring> #include<iostream> #i

  • java异常信息分析

    6控制台报错信息分析:1单个类报错信息分析: atjavax.validation.Validation$GenericBootstrapImpl.configure(Validation.java:276)~[validation-api-1.1.0.Final.jar:na] 1一个类信息 2括号前:类的路径+方法名>类所在的包路径.类名.方法名 3括号内:类名+报错行,可以直接点到报错行,最常用 4括号后:依赖的jar名称 2单个异常报错信息分析 1报错的异常 2异常的说明 3报错业务流程所涉及的类 4业务逻辑:自下而上读类 5省略了多少个框架:...20commonframesomitted,省略了20个公共框架,啥意思?不懂 3多个异常分析 1后面的异常一般都是诱发异常,就像多米诺积木,一个倒了,后面的都倒了 2查看异常发生的整个业务流程,可以帮助我们理解,这个业务功能是在做什么,有什么用,为什么要这么做 从而能够帮助我们限定错误的范围,联想错误的诱因,从业务功能的角度上去理解,联系,找到,和定位错误的根本原因 有时候不从功能和业务的角度,

  • BZOJ3083: 遥远的国度

    3083:遥远的国度 TimeLimit:10Sec  MemoryLimit:128MBSubmit:839  Solved:192[Submit][Status] Description 描述 zcwwzdjn在追杀十分sb的zhx,而zhx逃入了一个遥远的国度。当zcwwzdjn准备进入遥远的国度继续追杀时,守护神RapiD阻拦了zcwwzdjn的去路,他需要zcwwzdjn完成任务后才能进入遥远的国度继续追杀。 问题是这样的:遥远的国度有n个城市,这些城市之间由一些路连接且这些城市构成 了一颗树。这个国度有一个首都,我们可以把这个首都看做整棵树的根,但遥远的国度比较奇怪,首都是随时有可能变为另外一个城市的。遥远的国度的每个城市有 一个防御值,有些时候RapiD会使得某两个城市之间的路径上的所有城市的防御值都变为某个值。RapiD想知道在某个时候,如果把首都看做整棵树的根的 话,那么以某个城市为根的子树的所有城市的防御值最小是多少。由于RapiD无法解决这个问题,所以他拦住了zcwwzdjn希望他能帮忙。但 zcwwzdjn还要追杀sb

  • 在没有父母的老屋,我只是故乡的客人

    在没有父母的老屋,我只是故乡的客人作者:孙道荣老家亲戚的孩子结婚,邀请他去喝喜酒。他欣然应允。回到了故乡,从车站走出来,他却有点恍惚了,喜宴是明天,他不知道是直奔亲戚家好,还是该先找个酒店住下,明天再赶过去?这是母亲过世后,他第一次返乡。父亲早年就过世了,3年前,母亲也走了。办完母亲的丧事,他在县城的妹妹家小住了几日。临别时,妹妹对他说:“哥,以后回来你就上我家住吧。”当时他点点头。但是,当他再次回来,站在熟悉却又陌生的车站出口,他忽然发觉,自己不知道该往哪去了。以前当然不是这样。父母在时,每次回来,不管多晚,他都不担心,他会打个车,直奔县城20里外的家,那个他从小长大的乡村。有时候,他会提前告诉父母他要回来;有时,他也会忽然就出现在了家门口,让父母又惊又喜,嗔怪他搞突然袭击。也有时候,他并不急于回家,先到县城的妹妹家歇个脚,然后,再和妹妹全家,一大帮子人,浩浩荡荡地回乡。一到村头,就看见了手搭额头眺望的老母亲,露水打湿了她的裤脚,天知道她从几点就站在村口了,一定是妹妹提前告诉了老母亲。每次这样兴师动众地回来,陈旧的老宅,忽然被人声塞满,兴奋得吱吱作响。老宅只在他们回来时,才再一次呈现

  • matplotlib绘制常见图形的实现方法

    1、条形图(柱状图)   绘制柱状图的相关API: 1plt.figure('Bar',facecolor='lightgray') 2plt.bar( 3x,#水平坐标数组 4y,#柱状图高度数组 5width,#柱子的宽度 6bottom,#柱子的底部基准位置 7color='',#填充颜色 8label='',#标签 9alpha=0.2#透明度复制   示例: 1importnumpyasnp 2importmatplotlib.pyplotasplt 3 4apples=np.random.randint(10,30,size=10) 5oranges=np.random.randint(50,70,size=10) 6 7plt.figure('Bar',facecolor='lightgray') 8plt.title('Bar',fontsize=14) 9plt.xlabel('Month',fontsize=14) 10plt.ylabel('Data',fontsize=14) 11plt.grid(linestyle=':',axis='y') 12x=n

相关推荐

推荐阅读