C#获取企业微信《会话内容存档》

因为公司某些原因需要使用企业微信的会话内容存档内容,看微信的文档踩了一些坑,现在将项目代码记录下来,以备各位码农同行查阅。

项目使用 .NET8.0架构,节本结构如下图:

项目中的Lib是下载的微信SDK,项目地址为:https://wwcdn.weixin.qq.com/node/wework/images/sdk_win_v1.1.zip

1:建立项目Mian方法,没什么好说的:

  private static void Main(string[] args)
  {

      var getchat = new GetChatDataService();
      getchat.GetChatDataList();

  }

 2:GetChatDataService 获取消息类:

    public class GetChatDataService
    {

        private long InitSDK()
        {
            long sdk = Finance.NewSdk();

            // 这里填写 企业微信 corpid,secret
            var corpid = "wwexxxxxx";//企业ID
            var secret = "xxxxxxxxxx";//企业secret
            Finance.Init(sdk, corpid, secret);

            return sdk;
        }

        public void GetChatDataList()
        {
            try
            {
                var sdk = InitSDK();
                int seq = 1;
                int limit = 1000;// 每次最多请求1000 条
                int timeOut = 500;
                long slice = Finance.NewSlice();
                var ret = Finance.GetChatData(sdk, seq, limit, "", "", timeOut, slice);//获取会话记录数据
                CheckResultInt(ret, nameof(GetChatDataList));
                var resResultStr = this.GetContentFromSlice(slice);  //获取返回文本
                var resData = CheckAndGetResultText(resResultStr, "chatdata", nameof(GetChatDataList));
                #region
                JArray jArrayData = JArray.Parse(resData);
                foreach (var item in jArrayData)
                {
                    var chatData = this.DecryptChatData(item["encrypt_random_key"]?.ToString(), item["encrypt_chat_msg"]?.ToString());
                }
                #endregion
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }


        #region  解密 chatdata
        /// <summary>
        /// encrypt_random_key内容解密说明:
        /// encrypt_random_key是使用企业在管理端填写的公钥(使用模值为2048bit的秘钥),采用RSA加密算法进行加密处理后base64 encode的内容,加密内容为企业微信产生。RSA使用PKCS1。
        ///企业通过GetChatData获取到会话数据后:
        ///a) 需首先对每条消息的encrypt_random_key内容进行base64 decode, 得到字符串str1.
        ///b) 使用publickey_ver指定版本的私钥,使用RSA PKCS1算法对str1进行解密,得到解密内容str2.
        ///c) 得到str2与对应消息的encrypt_chat_msg,调用下方描述的DecryptData接口,即可获得消息明文。
        ///  解密 chatdata
        /// </summary>
        /// <param name="encrypt_random_key"></param>
        /// <param name="encrypt_chat_msg"></param>
        /// <returns></returns>
        public string DecryptChatData(string encrypt_random_key, string encrypt_chat_msg)
        {
            try
            {

                #region privatekey
                var privatekey = @"xxxxxxx";//RSA私钥
                #endregion


                if (string.IsNullOrWhiteSpace(privatekey))
                    throw new Exception("privatekey 私钥为空!");

                var sliceMsg = Finance.NewSlice();
                var random_key = RSAHelper.RSADecrypt(encrypt_random_key, "utf-8", privatekey) ;
                var ret = Finance.DecryptData(random_key, encrypt_chat_msg, sliceMsg);

                //得到str2与对应消息的encrypt_chat_msg,调用下方描述的DecryptData接口,即可获得消息明文
                CheckResultInt(ret, nameof(DecryptChatData));

                //获取返回文本
                var resResultStr = this.GetContentFromSlice(sliceMsg);

                return resResultStr;
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
        #endregion

        #region 获取文本
        /// <summary>
        /// 获取文本
        /// </summary>
        /// <param name="slice"></param>
        /// <returns></returns>
        private string GetContentFromSlice(long slice)
        {
            int len = Finance.GetSliceLen(slice);

            byte[] vbyte = new byte[len];

            var intPtr = Finance.GetContentFromSlice(slice);

            System.Runtime.InteropServices.Marshal.Copy(intPtr, vbyte, 0, vbyte.Length);

            return Encoding.UTF8.GetString(vbyte);
        }
        #endregion

        #region check
        /// <summary>
        /// 验证 sdk 返回的 int信息
        /// </summary>
        private void CheckResultInt(long ret, string methodName = "")
        {
            if (ret == 0) return;

            throw new Exception($"【{methodName}】 验证失败,返回为:{ret}");

        }

        /// <summary>
        ///  验证 sdk 返回的数据信息
        /// </summary>
        /// <param name="result">SDK返回的结果集</param>
        /// <param name="dataColumn">data 列名</param>
        /// <param name="methodName">请求的方法名</param>
        /// <returns></returns>
        private string CheckAndGetResultText(string result, string dataColumn, string methodName = "")
        {
            if (string.IsNullOrWhiteSpace(result))
                throw new Exception($"CheckResultText 【{methodName}】 验证失败,返回结果为空");

            try
            {
                JToken jToken = JToken.Parse(result);

                if (jToken["errcode"].ToString() == "0")
                {
                    return jToken[dataColumn].ToString();
                }

                throw new Exception($"【{methodName}】数据返回失败,errmsg:{jToken["errmsg"]}");

            }
            catch (Exception ex)
            {
                throw new Exception($"【{methodName}】解析失败,错误:{ex.Message}");
            }
        }
        #endregion
    }

 3:RSAHelper RSA加密解密类:

   public static class RSAHelper
   {
       private static string DEFAULT_CHARSET = "UTF-8";

       /// <summary>
       /// 加密
       /// </summary>
       /// <param name="content"></param>
       /// <param name="charset"></param>
       /// <param name="publicKeyPem"></param>
       /// <returns></returns>
       /// <exception cref="Exception"></exception>
       public static string RSAEncrypt(string content, string charset, string publicKeyPem)
       {
           try
           {
               //假设私钥长度为1024, 1024/8-11=117。
               //如果明文的长度小于117,直接全加密,然后转base64。(data.Length <= maxBlockSize)
               //如果明文长度大于117,则每117分一段加密,写入到另一个Stream中,最后转base64。while (blockSize > 0)                 

               //转为纯字符串,不带格式
               publicKeyPem = publicKeyPem.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "").Replace("\r", "").Replace("\n", "").Trim();

               RSA rsa = RSA.Create();
               rsa.ImportSubjectPublicKeyInfo(Convert.FromBase64String(publicKeyPem), out _);

               if (string.IsNullOrEmpty(charset))
               {
                   charset = DEFAULT_CHARSET;
               }
               byte[] data = Encoding.GetEncoding(charset).GetBytes(content);
               int maxBlockSize = rsa.KeySize / 8 - 11; //加密块最大长度限制
               if (data.Length <= maxBlockSize)
               {
                   byte[] cipherbytes = rsa.Encrypt(data, RSAEncryptionPadding.Pkcs1);
                   return Convert.ToBase64String(cipherbytes);
               }
               MemoryStream plaiStream = new MemoryStream(data);
               MemoryStream crypStream = new MemoryStream();
               byte[] buffer = new byte[maxBlockSize];
               int blockSize = plaiStream.Read(buffer, 0, maxBlockSize);
               while (blockSize > 0)
               {
                   byte[] toEncrypt = new byte[blockSize];
                   Array.Copy(buffer, 0, toEncrypt, 0, blockSize);
                   byte[] cryptograph = rsa.Encrypt(toEncrypt, RSAEncryptionPadding.Pkcs1);
                   crypStream.Write(cryptograph, 0, cryptograph.Length);
                   blockSize = plaiStream.Read(buffer, 0, maxBlockSize);
               }

               return Convert.ToBase64String(crypStream.ToArray(), Base64FormattingOptions.None);
           }
           catch (Exception ex)
           {
               throw new Exception("EncryptContent = " + content + ",charset = " + charset, ex);
           }
       }

       /// <summary>
       /// 解密
       /// </summary>
       /// <param name="content"></param>
       /// <param name="charset"></param>
       /// <param name="privateKeyPem"></param>
       /// <param name="keyFormat"></param>
       /// <returns></returns>
       /// <exception cref="Exception"></exception>
       public static string RSADecrypt(string content, string charset, string privateKeyPem, string keyFormat = "PKCS8")
       {
           try
           {
               //假设私钥长度为1024, 1024/8 =128。
               //如果明文的长度小于 128,直接全解密。(data.Length <= maxBlockSize)
               //如果明文长度大于 128,则每 128 分一段解密,写入到另一个Stream中,最后 GetString。while (blockSize > 0)                                 

               //转为纯字符串,不带格式
               privateKeyPem = privateKeyPem.Replace("-----BEGIN RSA PRIVATE KEY-----", "").Replace("-----END RSA PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", "").Trim();
               privateKeyPem = privateKeyPem.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", "").Trim();


               RSA rsaCsp = RSA.Create();
               if (keyFormat == "PKCS8")
                   rsaCsp.ImportPkcs8PrivateKey(Convert.FromBase64String(privateKeyPem), out _);
               else if (keyFormat == "PKCS1")
                   rsaCsp.ImportRSAPrivateKey(Convert.FromBase64String(privateKeyPem), out _);
               else
                   throw new Exception("只支持PKCS8,PKCS1");

               if (string.IsNullOrEmpty(charset))
               {
                   charset = DEFAULT_CHARSET;
               }
               byte[] data = Convert.FromBase64String(content);
               int maxBlockSize = rsaCsp.KeySize / 8; //解密块最大长度限制
               if (data.Length <= maxBlockSize)
               {
                   byte[] cipherbytes = rsaCsp.Decrypt(data, RSAEncryptionPadding.Pkcs1);
                   return Encoding.GetEncoding(charset).GetString(cipherbytes);
               }
               MemoryStream crypStream = new MemoryStream(data);
               MemoryStream plaiStream = new MemoryStream();
               byte[] buffer = new byte[maxBlockSize];
               int blockSize = crypStream.Read(buffer, 0, maxBlockSize);
               while (blockSize > 0)
               {
                   byte[] toDecrypt = new byte[blockSize];
                   Array.Copy(buffer, 0, toDecrypt, 0, blockSize);
                   byte[] cryptograph = rsaCsp.Decrypt(toDecrypt, RSAEncryptionPadding.Pkcs1);
                   plaiStream.Write(cryptograph, 0, cryptograph.Length);
                   blockSize = crypStream.Read(buffer, 0, maxBlockSize);
               }

               return Encoding.GetEncoding(charset).GetString(plaiStream.ToArray());
           }
           catch (Exception ex)
           {
               throw new Exception("DecryptContent = " + content + ",charset = " + charset, ex);
           }
       }
   }

4:企业微信调用类 Finance:

    public static class Finance
    {

        private const string DllName = "Lib\\WeWorkFinanceSdk.dll";

        [DllImport(DllName)]
        public extern static long NewSdk();

        /**
         * 初始化函数
         * Return值=0表示该API调用成功
         * 
         * @param [in]  sdk			NewSdk返回的sdk指针
         * @param [in]  corpid      调用企业的企业id,例如:wwd08c8exxxx5ab44d,可以在企业微信管理端--我的企业--企业信息查看
         * @param [in]  secret		聊天内容存档的Secret,可以在企业微信管理端--管理工具--聊天内容存档查看
         *						
         *
         * @return 返回是否初始化成功
         *      0   - 成功
         *      !=0 - 失败
         */
        [DllImport(DllName)]
        public extern static int Init(long sdk, String corpid, String secret);

        /**
         * 拉取聊天记录函数
         * Return值=0表示该API调用成功
         * 
         *
         * @param [in]  sdk				NewSdk返回的sdk指针
         * @param [in]  seq				从指定的seq开始拉取消息,注意的是返回的消息从seq+1开始返回,seq为之前接口返回的最大seq值。首次使用请使用seq:0
         * @param [in]  limit			一次拉取的消息条数,最大值1000条,超过1000条会返回错误
         * @param [in]  proxy			使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081
         * @param [in]  passwd			代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123
         * @param [out] chatDatas		返回本次拉取消息的数据,slice结构体.内容包括errcode/errmsg,以及每条消息内容。


         *
         * @return 返回是否调用成功
         *      0   - 成功
         *      !=0 - 失败	
         */
        [DllImport(DllName)] public extern static int GetChatData(long sdk, long seq, long limit, String proxy, String passwd, long timeout, long chatData);

        /**
         * 拉取媒体消息函数
         * Return值=0表示该API调用成功
         * 
         *
         * @param [in]  sdk				NewSdk返回的sdk指针
         * @param [in]  sdkFileid		从GetChatData返回的聊天消息中,媒体消息包括的sdkfileid
         * @param [in]  proxy			使用代理的请求,需要传入代理的链接。如:socks5://10.0.0.1:8081 或者 http://10.0.0.1:8081
         * @param [in]  passwd			代理账号密码,需要传入代理的账号密码。如 user_name:passwd_123
         * @param [in]  indexbuf		媒体消息分片拉取,需要填入每次拉取的索引信息。首次不需要填写,默认拉取512k,后续每次调用只需要将上次调用返回的outindexbuf填入即可。
         * @param [out] media_data		返回本次拉取的媒体数据.MediaData结构体.内容包括data(数据内容)/outindexbuf(下次索引)/is_finish(拉取完成标记)
         
         *
         * @return 返回是否调用成功
         *      0   - 成功
         *      !=0 - 失败
         */
        [DllImport(DllName)] public extern static int GetMediaData(long sdk, String indexbuf, String sdkField, String proxy, String passwd, long timeout, long mediaData);

        /**
         * @brief 解析密文
         * @param [in]  encrypt_key, getchatdata返回的encrypt_key
         * @param [in]  encrypt_msg, getchatdata返回的content
         * @param [out] msg, 解密的消息明文
         * @return 返回是否调用成功
         *      0   - 成功
         *      !=0 - 失败
         */
        [DllImport(DllName)]
        public extern static int DecryptData(String encrypt_key, String encrypt_msg, long msg);


        [DllImport(DllName)] public extern static void DestroySdk(long sdk);
        [DllImport(DllName)] public extern static long NewSlice();
        /**
         * @brief 释放slice,和NewSlice成对使用
         * @return 
         */
        [DllImport(DllName)] public extern static void FreeSlice(long slice);

        /**
         * @brief 获取slice内容
         * @return 内容
         */
        [DllImport(DllName)]
        // IntPtr 换成 String 就需要将下面这个注释启用
        //[return: MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(UTF8Marshaler))]
        public extern static IntPtr GetContentFromSlice(long slice);

        /**
         * @brief 获取slice内容长度
         * @return 内容
         */
        [DllImport(DllName)] public extern static int GetSliceLen(long slice);
        [DllImport(DllName)] public extern static long NewMediaData();
        [DllImport(DllName)] public extern static void FreeMediaData(long mediaData);

        /**
         * @brief 获取mediadata outindex
         * @return outindex
         */
        [DllImport(DllName)] public extern static String GetOutIndexBuf(long mediaData);
        /**
         * @brief 获取mediadata data数据
         * @return data
         */
        [DllImport(DllName)] public extern static IntPtr GetData(long mediaData);
        [DllImport(DllName)] public extern static int GetIndexLen(long mediaData);
        [DllImport(DllName)] public extern static int GetDataLen(long mediaData);

        /**
         * @brief 判断mediadata是否结束
         * @return 1完成、0未完成
         */
        [DllImport(DllName)] public extern static int IsMediaDataFinish(long mediaData);
    }

项目代码全部上完,不出意外的将企业ID与secret替换一下就可以取回消息记录了。

下面讲一下一些坑:

1:

encrypt_random_key内容解密说明:
encrypt_random_key是使用企业在管理端填写的公钥(使用模值为2048bit的秘钥),采用RSA加密算法进行加密处理后base64 encode的内容,加密内容为企业微信产生。RSA使用PKCS1。
企业通过GetChatData获取到会话数据后:
a) 需首先对每条消息的encrypt_random_key内容进行base64 decode,得到字符串str1.
b) 使用publickey_ver指定版本的私钥,使用RSA PKCS1算法对str1进行解密,得到解密内容str2.
c) 得到str2与对应消息的encrypt_chat_msg,调用下方描述的DecryptData接口,即可获得消息明文。

