为你的项目加上微信登录(个人开发)

news2024/11/29 6:26:44

当我们开发个人项目的时候,为了用户登录的便捷性,经常会给我们的项目加上一些除了注册之外的方式,其中最常见的就是微信登录,但作为个人开发者,是无法使用微信的授权登录的,但是通过微信公众号可以获得同样的结果。

看完这篇文章你能得到什么?

  1. 引入微信公众号开发:微信公众号开发比较容易,只要入门后面根据开发文档操作即可
  2. 为你的项目加上微信登陆功能
  3. 学会如何阅读文档:文章中标识了这个模块具体在哪个章节,可以先去尝试自己学习开发
  4. 掌握一些开发的思想

实现思路

  1. 微信公众号为我们提供了带信息的二维码的方式,通过这个二维码我们可以让用户去关注我们的公众号
  2. 微信为我们提供了一个可以获取到用户头像等基本信息的方法
  3. 用户点击链接授权后我们为他注册账号然后返回登录密钥
  4. 我们将这个密钥作为键存入我们的 redis,设置过期时间
  5. 将用户的 openId 作为值,当用户输入正确的密钥的时候
  6. 后端就能拿到这个密钥对应的 openId
  7. 记录用户的登录态完成登录功能

前期准备

注册公众号

我们首先需要去注册一个属于我们的公众号,微信公众平台:https://mp.weixin.qq.com/,image.png
注意注册类型一定要选择订阅号,服务号需要企业身份,而且选择后是无法更改的(一个邮箱只能绑定一种账号),选择的时候务必注意,跟着走完注册流程后我们就获得了自己的公众号。
为了开发的便捷性,我们使用开发者工具中提供的公众平台的测试号来尝试实验,测试号省去了一些繁琐的部分,我们开发完成后将代码上线后再去改回使用我们的公众号即可。

内网穿透工具

公众号开发是需要向我们后端发送请求的,我们不可能再服务器上进行代码开发,为了本地开发的便捷,这里使用内网穿透工具来让微信服务器可以访问我们本地的端口。
这里我们选择一个免费的内网穿透工具(提供的免费服务供给我们开发足够了),
netapp:https://natapp.cn/,根据系统的提示下载即可,配置参考官方给的图文即可https://natapp.cn/article/natapp_newbie,唯一需要注意的点就是这个 config.ini 文件的编写,image.png
我们将这两个文件放在同一个目录下直接启动即可image.png,启动后的界面是这样image.png

开发者文档

文章后面括号中会标识这个操作对应的开发者文档中的哪个模块,方便大家进行学习和查阅,我们可以再开发者工具中很容易的找到开发者文档:
image.png

开始开发

接入微信公众平台开发

首先我们打开我们的微信测试号,填写接口配置信息,微信服务器发送请求的时候会向我们填写的 url 发送请求,有了上面的内网穿透工具,微信服务器已经可以发送请求到我们的内网。
将想要微信服务器访问到的组件粘贴到接口配置信息中,这里我们使用 /wx 来接收所有微信发送的请求。

@RequestMapping("/wx")
@Slf4j
@RestController
@CrossOrigin(origins = "http://localhost:8000", allowCredentials = "true")
public class WxController {
    /**
     * 微信 url 验证接口
     */
    @GetMapping("/")
    public String wxCheck(@RequestParam("signature") String signature,
                        @RequestParam("timestamp") Integer timestamp,
                        @RequestParam("nonce") String nonce,
                        @RequestParam("echostr") String echostr) {
        log.info("开始验证 url signature={},timestamp={},nonce={},echostr={}", 
                 signature,timestamp, nonce, echostr);
        //TODO:验证是微信发送的
        return echostr;
}

image.png
这时候微信会向我们填写的地址发送一个 GET 请求,同时会附带上面写的参数,我们接收到这个信息后需要将随机数作为返回值返回给微信服务器才能通过验证**(开始开发 / 接入指南)**,微信发送给我们的参数为:
image.png
通过开发文档提供的方式可以验证这个消息是否来自微信,这里先不提供具体的实现方法。

开发带参数的二维码

想要实现扫码登录,需要一个能导航到我们公众号的二维码,二维码开发的具体信息在**(账号管理 / 生成带参数的二维码)**,简单来说就是我们拿我们的 accessToken 和我们希望二维码中携带的信息去向微信服务器换取一个 ticket,再通过这个 ticket 去获得我们的二维码图片地址。

获取 AccessToken

access_toke n是公众号的全局唯一接口调用凭据,公众号调用各接口时都需使用access_token。开发者需要进行妥善保存。

关于获取 AccessToken 的具体流程在**(开始开发 / 获取 Access token)**,我们发送 GET 请求到这个地址

https请求方式: GET https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET

注意将参数改为自己的,就能获取到类似于这样的 JSON 字符串

{"access_token":"ACCESS_TOKEN","expires_in":7200}

image.png
示例实现方法
我在项目中是封装了一个 WxUtil 作为工具类来存放这些方法,发送请求是通过 HuTool 的 HttpUtil,这里就不再赘述使用方法,可以当作参考:

