当前位置: 首页 > news >正文

Electron Apple SignIn 登录

本人写博客,向来主张:代码要完整,代码可运行,文中不留下任何疑惑。

最讨厌写博客,代码只留下片段,文中关键的东西没写清楚。之前看了那么多文章,就是不告诉我clientId从哪来的。

官方资料地址:

Sign in with Apple JS | Apple Developer Documentation

一、网页客户端代码

clientId:这个会在下文中告诉你怎么来的

usePopup:如果设置为true,就会以弹框的方式打开苹果登录窗口。设置 为false,你自己试试吧

redirectURI:这在usePopup=true时,没啥用

state:在各种页面跳转时会原样传递,你自己看着办

nonce:一个随机数,至于作用么,你自己猜,照着做就好

<!DOCTYPE html>
<html><head><meta charset="utf-8"><meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" /><title></title><script type="text/javascript">var hostBase = "https://myserver.cn";function getQuery(i) { var j = location.search.match(new RegExp("[?&]" + i + "=([^&]*)(&?)", "i")); return j ? j[1] : j; }function getQueryIn(i, params) { var j = ("?&" + params).match(new RegExp("[?&]" + i + "=([^&]*)(&?)", "i")); return j ? j[1] : j; }</script>
</head><body><div id="app"><div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div></div>
</body><style></style><script src="https://unpkg.com/vue/dist/vue.js"></script>
<script type="text/javascript"src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script><script>window.app = new Vue({el: '#app',data: function () {return {}},watch: {},created: function () {setTimeout(function () {AppleID.auth.init({clientId: '填上你的clientId',scope: 'name email',redirectURI: 'https://myclient.cn/login_apple_redirect.html',state: "这个参数在各种跳转中会一直带上,你可以用来标记这次登录过程",nonce: '' + new Date().getTime(),usePopup: true});}, 10);},methods: {OnAppleSignIn: function (authorization) {var that = this;var req = {state: authorization.state,code: authorization.code,idToken: authorization.id_token};$.ajax({url: hostBase + "/fawork/AppleLoginGetResult", //请求的url地址dataType: "json", //返回格式为jsoncontentType: "application/json; charset=utf-8",async: true, //请求是否异步,默认为异步,这也是ajax重要特性data: JSON.stringify(req),async: true, //请求是否异步,默认为异步,这也是ajax重要特性type: "POST", //请求方式beforeSend: function () {//请求前的处理},success: function (rsp) {console.log(rsp);// 返回 uid和token},complete: function () {//请求完成的处理},error: function () {//请求出错处理}});}}});// Listen for authorization success.document.addEventListener('AppleIDSignInOnSuccess', function (event) {// Handle successful response.console.log(event);window.app.OnAppleSignIn(event.detail.authorization);});// Listen for authorization failures.document.addEventListener('AppleIDSignInOnFailure', function (event) {// Handle error.console.log(event);});
</script></html>

登录成功时,前端 AppleIDSignInOnSuccess事件中,打印的event参数值

二、服务器端代码

前端传递过来code和id_token,这是两个不同的校验方法

code是一种,不过在苹果登录中我不知道怎么用,我用的是id_token校验。

对于id_token,这就是JWT校验技术。

JWT的介绍,请看我用ChatGpt问的结果。

在JWT(JSON Web Token)中,id token是一种用于身份验证和认证的令牌。要验证id token的有效性,您可以遵循以下步骤:

  1. 解码id token:JWT由三部分组成,即头部、载荷和签名。使用Base64解码id token,您可以获取其中的头部和载荷信息。

  2. 验证签名:使用头部中提供的算法(通常是HMAC、RSA或ECDSA)和密钥,验证签名的正确性。您需要获取与签名算法相对应的密钥,并将其与头部和载荷一起使用相同的算法进行签名验证。如果签名验证失败,则表示id token被篡改过或者是伪造的。

  3. 验证令牌的有效期:在载荷中,id token包含了发行时间(issued time)和过期时间(expiration time)。您需要检查当前时间是否在有效期范围内。如果当前时间在过期时间之后,说明id token已过期,不能再继续使用。

  4. 校验接收者:在载荷中,id token还可以包含一个接收者(audience)字段,用于指定该令牌的预期接收者。您可以检查接收者字段是否与您的应用程序的标识符匹配,以确保id token只能被合法的接收者使用。

  5. 可选的附加校验:根据您的需求,您还可以进行其他的校验,例如验证签发者(issuer)字段、检查令牌是否被撤销等。

需要注意的是,为了保证安全性,您应该将密钥存储在安全的位置,并定期更换密钥以防止泄露和滥用。此外,使用受信任的JWT库来处理JWT的解码和验证操作,而不是自行编写代码,以确保正确性和安全性。

        <dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred --><version>0.11.5</version><scope>runtime</scope></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.22</version></dependency><dependency><groupId>commons-codec</groupId><artifactId>commons-codec</artifactId><version>1.15</version>
</dependency>
package cn.huali.fawork.constant;import cn.huali.fawork.exception.SelfException;
import cn.huali.fawork.utils.Base64Util;
import cn.huali.fawork.utils.HttpsUtils;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.apache.commons.codec.binary.Base64;import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.RSAPublicKeySpec;
import java.util.HashMap;
import java.util.Map;public class AppleAuthorizationConfig {private static final String APPLE_HOST_URL = "https://appleid.apple.com";private static final String APPLE_PUB_KEY_ENDPOINT = "https://appleid.apple.com/auth/keys";private static final String APPLE_AUTH_TOKEN_ENDPOINT = "https://appleid.apple.com/auth/token";private static final String  Apple_Client_Id = "填上你的clientId";public static AppleAuthCheckResult checkAuth(String idToken, long unixtimeAt) throws UnsupportedEncodingException {AppleAuthCheckResult result = new AppleAuthCheckResult();result.isOK = false;try {String[] identityTokens = idToken.split("\\.");String headerStr = new String(Base64Util.decodeWithUTF8(identityTokens[0]));JSONObject jsonObjectHeader = JSON.parseObject(headerStr);String contentStr = new String(Base64Util.decodeWithUTF8(identityTokens[1]));JSONObject jsonObjectContent = JSON.parseObject(contentStr);System.out.println(headerStr);System.out.println(contentStr);String kid = jsonObjectHeader.getString("kid");String alg = jsonObjectHeader.getString("alg");String iss = jsonObjectContent.getString("iss");String aud = jsonObjectContent.getString("aud");String exp = jsonObjectContent.getString("exp");String iat = jsonObjectContent.getString("iat");String sub = jsonObjectContent.getString("sub");String nonce = jsonObjectContent.getString("nonce");String c_hash = jsonObjectContent.getString("c_hash");String email = jsonObjectContent.getString("email");String email_verified = jsonObjectContent.getString("email_verified");boolean is_private_email = jsonObjectContent.getBooleanValue("is_private_email");String auth_time = jsonObjectContent.getString("auth_time");String nonce_supported = jsonObjectContent.getString("nonce_supported");result.email = email;result.sub = sub;result.is_private_email = is_private_email;result.tokenPayload = contentStr;JSONObject publicKey = getPublicKey(APPLE_PUB_KEY_ENDPOINT, kid);PublicKey rsaPublicKey = getRSAPublicKey(publicKey.getString("n"), publicKey.getString("e"));// require部分,是jwt自动帮你校验,如果校验不通过,会报异常
// 切记不要在require部分校验auth_time和iat两部分,Jwt有bug,会报异常的,所以还是手动校验比较好JwtParser jwtParser = Jwts.parserBuilder().setSigningKey(rsaPublicKey).requireAudience(Apple_Client_Id) //一般是项目包名称.requireIssuer(APPLE_HOST_URL) //固定值//.require("auth_time", auth_time) //这里做了个简单的验证,如果auth_time == iat则是有效的。.require("email", email).require("sub", sub).build();Jws<Claims> claimsJws = jwtParser.parseClaimsJws(idToken);Claims claims = claimsJws.getBody();if (!claims.get("auth_time").toString().equalsIgnoreCase(auth_time)) {result.isOK = false;result.msg = "auth_time不一致";} else if (!claims.get("iat").toString().equalsIgnoreCase(iat)) {result.isOK = false;result.msg = "iat不一致";} else if (!auth_time.equalsIgnoreCase(iat)) {result.isOK = false;result.msg = "iat和auth_time不一致";} else if (!claims.get("exp").toString().equalsIgnoreCase(exp)) {result.isOK = false;result.msg = "exp不一致";} else if (Long.parseLong(exp) < unixtimeAt) {result.isOK = false;result.msg = "exp已过期";}result.isOK = true;result.msg = "";}catch (SelfException e) {result.isOK = false;result.msg = e.msg;}catch (Exception e) {result.isOK = false;result.msg = e.getMessage();}return result;}public static class AppleAuthCheckResult {public boolean isOK;public String msg;/*** 用户唯一账号*/public String sub;public String email;public boolean is_private_email;public String tokenPayload;}private static volatile Map<String, JSONObject> pubKeyMap = new HashMap<>();private static PublicKey getRSAPublicKey(String modulus, String exponent) {try {BigInteger bigModule = new BigInteger(1, Base64.decodeBase64(modulus));BigInteger bigExponent = new BigInteger(1, Base64.decodeBase64(exponent));RSAPublicKeySpec keySpec = new RSAPublicKeySpec(bigModule, bigExponent);KeyFactory keyFactory = KeyFactory.getInstance("RSA");PublicKey publicKey = keyFactory.generatePublic(keySpec);return publicKey;} catch (Exception e) {return null;}}/*** {* "keys": [* {* "kty": "RSA",* "kid": "W6WcOKB",* "use": "sig",* "alg": "RS256",* "n": "2Zc5d0-zkZ5AKmtYTvxHc3vRc41YfbklflxG9SWsg5qXUxvfgpktGAcxXLFAd9Uglzow9ezvmTGce5d3DhAYKwHAEPT9hbaMDj7DfmEwuNO8UahfnBkBXsCoUaL3QITF5_DAPsZroTqs7tkQQZ7qPkQXCSu2aosgOJmaoKQgwcOdjD0D49ne2B_dkxBcNCcJT9pTSWJ8NfGycjWAQsvC8CGstH8oKwhC5raDcc2IGXMOQC7Qr75d6J5Q24CePHj_JD7zjbwYy9KNH8wyr829eO_G4OEUW50FAN6HKtvjhJIguMl_1BLZ93z2KJyxExiNTZBUBQbbgCNBfzTv7JrxMw",* "e": "AQAB"* },* {* "kty": "RSA",* "kid": "fh6Bs8C",* "use": "sig",* "alg": "RS256",* "n": "u704gotMSZc6CSSVNCZ1d0S9dZKwO2BVzfdTKYz8wSNm7R_KIufOQf3ru7Pph1FjW6gQ8zgvhnv4IebkGWsZJlodduTC7c0sRb5PZpEyM6PtO8FPHowaracJJsK1f6_rSLstLdWbSDXeSq7vBvDu3Q31RaoV_0YlEzQwPsbCvD45oVy5Vo5oBePUm4cqi6T3cZ-10gr9QJCVwvx7KiQsttp0kUkHM94PlxbG_HAWlEZjvAlxfEDc-_xZQwC6fVjfazs3j1b2DZWsGmBRdx1snO75nM7hpyRRQB4jVejW9TuZDtPtsNadXTr9I5NjxPdIYMORj9XKEh44Z73yfv0gtw",* "e": "AQAB"* },* {* "kty": "RSA",* "kid": "lVHdOx8ltR",* "use": "sig",* "alg": "RS256",* "n": "nXDu9MPf6dmVtFbDdAaal_0cO9ur2tqrrmCZaAe8TUWHU8AprhJG4DaQoCIa4UsOSCbCYOjPpPGGdE_p0XeP1ew55pBIquNhNtNNEMX0jNYAKcA9WAP1zGSkvH5m39GMFc4SsGiQ_8Szht9cayJX1SJALEgSyDOFLs-ekHnexqsr-KPtlYciwer5jaNcW3B7f9VNp1XCypQloQwSGVismPHwDJowPQ1xOWmhBLCK50NV38ZjobUDSBbCeLYecMtsdL5ZGv-iufddBh3RHszQiD2G-VXoGOs1yE33K4uAto2F2bHVcKOUy0__9qEsXZGf-B5ZOFucUkoN7T2iqu2E2Q",* "e": "AQAB"* }* ]* }** @param url* @param kid* @return*/private static JSONObject getPublicKey(String url, String kid) {if (!pubKeyMap.containsKey(kid)) {String allPubKeyJsonStr = getPublicKeyFromServer(url);JSONObject jsonObjectAllPubKey = JSON.parseObject(allPubKeyJsonStr);JSONArray keysArray = jsonObjectAllPubKey.getJSONArray("keys");if (keysArray.size() > 0) {pubKeyMap.clear();for (int i = 0; i < keysArray.size(); i++) {JSONObject key = keysArray.getJSONObject(i);String tmpKid = key.getString("kid");pubKeyMap.put(tmpKid, key);}}}JSONObject keyJsonObject = pubKeyMap.getOrDefault(kid, null);if (keyJsonObject == null) {throw new SelfException("没有找到PublicKey:kid=" + kid);}return keyJsonObject;}private static String getPublicKeyFromServer(String url) {HttpsUtils.HttpRsp httpRsp = HttpsUtils.get(url);if (httpRsp.statusCode != 200) {throw new SelfException("获取PublicKey出错:" + httpRsp.statusCode + "," + httpRsp.statusDesc);}return httpRsp.content;}
}
package cn.huali.fawork.utils;import java.io.UnsupportedEncodingException;
import java.util.Base64;public class Base64Util {public static String encode(byte[] bytes) {byte[] newBytes = Base64.getEncoder().encode(bytes);String content = new String(newBytes);return content;}public static byte[] decode(String content) {byte[] newBytes = Base64.getDecoder().decode(content.getBytes());return newBytes;}public static String encodeWithUTF8(byte[] bytes) throws UnsupportedEncodingException {byte[] newBytes = Base64.getEncoder().encode(bytes);String content = new String(newBytes, "UTF-8");return content;}public static byte[] decodeWithUTF8(String content) throws UnsupportedEncodingException {byte[] newBytes = Base64.getDecoder().decode(content.getBytes("UTF-8"));return newBytes;}
}
/****/
package cn.huali.fawork.utils;import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;/*** @author Administrator**/
public class HttpsUtils {public static HttpRsp get(String url) {return doGet(url, null, null);}public static HttpRsp get(String url, String param) {return doGet(url, param, null);}public static HttpRsp get(String url, Map<String, String> param) {return doGet(url, makeParam(param), null);}public static HttpRsp get(String url, Map<String, String> param, Map<String, String> headMap) {return doGet(url, makeParam(param), headMap);}public static HttpRsp get(String url, String param, Map<String, String> headMap) {return doGet(url, param, headMap);}private static HttpRsp doGet(String url, String param, Map<String, String> headMap) {HttpRsp rsp = new HttpRsp();BufferedReader in = null;try {if (param != null && param.isEmpty() == false) {if (url.endsWith("&") || url.endsWith("?")) {url += param;} else if (url.contains("?")) {url += "&" + param;} else {url += "?" + param;}}HttpURLConnection connection = (HttpURLConnection) (new URL(url)).openConnection();connection.setRequestMethod("GET");connection.setDoInput(true);if (headMap != null && headMap.isEmpty() == false) {// 设置包头Iterator<Entry<String, String>> it = headMap.entrySet().iterator();Entry<String, String> entry = null;while (it.hasNext()) {entry = it.next();System.out.println(entry.getKey() + ":" + entry.getValue());connection.setRequestProperty(entry.getKey(), entry.getValue());}}in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));StringBuffer sb = new StringBuffer();String line;while ((line = in.readLine()) != null) {sb.append(line + System.lineSeparator());}rsp.content = sb.toString();rsp.headerFieldsMap = connection.getHeaderFields();rsp.statusCode = connection.getResponseCode();rsp.statusDesc = connection.getResponseMessage();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {try {if (in != null) {in.close();}} catch (Exception e) {e.printStackTrace();}}return rsp;}public static HttpRsp post(String url) {return doPost(url, null, null);}public static HttpRsp post(String url, String param) {return doPost(url, param, null);}public static HttpRsp post(String url, Map<String, String> param) {return doPost(url, makeParam(param), null);}public static HttpRsp post(String url, Map<String, String> param, Map<String, String> headMap) {return doPost(url, makeParam(param), headMap);}public static HttpRsp post(String url, String param, Map<String, String> headMap) {return doPost(url, param, headMap);}private static HttpRsp doPost(String url, String param, Map<String, String> headMap) {HttpRsp rsp = new HttpRsp();PrintWriter out = null;BufferedReader in = null;try {HttpURLConnection connection = (HttpURLConnection) (new URL(url)).openConnection();connection.setRequestMethod("POST");connection.setDoInput(true);connection.setDoOutput(true);if (headMap != null && headMap.isEmpty() == false) {// 设置包头Iterator<Entry<String, String>> it = headMap.entrySet().iterator();Entry<String, String> entry = null;while (it.hasNext()) {entry = it.next();connection.setRequestProperty(entry.getKey(), entry.getValue());}}if (param != null && param.isEmpty() == false) {out = new PrintWriter(connection.getOutputStream());out.print(param);out.flush();}in = new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));StringBuffer sb = new StringBuffer();String line;while ((line = in.readLine()) != null) {sb.append(line + System.lineSeparator());}rsp.content = sb.toString();rsp.headerFieldsMap = connection.getHeaderFields();rsp.statusCode = connection.getResponseCode();rsp.statusDesc = connection.getResponseMessage();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {try {if (in != null) {in.close();}} catch (Exception e) {e.printStackTrace();}}return rsp;}/*** 将参数组织在一块** @param map* @return*/public static String makeParam(Map<String, String> map) {StringBuffer sb = new StringBuffer();Iterator<Entry<String, String>> it = map.entrySet().iterator();Entry<String, String> entry = null;if (it.hasNext()) {entry = it.next();sb.append(entry.getKey() + "=" + entry.getValue());}while (it.hasNext()) {entry = it.next();sb.append("&" + entry.getKey() + "=" + entry.getValue());}return sb.toString();}public static class HttpRsp {public int statusCode;public String statusDesc;public String content;public Map<String, List<String>> headerFieldsMap = new HashMap<String, List<String>>();@Overridepublic String toString() {return "statusCode=" + statusCode + ", statusDesc=" + statusDesc + ", content=" + content;}public List<String> getCookie() {if (headerFieldsMap != null) {return headerFieldsMap.get("Set-Cookie");} else {return null;}}}
}
package cn.huali.fawork.exception;/*** 此处的异常一定要集成于RuntimeException,是为了数据库事务回滚,请不要改动*/
public class SelfException extends RuntimeException {public Integer code;public String msg;public Object data;public SelfException(Exception e) {super(e);this.code = 1;this.msg = e.getLocalizedMessage();}public SelfException(int code, String msg) {super(msg);this.code = code;this.msg = msg;}public SelfException(String msg) {super(msg);this.code = 1;this.msg = msg;}public SelfException(int code, String msg, Object data) {super(msg);this.code = code;this.msg = msg;this.data = data;}public String toString() {return "code=" + code + ",msg=" + msg;}
}

这段代码是关键,即验证id_token是否有效;也验证require部分的字段是否存在,是否一致。注意,如果验证不通过,这段代码会报Exception的,如果报了Exception说明id_token是无效的。

这一部分,你主要验证一下 auth_time 和 iat 两个时间是否过了期限,其它无所谓。我写的可代码可能有点多余,你自己看着办。

三、创建clientId

Sign In - Apple

1、创建一个Services IDs

看到没,这个Identifier就是clientId

后面我就不截图了,反正会关联一个 appId;也会填一个域名,域名就是前段登录的网页的域名;redirectURI或者returnURI,就是登录后跳转的页面,与客户端网页代码中的那个字段保持一致即可,一般对于usePopup=true时,这个字段用不上。

2、在AppId中,启用登录功能

点上图中的那个Edit按钮后,再进行配置

相关文章:

Electron Apple SignIn 登录

本人写博客&#xff0c;向来主张&#xff1a;代码要完整&#xff0c;代码可运行&#xff0c;文中不留下任何疑惑。 最讨厌写博客&#xff0c;代码只留下片段&#xff0c;文中关键的东西没写清楚。之前看了那么多文章&#xff0c;就是不告诉我clientId从哪来的。 官方资料地址&…...

常用中间件漏洞

IIS6 IIS7 安装 控制面板-----打开关闭windows功能 添加角色-----添加IIS 启动之后访问localhost 复现 服务器换成IIS7 访问报错 大概就是缺少CGI模块 问题解决 添加php-cgi的路径 添加脚本映射 修改php.ini文件 将 cgi.fix_pathinfo1 然后设置一个图片 访问 在后缀加上/.…...

Windows系统使用手册

点击前往查看&#x1f517;我的博客文章目录 Windows系统使用手册 文章目录 Windows系统使用手册Windows10解决大小核调度问题Windows系统安装软件Windows系统Typora快捷键Windows系统压缩包方式安装redisWindows安装dockerWindows系统的docker设置阿里源Windows系统下使用doc…...

mp4文件可以转成mp3音频吗

现在是个非常流行刷短视频一个年代&#xff0c;刷短视似乎成了人们休闲娱乐的一种方式&#xff0c;在日常刷短视频过程中&#xff0c;肯定会有很多同学被短视频 bgm 神曲洗脑&#xff0c;比如很多被网红翻唱带火的歌曲&#xff0c;例如其中"不负人间”&#xff0c;就是其中…...

Java-IO流【登录注册小项目】

♥️作者&#xff1a;白日参商 &#x1f935;‍♂️个人主页&#xff1a;白日参商主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…...

数字化金融时代:探讨全球金融科技创新的最新动态

在当今数字化金融时代&#xff0c;金融科技创新如影随形&#xff0c;迅猛发展。本文将深入探讨全球范围内金融科技的最新动态&#xff0c;剖析各地新兴趋势与突破。从区块链技术的应用到人工智能在金融领域的崭露头角&#xff0c;我们将一一解读这个正在不断演变的金融科技画卷…...

LeetCode:206. 反转链表

力扣链接 算法思想&#xff1a;由于单链表是单向的&#xff0c;想要对当前元素进行操作&#xff0c;需找到前一个元素。本题利用双指针&#xff0c;初始pre指针指向NULL&#xff0c;cur指针指向head.再对局部翻转之前&#xff0c;先把下一个结点存到temp指针中。当进行完如下代…...

linux 安装nginx

介绍 官网 https://nginx.org/en/download.html 在安装nginx前首先要确认系统中安装了gcc、pcre-devel、zlib-devel、openssl-devel linux 检查是否安装过某软件包 yum -y install gcc pcre-devel zlib-devel openssl openssl-devel #下载 wget https://nginx.org/downloa…...

1.C语言——基础知识

C语言基础知识 1.第一个C语言程序2.注释3.标识符4.关键字5.数据类型6.变量7.常量8.运算符9.输入输出输入输出 1.第一个C语言程序 C语言的编程框架 #include <stdio.h> int main() {/* 我的第一个 C 程序 */printf("Hello, World! \n");return 0; }2.注释 单行…...

Redis 存在线程安全问题吗?为什么?

一个工作了 5 年的粉丝私信我。 他说自己准备了半年时间&#xff0c;想如蚂蚁金服&#xff0c;结果第一面就挂了&#xff0c;非常难过。 问题是&#xff1a; “Redis 存在线程安全问题吗&#xff1f;” 一、问题解析 关于这个问题&#xff0c;我从两个方面来回答。 第一个&a…...

无人机测绘助力实现高效、安全的城市规划

随着城市化进程的不断加快&#xff0c;城市规划显得尤为重要。而无人机测绘技术作为一种创新的工具&#xff0c;为城市规划提供了更加高效、安全的解决方案。它通过快速、精确的数据采集和分析&#xff0c;为行业提供有力的决策支持&#xff0c;助力城市规划的现代化和可持续发…...

实验七 RMAN恢复管理器

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎关注&#xff1a;&#x1f44d;点赞&#x1f64c;收藏✍️留言 &#x1f3c7;码字不易&#xff0c;你的&#x1f44d;点赞&#x1f64c;收藏❤️关注对我真的很重要&…...

未来 AI 可能给哪些产业带来哪些进步与帮助?

AI时代如何要让公司在创新领域领先吗&#xff1f;拥抱这5种创新技能&#xff0c;可以帮助你的公司应对不断变化。包括人工智能、云平台应用、数据分析、 网络安全和体验设计。这些技能可以帮助你提高业务效率、保护公司知识资产、明智决策、满足客户需求并提高销售额。 现在就加…...

Java医院信息管理系统

技术框架&#xff1a; springboot shiro layui jquery thymeleaf nginx 有需要的可以联系我。 运行环境&#xff1a; jdk8 mysql IntelliJ IDEA maven项目功能&#xff1a; 本项目是用springbootlayuishiro写的医院管理系统&#xff0c;系统的业务比较复杂&#x…...

QT+OSG/osgEarth编译之八十:ive+Qt编译(一套代码、一套框架,跨平台编译,版本:OSG-3.6.5插件库osgdb_ive)

文章目录 1、osgdb_ive介绍2、文件分析3、pro文件4、编译实践1、osgdb_ive介绍 通过osgdb_ive,OpenSceneGraph开源库能方便地读取ive格式的三维文件。 ive(Interchangeable Virtual Environment)是一种三维图形数据交换格式,主要用于虚拟现实和增强现实领域的场景数据交…...

Webpack5入门到原理3:基本配置

在开始使用 Webpack 之前&#xff0c;我们需要对 Webpack 的配置有一定的认识。 5 大核心概念 entry&#xff08;入口&#xff09; 指示 Webpack 从哪个文件开始打包 output&#xff08;输出&#xff09; 指示 Webpack 打包完的文件输出到哪里去&#xff0c;如何命名等 l…...

全开源多城市同城信息小程序源码(Laravel 框架),同城分类信息发布便民小程序系统【非DZ】

同城生活分类信息小程序&#xff0c;人才招聘、房产二手 多城市地区同城分类信息发布&#xff0c;商家入驻等功能 小程序前后端代码开源无加密&#xff0c;可进行二次开发 【源码运行要求】 1、需要已认证的微信小程序 2、已备案的域名及服务器空间 推荐使用宝塔面板LinuxPHP…...

PHP学习笔记1

//语法错误&#xff08;syntax error&#xff09;在语法分析阶段&#xff0c;源代码并未被执行&#xff0c;故不会有任何输出。 /* 【命名规则】 */ 常量名 类常量建议全大写&#xff0c;单词间用下划线分隔 // MIN_WIDTH 变量名建议用下划线方式分隔 // $var_na…...

C语言从入门到实战——文件操作

文件操作 前言一、 为什么使用文件二、 什么是文件2.1 程序文件2.2 数据文件2.3 文件名 三、 二进制文件和文本文件四、 文件的打开和关闭4.1 流和标准流4.1.1 流4.1.2 标准流 4.2 文件指针4.3 文件的打开和关闭4.4 文件的路径 五、 文件的顺序读写5.1 顺序读写函数介绍fgetcfp…...

数据结构中的一棵树

一、树是什么&#xff1f; 有根有枝叶便是树&#xff01;根只有一个&#xff0c;枝叶可以有&#xff0c;也可以没有&#xff0c;可以有一个&#xff0c;也可以有很多。 就像这样&#xff1a; 嗯&#xff0c;应该是这样&#xff1a; 二、一些概念 1、高度 树有多高&#x…...

Python|GIF 解析与构建(5):手搓截屏和帧率控制

目录 Python&#xff5c;GIF 解析与构建&#xff08;5&#xff09;&#xff1a;手搓截屏和帧率控制 一、引言 二、技术实现&#xff1a;手搓截屏模块 2.1 核心原理 2.2 代码解析&#xff1a;ScreenshotData类 2.2.1 截图函数&#xff1a;capture_screen 三、技术实现&…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

条件运算符

C中的三目运算符&#xff08;也称条件运算符&#xff0c;英文&#xff1a;ternary operator&#xff09;是一种简洁的条件选择语句&#xff0c;语法如下&#xff1a; 条件表达式 ? 表达式1 : 表达式2• 如果“条件表达式”为true&#xff0c;则整个表达式的结果为“表达式1”…...

【AI学习】三、AI算法中的向量

在人工智能&#xff08;AI&#xff09;算法中&#xff0c;向量&#xff08;Vector&#xff09;是一种将现实世界中的数据&#xff08;如图像、文本、音频等&#xff09;转化为计算机可处理的数值型特征表示的工具。它是连接人类认知&#xff08;如语义、视觉特征&#xff09;与…...

C++使用 new 来创建动态数组

问题&#xff1a; 不能使用变量定义数组大小 原因&#xff1a; 这是因为数组在内存中是连续存储的&#xff0c;编译器需要在编译阶段就确定数组的大小&#xff0c;以便正确地分配内存空间。如果允许使用变量来定义数组的大小&#xff0c;那么编译器就无法在编译时确定数组的大…...

论文笔记——相干体技术在裂缝预测中的应用研究

目录 相关地震知识补充地震数据的认识地震几何属性 相干体算法定义基本原理第一代相干体技术&#xff1a;基于互相关的相干体技术&#xff08;Correlation&#xff09;第二代相干体技术&#xff1a;基于相似的相干体技术&#xff08;Semblance&#xff09;基于多道相似的相干体…...