====

腾讯的文档这里提到的解密过程,a=>b=>c 三个步骤,a步骤是不需要的,只需要进行b=>c步骤就可以了,腾讯的客服回答是返回的encrypt_random_key是已经解码的数据,不需要用户再实现解码;

b步骤中有一个大坑:《使用publickey_ver指定版本的私钥,使用RSA PKCS1算法对str1进行解密》,开始用一直是使用RSA PKCS1算法解码,一直提示错误,后面心一横,使用RSA PKCS8解码,见了个鬼,OK了。所以b步骤这里使用的是RSA PKCS8算法解码。

之后直接调用腾讯SDK解密就可以得到原消息内容了。

====

在项目加载的时候腾讯的SDK需要放在项目的:xxx\RSA_Demo\RSA_Demo\bin\Debug\net8.0\Lib  这个文件夹下。

工5分dll文件。

====

关于RSA私钥公钥的生成,我使用的是openssl生成的。必须使用2048模生成,要不然通不过腾讯的验证。

私钥PRIVATE KEY,我使用xxxx替换了一些原本的内容:

-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDCIJXrZBUwBTc9
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ex3bNmDjIDEJdzztjZZBLO3Gx2whC9pq+cNLfYqWuBtiSzS9n1u1S0xDuLPGRHx8
qofhmAMPcy33gEQQeCUJuV5OG9jPTSxQZDVelIQQLxayZxwLeZgOWH7PVNdSLY30
Zb0TtoyW59orXA4krmLaZ1G1ZQKBgQDdsJjhVsHHGZ2JOLBte07p+v4VVyDPDPxi
SPhUV4Ak2XTNG5l1AEXHb0oGwltPLwxESiisioV/xHRS7ynlB4i+gxuj++ANwGZP
s8RhP6Yvem35yk6FiNAxfduS/pgAJkHMc9FlPJvLbEHxrfm5KeKfokwhfhAJdY8m
dF9nBTAkYQKBgEVT0pdWBrZPl0VeXap7vOQKkTQsxH2U7rbYztshNff/vKULsWTc
EVXLYYzzKyTe9VSfcBFFVDStboumqokzhAp0pJ2mlVSxylr9jRbQySWG3ypdR5Ml
KowxihYnOfG6iaXS2P+2xqEAMAcuJ3Sp8iuijSAuW/6E3aric5fv1AkxAoGAGlWF
A5eTszv2u7sxMgAo0qCPGCfebNoFDQPQA+zU+wud1VOG+iALKfKtX3os8I4NLfuF
M2HNE+1ZSBTC7ELl2oOmf+dGqTuGq8cV99tguVkYwUhn5XLoEEj8EU0O702cGVZU
tGrrstFsT/IzrOwt0HquAniAHS+Kzq2aO5mhK2ECgYAJwN3ZEjPkfGK5MXA/Xzdx
7if21jCEuicAUyWFS3bod8jLoyBHJHHyTunc/G8U1lrNBXB2EVQBMTDEVkXkAkCi
TM2b79MZY6Aj+mqDf8xmJJOhtrXOe+lbGOnRQZ5wS8YuCIpgS39xpcxcnsSfAHo8
ISAKzVwaoDs1fh6qgSC/9w==
-----END PRIVATE KEY-----