	/**
     * 获取 accessToken
     */
public static String getAccessToken() throws JsonProcessingException {
    String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential" +
    "&appid=" +
    "&secret=";
    HttpResponse execute = HttpUtil.createGet(url)
    .execute();
    String body = execute.body();
    ObjectMapper objectMapper = new ObjectMapper();
    // 将 JSON 字符串转化为 Map
    Map<String, Object> stringObjectMap = objectMapper.readValue(body,
                                                     new TypeReference<>(){});
    return (String)stringObjectMap.get("access_token");
}

这里的序列化工具采用的是 Jackson

<dependency>
  <groupId>com.fasterxml.jackson.dataformat</groupId>
  <artifactId>jackson-dataformat-xml</artifactId>
  <version>2.9.8</version>
</dependency>
获取带参数的二维码

接下来让我们再次回到主线上来,继续开发带参数的二维码,参考官方文档的说明:

临时二维码请求说明
http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN POST数据格式:json POST数据例子:{“expire_seconds”: 604800, “action_name”: “QR_SCENE”, “action_info”: {“scene”: {“scene_id”: 123}}} 或者也可以使用以下POST数据创建字符串形式的二维码参数:{“expire_seconds”: 604800, “action_name”: “QR_STR_SCENE”, “action_info”: {“scene”: {“scene_str”: “test”}}}
永久二维码请求说明
http请求方式: POST URL: https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=TOKEN POST数据格式:json POST数据例子:{“action_name”: “QR_LIMIT_SCENE”, “action_info”: {“scene”: {“scene_id”: 123}}} 或者也可以使用以下POST数据创建字符串形式的二维码参数: {“action_name”: “QR_LIMIT_STR_SCENE”, “action_info”: {“scene”: {“scene_str”: “test”}}}

这里我们获取的是临时的二维码,用户如果长时间不扫码则二维码会失效,我们需要构造一个 Post 请求将微信文档中需要参数传递过去,下面给出我的示例代码:

 public static String getTemporaryQRCode(Boolean isTemplate, Boolean isSTR) throws JsonProcessingException {
        HashMap<String, Object> map = new HashMap<>();
        //获取临时二维码
        //该二维码有效时间,以秒为单位。
        //最大不超过2592000(即30天),此字段如果不填,则默认有效期为60秒。
        map.put("expire_seconds", 120);
        if (isTemplate) {
            //二维码类型,QR_SCENE为临时的整型参数值,QR_STR_SCENE为临时的字符串参数值,
            // QR_LIMIT_SCENE为永久的整型参数值,QR_LIMIT_STR_SCENE为永久的字符串参数值
            if (isSTR) {
                map.put("action_name", "QR_STR_SCENE");
            }else {
                map.put("action_name", "QR_SCENE");
            }
        } else {
            if (isSTR) {
                map.put("action_name", "QR_LIMIT_STR_SCENE");
            } else {
                map.put("action_name", "QR_LIMIT_SCENE");
            }
        }
        // 构建 actionInfo,内嵌
        HashMap<String, Object> actionInfo = new HashMap<>();
        HashMap<String, Object> scene = new HashMap<>();
        scene.put("scene_id", "1");
        actionInfo.put("scene", scene);
        map.put("action_info", actionInfo);
        //发送请求来获取 ticket
        String ticket = getTicket(map);
        // 通过 ticket 来获取到二维码的地址
        String encode = URLEncoder.encode(ticket);
        String codeUrl = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=" + encode;
        return codeUrl;
    }

这段代码中,我需要传入两个参数来判断请求的是否是临时的二维码,场景值 ID 是否是字符串,通过判断语句来修改 POST 参数的内容,这里可以采用直接编写 JSON 字符串的方式实现,但如果想通过 Map 来实现的话需要注意请求的参数中有一个内嵌的 JSON 对象。
通过这个方法我们就能获取到我们二维码的 URL,这个二维码能干什么呢?

接收带参数的二维码

我们将这些信息封装到二维码后,用户扫码关注我们的公众号的时候就会将这些信息同时提交给我们的服务器**(基础消息能力 / 接收事件推送)**,当用户扫码后可以有两种事件推送,已经关注公众号和未关注但通过我们提供的二维码扫码关注两种事件。

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[FromUser]]></FromUserName>
  <CreateTime>123456789</CreateTime>
  <MsgType><![CDATA[event]]></MsgType>
  <Event><![CDATA[SCAN]]></Event>
  <EventKey><![CDATA[SCENE_VALUE]]></EventKey>
  <Ticket><![CDATA[TICKET]]></Ticket>
