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", //返回格式为json
                    contentType: "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 block
            e.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 block
            e.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>>();

        @Override
        public 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按钮后,再进行配置

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

c++学习笔记-STL案例-机房预约系统4-管理员模块

前言 衔接上一篇“c学习笔记-STL案例-机房预约系统3-登录模块”&#xff0c;本文主要设计管理员模块&#xff0c;从管理员登录和注销、添加账号、显示账号、查看机房、清空预约五个功能进行分析和实现。 目录 7 管理员模块 7.1 管理员登录和注销 7.1.1 构造函数 ​编辑7.1.2…

【算法】队列+bfs算法 解决树的相关算法题(C++)

文章目录 1. 前言2. 算法题429.N叉树的层序遍历103.二叉树的锯齿形层序遍历662.二叉树最大宽度515.在每个树行中找最大值 1. 前言 队列 与 宽度优先算法&#xff08;BFS&#xff09;是解决很多算法问题的常见工具。 BFS通过逐层遍历图或树的节点来寻找解决问题的最短路径或最…

画图案例分享

案例 1 from scipy.misc import derivative from scipy.integrate import quad import matplotlib.pyplot as plt import numpy as np import pandas as pd from scipy.stats import norm import warningsplt.style.use(ggplot) np.random.seed(37) warnings.filterwarnings(i…

《Linux C编程实战》笔记:出错处理

这一节书上把它放到线程这一章&#xff0c;按理说应该在前面就讲了 头文件errno.h定义了变量errno&#xff0c;它存储了错误发生时的错误码&#xff0c;通过错误码可以得到错误的信息 程序开始执行时&#xff0c;变量errno被初始化为0。很多库函数在执行过程中遇到错误时就会…

排序算法9----计数排序(C)

计数排序是一种非比较排序&#xff0c;不比较大小 。 1、思想 计数排序又称为鸽巢原理&#xff0c;是对哈希直接定址法的变形应用。 2、步骤 1、统计数据&#xff1a;统计每个数据出现了多少次。&#xff08;建立一个count数组&#xff0c;范围从[MIN,MAX],MAX代表arr中…

关于gltf模型格式文件的学习

目录 glTF模型 小黄鸭的gltf模型 字段分析 scene nodes meshes primitives attributes indices mode material accessors bufferView byteOffset count componentType type materials textures images samplers magFilter与minFilter wrapS与wrapT 进行…

10个用于Android开发的有用的Kotlin库及示例

10个用于Android开发的有用的Kotlin库及示例 在Android开发领域&#xff0c;Kotlin已成为一门领先的语言&#xff0c;带来了现代语法和功能的浪潮。随着Kotlin的崛起&#xff0c;涌现出了许多专为其定制的库&#xff0c;进一步增强了开发体验。本文将深入介绍其中的10个库&…

2024年美赛数学建模思路 - 案例:异常检测

文章目录 赛题思路一、简介 -- 关于异常检测异常检测监督学习 二、异常检测算法2. 箱线图分析3. 基于距离/密度4. 基于划分思想 建模资料 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 一、简介 – 关于异常…

C语言从入门到实战——动态内存管理

动态内存管理 前言一、 为什么要有动态内存分配二、 malloc和free2.1 malloc2.2 free 三、calloc和realloc3.1 calloc3.2 realloc 四、常见的动态内存的错误4.1 对NULL指针的解引用操作4.2 对动态开辟空间的越界访问4.3 对非动态开辟内存使用free释放4.4 使用free释放一块动态开…

赤藓糖醇行业研究:预计2029年将达到3.5亿美元

赤藓糖醇是一种四碳糖醇&#xff0c;存在于多种食物中&#xff0c;如葡萄、梨、西瓜等&#xff0c;可由微生物发酵法和化学合成法两种方法制备&#xff0c;目前商业化生产中均采用微生物发酵法。赤藓糖醇由葡萄糖发酵制作而成&#xff0c;上游原料主要包括葡萄糖、玉米淀粉糖和…

Android中的anr定位指导与建议

1.背景 8月份安卓出现了一次直播间卡死(ANR)问题&#xff0c;且由于排查难度较大&#xff0c;持续了较长时间。本文针对如何快速定位安卓端出现ANR问题进行总结和探讨. 这里大致补充一下当时的情况,当时看到情景的是从某一个特定的场景下进入直播间后整个直播间界面立刻就卡住…

23年11月移动广告行业大盘趋势,借鉴双 11 ,年货节该如何提高广告收益

前言 年货节开始啦&#xff0c;我们可以借鉴2023年双11期间的广告大盘趋势&#xff0c;洞悉如何在大型促销期间调整广告运营策略以提升效果。年货节是一个绝佳的时机&#xff0c;可以利用在双11期间积累的经验和策略&#xff0c;进行相应的调整和优化。通过精准定位广告投放高…

Elasticsearch:和 LIamaIndex 的集成

LlamaIndex 是一个数据框架&#xff0c;供 LLM 应用程序摄取、构建和访问私有或特定领域的数据。 LlamaIndex 是开源的&#xff0c;可用于构建各种应用程序。 在 GitHub 上查看该项目。 安装 在 Docker 上设置 Elasticsearch 使用以下 docker 命令启动单节点 Elasticsearch 实…

maven无法识别本地maven仓库包解决方案

前言&#xff1a;由于本地maven仓库已经有了相关依赖包&#xff0c;idea还是去远程仓库下载(不知何原因&#xff0c;生产上到远程仓库的网络突然不通了)&#xff0c;故需要自己本地上传相关包到生产主机并修改setttings文件来强制读取本地仓库方案 settings文件修改如下方式即…

iPad如何连接到Wi-Fi,这里提供详细步骤

这篇文章解释了如何将iPad连接到Wi-Fi&#xff0c;无论是公共Wi-Fi网络还是需要密码的专用网络。 将iPad连接到Wi-Fi 当你想让iPad联机时&#xff0c;请按照以下步骤连接到Wi-Fi&#xff1a; 1、在iPad的主屏幕上&#xff0c;点击设置。 2、点击Wi-Fi。 3、要启动iPad搜索附…

数据库作业三

1.创建student和score表 2.为student表和score表增加记录 3.查询student表的所有记录 4.查询student表的第2条到4条记录 5.从student表查询所有学生的学号&#xff08;id&#xff09;、姓名&#xff08;name&#xff09;和院系&#xff08;department&#xff09;的信息 6.从st…

Zabbix6.4 图形乱码怎么办

Zabbix6.4 图形乱码怎么办 Zabbix6.4 安装后&#xff0c;进入主机图形展示&#xff0c;你会发现文字部分乱成了乱码。 找一台Microsoft Windows 7/10/11的电脑&#xff0c;打开C:\Windows\Fonts 找到【楷体 常规】&#xff0c;将字体复制到桌面。 桌面上就会多出simkai.ttf字…

5.2 基于深度学习和先验状态的实时指纹室内定位

文献来源 Nabati M, Ghorashi S A. A real-time fingerprint-based indoor positioning using deep learning and preceding states[J]. Expert Systems with Applications, 2023, 213: 118889.&#xff08;5.2_基于指纹的实时室内定位&#xff0c;使用深度学习和前一状态&…

抖音弹幕直播玩法汉字找不同文字找不同无人值执守自动玩游戏自带语音播报的开发日志

#找不同# 要解决如下几个问题&#xff1a; 1.声音sprite的录制和调用&#xff0c;解决方案以及解决库如下&#xff1a; howler.min.js://一款不错的音频播放js库。 2.鼠标自动飘浮,使用的库 anime.min.js 3.资源预加载 preload.min.js 4.其它使用到的库 jquery,vue

Docker安装开源Blog(Typecho)

前言 首先这个镜像是centos7.9进行安装PHP环境&#xff0c;然后挂载目录去运行的&#xff0c;镜像大概300MB左右&#xff0c;没学过PHP&#xff0c;没办法给Dockerfile文件 参考文章&#xff1a;Docker安装Typecho | D-y Blog感知不强&#xff0c;图一乐https://www.wlul.top…