公钥 PUBLIC KEY:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwiCV62QVMAU3PetBOfZ+
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
RQIDAQAB
-----END PUBLIC KEY-----

以上就是取回企业微信消息记录的一些总结,希望能帮到广大踩坑的同行。

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

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

相关文章

9道软件测试面试题,刷掉90%的测试程序员

经历了“金9银10”&#xff0c;转眼2024年招聘季就要来了&#xff0c;没点真本事真技术&#xff0c;没点面试经验&#xff0c;不了解点职场套路&#xff0c;如何过五关斩六将&#xff1f;如何打败面试官&#xff1f;如何拿下那梦寐以求的offer&#xff1f; 如果你的跳槽意向已…

[C/C++]数据结构 希尔排序

&#x1f966;前言: 希尔排序也称 “缩小增量排序”&#xff0c;它也是一种插入类排序的方法,在学习希尔排序之前我们首先了解一下直接插入排序. 一: &#x1f6a9;直接插入排序 1.1 &#x1f31f;排序思路 直接插入排序的基本原理是将一条记录插入到已排好的有序表中&#x…

EDSR训练及测试教程

EDSR训练及测试教程 超分重建经典算法EDSR开源代码使用教程。 论文名称:Enhanced Deep Residual Networks for Single Image Super-Resolution,CVPR2017。 训练自己的数据集 由于EDSR开源代码只针对DIV2K数据集,在数据集加载时很多代码已经固定,因此在这里使用固定的文…