</xml> 

<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>

这两个事件仅有 Event 参数不同,我们可以通过获取这个参数来判断用户是否是第一次关注公众号

处理信息构建授权地址

我们在二维码的参数中设置一个标识登录的参数,当我们接收到这个参数的时候就执行登录的逻辑,同时为这个用户构建一个登录的密钥,将密钥和用户的 openId 作为一个键值对存储在 Redis 数据库中,同时将密钥返回给用户。
需要获取当用户的具体信息的时候,就需要学习公众号开发的具体授权**(微信网页开发 / 网页授权)**

用户同意授权,获取到用户的 code

请求地址
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect
参考示例
https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx807d86fb6b3d4fd2&redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect

这个地址的逻辑是用户访问这个地址后会请求授权,用户同意授权后会跳转到我们指定的回调界面,我这里希望在回调界面中为用户提供他们的登录密钥。
设置回调接口
image.png
找到测试公众号界面中的这个修改按钮,来指定回调的地址的域名,注意不要带 http,微信会检测我们的回调地址是否在这个域名下,如果在就无法实现跳转功能。
image.png

返回授权地址

当用户扫码进入我们的公众号,且验证事件信息后发现是登录请求的时候,就向他提供我们的授权地址
代码示例
下面提供我实现的方式,仅供参考

/**
     * 接收消息并自动回复的功能
     */
@PostMapping("/")
public String receiveMessage(HttpServletRequest request) throws IOException, DocumentException {
    ServletInputStream inputStream = request.getInputStream();
    // 将传来的信息封装到 map 中
    HashMap<String, String> map  = new HashMap<>();
    SAXReader saxReader = new SAXReader();
    Document read = saxReader.read(inputStream);
    Element rootElement = read.getRootElement();
    List<Element> elements = rootElement.elements();
    for (Element element : elements) {
        map.put(element.getName(), element.getStringValue());
    }
    log.info("开始返回信息,请求参数{}", map);
    // 拿到用户的 OpenId
    String openId = map.get("FromUserName");
    // 验证是扫码登录且事件值是1  || (map.get("Event").equals("subscribe"))
    //TODO:给事件值封装一个常量类
    if ((map.get("Event").equals("subscribe") || map.get("Event").equals("SCAN"))) {
        //String token = wxService.WxLogin(openId);
        String url = "https://open.weixin.qq.com/connect/oauth2/authorize?" +
        "appid=" +
        "&redirect_uri=http://pab2mw.natappfree.cc/wx/wxCallBack" +
        "&response_type=code" +
        "&scope=snsapi_userinfo" +
        "&state=STATE#wechat_redirect";
        return WxUtil.getReplyMessage(map, "请跳转到授权网址:" + url);
    }
    return "";

先从 request 中接收到提供的参数,再去验证这个请求是否为登录请求,再将我们前面构造的回调地址传回去,这里需要学习被动回复用户信息**(基础消息能力 / 被动回复用户信息)**,这里我只解析这个回复文本消息的能力
被动回复用户信息
我们需要在 return 中返回一个 XML 格式的字符串

<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>12345678</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[你好]]></Content>
</xml>

这是给出的字符串的示例,其中 ToUserName 和 FromUSerName 都可以通过请求的 XML 拿取,我们只需要指定创建时间和回复的具体消息即可。

public static String getReplyMessage(Map<String, String> map, String message) throws JsonProcessingException {
    // 设置基础的信息
    TestMessage testMessage = new TestMessage();
    testMessage.setFromUserName(map.get("ToUserName"));
    testMessage.setToUserName(map.get("FromUserName"));
    testMessage.setMsgType("text");
    testMessage.setCreateTime(System.currentTimeMillis() / 1000);
    //设置需要返回给用户的信息
    testMessage.setContent(message);
    // 利用 JackSon 实现序列化
    return JsonUtil.getXmlString(testMessage);
}