Android Studio 如何实现软件英文变中文教程

目录 前言 一、确认版本号 二、下载汉化包 三、汉化包安装 四、如何实现中英文切换 五、更多资源 前言 Android Studio是一款功能强大的集成开发环境&#xff08;IDE&#xff09;&#xff0c;用于开发Android应用程序。默认情况下&#xff0c;Android Studio的界面和…

STM32实战之深入理解I²C通信协议

目录 IC的物理层 IC的协议层 IC特点 IC 总线时序图 软件模拟IC时序分享 例程简介 例程分享 STM32的IC外设 IIC&#xff08;Inter-Integrated Circuit&#xff09;&#xff0c;也称为IC或TWI&#xff08;Two-Wire Interface&#xff09;&#xff0c;是一种广泛使用的串行…

数据仓库【1】:简介

数据仓库【1】&#xff1a;简介 1、诞生背景1.1、数据仓库诞生原因1.2、历史数据积存1.3、企业数据分析需要 2、基本概述2.1、数据仓库&#xff08;Data Warehouse&#xff0c;DW&#xff09;2.2、数据仓库特点2.3、数据仓库 VS 数据库 3、技术实现3.1、数据仓库建设方案3.2、传…

『CVE』简析CVE-2023-48795:SSH协议前缀截断攻击(Terrapin攻击)