这个方法我也封装到了 WxUtil 这个类中,可以作为参考,序列化库使用的 Jackson,导入方式上面已经讲过了。

拉取用户信息

当用户授权成功后,微信会调用我们提供的回调地址,并且将 code 传递过来,比如我们上面指定的 /wxcallback,我们在这个回调接口中实现注册和密钥的返回。
我们拿到用户的 code 就可以向微信请求用户的具体信息了,首先要请求获取一个 accessToken,这和我们上面讲到的 accessToken 不是同一种,是用于网页授权的 accessToken。
我们请求这个地址:

获取code后,请求以下链接获取access_token:
https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

image.png
正确返回的 JSON 对象:

{
  "access_token":"ACCESS_TOKEN",
  "expires_in":7200,
  "refresh_token":"REFRESH_TOKEN",
  "openid":"OPENID",
  "scope":"SCOPE",
  "is_snapshotuser": 1,
  "unionid": "UNIONID"
}

通过这个 accessToken 我们就可以获取到用户的信息了
如果网页授权作用域为snsapi_userinfo,则此时开发者可以通过access_token和openid拉取用户信息了。

http:GET(请使用https协议):
https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN

向这个地址发送请求信息就能得到用户的详细信息:

{   
  "openid": "OPENID",
  "nickname": NICKNAME,
  "sex": 1,
  "province":"PROVINCE",
  "city":"CITY",
  "country":"COUNTRY",
  "headimgurl":"https://thirdwx.qlogo.cn/mmopen/g3MonUZtNHkdmzicIlibx6iaFqAc56vxLSUfpb6n5WKSYVY0ChQKkiaJSgQ1dZuTOgvLLrhJbERQQ4eMsv84eavHiaiceqxibJxCfHe/46",
  "privilege":[ "PRIVILEGE1" "PRIVILEGE2"     ],
  "unionid": "o6_bmasdasdsad6_2sgVt7hMZOPfL"
}

返回的数据是这样的
image.png
下面给出我实现的代码示例,仅供参考

/**
     * 获取用户详细信息
     */
public static Map<String, Object> getUserMes(String pageAccessToken, String openId) throws JsonProcessingException {
    HttpResponse execute = HttpUtil.createGet("https://api.weixin.qq.com/sns/userinfo?" +
                                              "access_token=" + pageAccessToken +
                                              "&openid=OPENID" + openId +
                                              "&lang=zh_CN").execute();
    String body = execute.body();
    return JsonUtil.getParamMap(body);
}

完成注册逻辑返回密钥

Controller 层

    @RequestMapping("/wxCallBack")
    @ResponseBody
    public String wxCallBack(String code) throws JsonProcessingException {
        Map<String, Object> pageMap = WxUtil.getPageMap(code);
        String pageAccessToken = (String) pageMap.get("access_token");
        String openId = (String) pageMap.get("openid");
        String token = wxService.WxLogin(pageAccessToken, openId);
        return "你的登录密钥为" + token;
    }

Service 层

@Override
    public String WxLogin(String pageAccessToken, String openId) throws JsonProcessingException {
        // 先去数据库中查询是否有 openId 如果有则表明已经注册
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("mpOpenId", openId);
        User one = userService.getOne(queryWrapper);
        String token = TokenUtil.generateShortToken();
        String key = "LoginToken" + token;
        if (one == null) {
            // 用户还没有注册,调用注册方法
            Map<String, Object> userMes = WxUtil.getUserMes(pageAccessToken, openId);
            User user = new User();
            // 保存用户的 unionId 和 openId
            user.setMpOpenId(openId);
            user.setUnionId((String) userMes.get("unionid"));
            user.setUserName((String)userMes.get("nickname"));
            user.setUserAvatar((String)userMes.get("headimgurl"));
            //TODO:完成 API 调用的 accessKey 的获取
            boolean save = userService.save(user);
            if (!save) {
                throw new BusinessException(400, "系统内部错误");
            }
        }
        // 在 Redis 中存储密钥信息
        redisService.setExpireString(key, openId, 120, TimeUnit.SECONDS);
        return token;
    }

在业务层实现了注册的业务,并且设置了向 Redis 中设置密钥。

用户输入密钥执行登录逻辑