文章目录 OpenSSH 9.6更新公告Terrapin攻击 (CVE-2023-48795)基本信息利用手段利用路径利用条件利用原理及示意图危害Terrapin-Scanner 基于Terrapin的潜在风险&#xff1a;CVE-2023-46445 & 46446参考完 OpenSSH 9.6更新公告 *ssh(1), sshd(8): implement protocol extens…

第十六节TypeScript 类

1、简介 TypeScript是面向对象的JavaScript。 类描述了所创建的对象共同的属性与方法。 2、类的定义 class class_name { // 类作用域 } 定义类的关键字是class&#xff0c;后面紧跟类名&#xff0c;类可以包含以下几个模块&#xff1a; 字段 – 字段是类里面声明的变量。字…

java练习之abstract (抽象) final(最终) static(静态) 练习

1&#xff1a;分析总结&#xff1a;写出private、abstract、static、final之间能否联动使用&#xff0c;并写出分析原因 private static final 之间可以任意结合 abstract 不可以与private static final 结合使用 2&#xff1a;关于三个修饰符描述不正确的是(AD) A. static …

实习知识整理8:如何实现将商品加入购物车

情景分析&#xff1a;当我们进入商品详情页面时&#xff0c;一般会有两个按钮&#xff0c;一个是加入购物车&#xff0c;另一个是直接购买的按钮&#xff0c;我们先来看看加入购物车是如何实现的 1. 数据库表分析 需要3个表&#xff1a;商品表item、用户表user、购物车表cart 需…

基于JAVA的医院门诊预约挂号系统 开源项目

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 功能性需求2.1.1 数据中心模块2.1.2 科室医生档案模块2.1.3 预约挂号模块2.1.4 医院时政模块 2.2 可行性分析2.2.1 可靠性2.2.2 易用性2.2.3 维护性 三、数据库设计3.1 用户表3.2 科室档案表3.3 医生档案表3.4 医生放号…

Autosar CAN开发05(从实际应用认识CAN波特率)

建议同时阅读本专栏的&#xff1a; Autosar CAN开发03&#xff08;从实际应用认识CAN总线的物理层&#xff09; Autosar CAN开发04&#xff08;从实际应用认识CAN报文&#xff09; Autosar CAN开发05&#xff08;从实际应用认识CAN波特率&#xff09; 前言 当知道了CAN的物…

STM32MP157D-DK1开发板Qt镜像构建

上篇介绍了STM32MP57-DK1开发板官方系统的烧录。那个系统包含Linux系统的基础功能&#xff0c;如果要进行Qt开发&#xff0c;还需要重新构建带有Qt功能的镜像 本篇就来介绍如何构建带有Qt功能的系统镜像&#xff0c;并在开发板中烧录构建的镜像。 1 Distribution包的构建 ST…

优化模型:MATLAB整数规划

一、整数规划介绍 1.1 整数规划的定义 若规划模型的所有决策变量只能取整数时&#xff0c;称为整数规划。若在线性规划模型中&#xff0c;变量限制为整数&#xff0c;则称为整数线性规划。 1.2 整数规划的分类 整数规划模型大致可分为两类&#xff1a; &#xff08;1&…

HAL库的常用库函数(根据学习而更新)

目录 一、常用的GPIO相关HAL库函数 1、GPIO的初始化 2、配置GPIO引脚输出电平 3、切换指定引脚的电平&#xff0c;电平的翻转 4、读取指定GPIO引脚的电平 5、结构体 GPIO_InitTypeDef &#xff08;引脚&#xff09;定义&#xff1a; 6、高低电平的表示 7、延时函数&…

Java架构师系统架构需求分析实战

目录 1 导语2 需求分析实战3 核心方法论-架构立方体4 功能性模型-模块定义5 功能性模型-模块关系图6 功能性模型-模块细化 想学习架构师构建流程请跳转&#xff1a;Java架构师系统架构设计 1 导语 架构设计的实战和思维方法的讨论&#xff0c;主要聚焦于需求分析的重要性和方…

buuctf-Misc 题目解答分解97-99

97.[BSidesSF2019]zippy 下载完就是一个流量包 追踪tcp nc -l -p 4445 > flag.zip unzip -P supercomplexpassword flag.zip Archive: flag.zip 压缩包密码 supercomplexpassword 保存为 flag.zip 解压得到flag 98.[GUET-CTF2019]虚假的压缩包 先从虚假的压缩包入手 &am…

逆向P1P2总结

字节八位 word 16位 deword 32 位 00 00 00 e8 是存储32位信息的起点 不是程序运行的起点 为什么电脑有32位与64位之分 寻址宽度 以字节为单位 0xfffffff 1 就是最大容量 转为十进制为 4294967296 / 1024 &#xff08;k&#xff09;/1024 &#xff08;kb&#xff09;/ 1…

软件测试面试八股文——基础篇

5&#xff09;错误推测法&#xff1a;是基于经验和直觉推测程序中所有可能存在的各种错误&#xff0c;从而有针对性的设计测试用例的方法 6&#xff09;正交实验法 7&#xff09;判定表法 8&#xff09;测试大纲法 3、提交缺陷的八大要素 1&#xff09;缺陷编号&#xff1a…

数据通信网络基础华为ICT网络赛道

目录 前言&#xff1a; 1.网络与通信 2.网络类型与网络拓扑 3.网络工程与网络工程师 前言&#xff1a; 数据通信网络基础是通信领域的基本概念&#xff0c;涉及数据传输、路由交换、网络安全等方面的知识。华为ICT网络赛道则是华为公司提出的一种技术路径&#xff0c;旨在通…