这时候用户就能在授权地址中看到我们的 token,在前端界面输入 token,验证后即可完成登录逻辑。

    @GetMapping("/verifyToken")
    public BaseResponse<User> verifyToken(String wxToken, HttpServletRequest request) {
        log.info("接收到的 token 为{}", wxToken);
        String key = "LoginToken" + wxToken;
        String openId = redisService.getString(key);
        if (openId == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "密钥错误");
        }
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("mpOpenId", openId);
        User one = userService.getOne(wrapper);
        request.getSession().setAttribute(USER_LOGIN_STATE,one);
        return ResultUtils.success(one);
    }

具体根据自己的业务调整,这只是给出一个实例。

小结

这样就完成了微信登录功能的开发,当然还有很多其他的实现方式,这个方式或许不是最好的,也存在一些问题,比如用户恰好输成别人的密钥就会导致登录到别人的账号,可以后期调整,当然用户授权后可以将信息保存到 session 中,前端轮询查看这个 session 是否有登录数据,查询后完成登录跳转。。。
总之实现的方法有很多,希望这篇文章能为大家带来启发。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1256386.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【nginx】 实现限流

这里写自定义目录标题 前言正文nginx实现限流并发限制限制单IP并发数量限制单主机服务并发数量 速率限制限流效果 注意疑问参考链接 小结 前言 好久不见&#xff0c;还算为时不晚。最近一个月经历了工作的调整&#xff0c;技术栈从Java转向了Go和Python, 工作显得更忙了些&…

JavaScript基础—for语句、循环嵌套、数组、冒泡排序、综合案例—根据数据生成柱形图

版本说明 当前版本号[20231126]。 版本修改说明20231126初版 目录 文章目录 版本说明目录JavaScript 基础第三天笔记for 语句for语句的基本使用循环嵌套倒三角九九乘法表 数组数组是什么&#xff1f;数组的基本使用定义数组和数组单元访问数组和数组索引数据单元值类型数组长…

测试工程师必学看系列之Jmeter_性能测试:性能测试的流程和术语

性能测试的流程 一、准备工作 1、系统基础功能验证 一般情况下&#xff0c;只有在系统基础功能测试验证完成、系统趋于稳定的情况下&#xff0c;才会进行性能测试&#xff0c;否则性能测试是无意义的。2、测试团队组建 根据该项目的具体情况&#xff0c;组建一个几人的性能测试…

Linux面试题(三)

目录 34、du 和 df 的定义&#xff0c;以及区别&#xff1f; 35、awk 详解。 36、当你需要给命令绑定一个宏或者按键的时候&#xff0c;应该怎么做呢&#xff1f; 37、如果一个 linux 新手想要知道当前系统支持的所有命令的列表&#xff0c;他需要怎么做&#xff1f; 38、…

23年几个能打的UE4游戏技术选型

近期发现很多的精力放在游戏的整体技术选型以及产生的结果上面&#xff0c;所以回顾下几个游戏的选型和结果&#xff1b; 这里一个是自己玩游戏的画面流畅度的直接感受&#xff0c;以及一直非常喜爱的评测“数毛社”&#xff0c;digital foundry&#xff1b; 23年目前来看&…

【NeRF】3、MobileR2L | 移动端实时的神经光场(CVPR2023)

论文&#xff1a;Real-Time Neural Light Field on Mobile Devices 代码&#xff1a;https://github.com/snap-research/MobileR2L 出处&#xff1a;CVPR2023 贡献&#xff1a; 设计了一套移动端实时的 R2L 网络结构 MobileR2L&#xff0c;在 iphone13 上渲染一张 1008x756…

前端学习--React(4)路由

一、认识ReactRouter 一个路径path对应一个组件component&#xff0c;当我们在浏览器中访问一个path&#xff0c;对应的组件会在页面进行渲染 创建路由项目 // 创建项目 npx create router-demo// 安装路由依赖包 npm i react-router-dom// 启动项目 npm run start 简单的路…

【JavaEE】多线程 (2) --线程安全

目录 1. 观察线程不安全 2. 线程安全的概念 3. 线程不安全的原因 4. 解决之前的线程不安全问题 5. synchronized 关键字 - 监视器锁 monitor lock 5.1 synchronized 的特性 5.2 synchronized 使⽤⽰例 1. 观察线程不安全 package thread; public class ThreadDemo19 {p…

LeetCode中链表类题目十条血泪经验总结-全程干货

文章目录 前言干货经验汇总第一梯队第二梯队 力扣代表性链表题目推荐 前言 链表是以节点&#xff08;node&#xff09;存储的链式存储结构&#xff0c;一个node包含一个data域&#xff08;存放数据&#xff09;和一个next域&#xff08;存放下一个node的指针&#xff09;&…

Co-DETR:DETRs与协同混合分配训练论文学习笔记

论文地址&#xff1a;https://arxiv.org/pdf/2211.12860.pdf 代码地址&#xff1a; GitHub - Sense-X/Co-DETR: [ICCV 2023] DETRs with Collaborative Hybrid Assignments Training 摘要 作者提出了一种新的协同混合任务训练方案&#xff0c;即Co-DETR&#xff0c;以从多种标…

Web框架与Django简介

Web框架与Django简介 一、Web应用的组成 我们为了开发一款Web软件首先要了解什么才是Web应用软件呢&#xff1f; 对于传统的应用软件来说&#xff0c;基本都是部署单机使用&#xff0c;而Web应用软件就不一样&#xff0c;Web应用软件是基于B/S架构的&#xff0c;B和S都在不同…

Vue常见的实现tab切换的两种方法

目录 方法一&#xff1a;事件绑定属性绑定 效果图 完整代码 方法二&#xff1a;属性绑定 动态组件 component标签 效果图 完整代码 方法一&#xff1a;事件绑定属性绑定 效果图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta c…

Qt 样式表

QLabel&#xff0c;应用于Widget&#xff1a; .QLabel {background-color:pink; }.QLabel[warnlevel_1] {border:5px solid yellow; }.QLabel[warnlevel_2] {border:5px solid red; } QWidget{background-color:rgb(54,54,54); }QLineEdit{border: 1px solid #ABCDA0; /…

8 增强型脉宽调制模块ePWM

文章目录 8.1 PWM控制基本原理8.2 PWM结构及组成单位8.3 时基模块TB8.3.1 ePWM时基模块作用8.3.2 时基模块的关键信号和寄存器 8.5 动作模块 AC8.5.1 动作模块的作用8.5.2 动作模块关键信号与寄存器 8.11 PWM模块输出8.11.1 单边非对称波形8.11.2 单边非对称脉冲波形 8.1 PWM控…

STM32F103C8T6_PWM引脚

可以看到&#xff1a;一共可以产生4 x 416路PWM信号&#xff1a;每个TIMER4路PWM&#xff0c; PA0,PA1,PA2,PA3,PA8,PA10,PA11; PA共7个 PB0,PB1,PB6,PB7,PB8,PB9,PB14; PB共7个

【Linux】Android平板上远程连接Ubuntu服务器code-server进行代码开发

文章目录 1.ubuntu本地安装code-server2. 安装cpolar内网穿透3. 创建隧道映射本地端口4. 安卓平板测试访问5.固定域名公网地址6.结语 1.ubuntu本地安装code-server 准备一台虚拟机&#xff0c;Ubuntu或者centos都可以&#xff0c;这里以VMwhere ubuntu系统为例 下载code serve…

BrokerChain

BrokerChain: A Cross-Shard Blockchain Protocol for Account/Balance-based State Sharding 我总感觉这篇文章不完整&#xff0c;缺少一些东西。或者说有些地方并没有详细说。比如状态图的构建&#xff0c;网络重分片的的配置过程。都直接忽略了。 Motivation 1 跨片交易不…

Qt5.15.2静态编译 VS2017 with static OpenSSL

几年前编译过一次Qt静态库:VS2015编译Qt5.7.0生成支持XP的静态库,再次编译,毫无压力。 一.环境 系统:Windows 10 专业版 64位 编译器:visual studio 2017 第三方工具:perl,ruby和python python用最新的3.x.x版本也是可以的 这三个工具都需要添加到环境变量,安装时勾选…

Android aidl的简单使用

一.服务端 1.创建aidl文件&#xff0c;然后记得build下生成java文件 package com.example.aidlservice31;// Declare any non-default types here with import statementsinterface IMyAidlServer {// 接收一个字符串参数void setData(String value);// 返回一个字符串String …

windows通过regsvr32注册dll文件失败

1、注册dll文件失败 最近在研究中文输入法&#xff0c;下载SampleIME源码后编译得到SampleIME.dll&#xff0c;最后只需要将输入法安装&#xff08;即注册&#xff09;就可以使用了。 但是通过命令&#xff1a; regsvr32 C:\Windows\System32\SampleIME.dll 注册时却提示错…