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

慕尚花坊项目笔记

慕尚花坊项目笔记

一、项目初始化

重置app.js中的代码
删除app.jsonpages下的"pages/logs/logs"路径,同时删除pages.logs文件夹
删除app.jsonpages下的"renderOptions"以及"componentFrameword"字段
重置app.wxss中的代码
删除components中的自定义组件
重置pages/index文件夹下的index.jsindex.wxssindex.html以及index.json文件
更新utilsutils.js的文件名为formatTime.js

二、自定义构建npm、集成sass

1、自定义构建npm

  1. 首先在project.config.json中配置miniprogramRoot,指定小程序源码的目录

  2. 然后配置 project.config.jsonsetting.packNpmManuallytrue开启自定义 node_modulesminiprogram_npm 位置的构建 npm 方式

  3. 最后配置 project.config.json 的 setting.packNpmRelationList 项,指定 packageJsonPathminiprogramNpmDistDir 的位置

    • packageJsonPath 表示 node_modules 源对应的 package.json相对地址
    • miniprogramNpmDistDir 表示 node_modules 的构建结果目标地址
  4. 创建package.json并安装 vant ,然后进行 npm 构建,测试是否能够正常 vant 构建成功

npm init -y
npm i @vant/weapp

注意:因为npm init -y命令会将当前目录名作为package.json中name字段的默认值,而name字段不允许使用中文。 如果目录名包含中文,就会导致初始化失败,提示类似 “Invalid name” 的错误信息。

2、集成sass

project.config.json 文件中,修改 setting 下的 useCompilerPlugins 字段为 ["sass"],即可开启工具内置的 sass 编译插件。
注意:sass需要用双引号包裹,JSON 规定字符串必须用 双引号 " 包裹,单引号 ’ 或未闭合的引号会导致错误

三、使用vscode开发

  1. 项目的根目录下创建 .vscode 文件夹,注意:文件夹名字前面带 .

  2. .vscode 文件夹下,创建 settings.json,用来对安装的插件属性进行设置,具体属性设置从下面复制即可

    注意:.vscode 文件夹下的 settings.json 文件只对当前一个项目生效

  3. 在【项目的根目录】下创建 .prettierrc 文件,进行 Prettier 代码规则的配置,规则从下面复制

  4. 为了让 Prettier 配置项在微信开发者工具生效,需要在微信开发者工具中也安装 Prettier 扩展插件。

.vscode/settings.json

{// 保存文件时是否自动格式化"editor.formatOnSave": true,// ---------------- 以下是 [ prettier ] 插件配置 ----------------// 指定 javascript、wxss、scss、less、json、jsonc 等类型文件使用 prettier 进行格式化"[javascript]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[wxss]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[scss]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[less]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[json]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},"[jsonc]": {"editor.defaultFormatter": "esbenp.prettier-vscode"},// Prettier 的一个配置项,用于指定哪些文件类型需要使用 Prettier 进行格式化"prettier.documentSelectors": ["**/*.wxml", "**/*.wxss", "**/*.wxs"],// ---------------- 以下是 [ WXML - Language Service ] 插件配置 ----------------// wxml 文件使用 prettier 进行格式化"[wxml]": {// "qiu8310.minapp-vscode" 是 WXML - Language Service 插件提供的配置项// 此插件主要是针对小程序的 wxml 模板语言,可以自动补全所有的组件、组件属性、组件属性值等等// 如果是 VsCode 需要开启这个配置"editor.defaultFormatter": "qiu8310.minapp-vscode"// 如果是微信小程序,需要开启这个配置,通过 esbenp.prettier-vscode 对代码进行格式化// "editor.defaultFormatter": "esbenp.prettier-vscode"},// 创建组件时使用的 css 后缀"minapp-vscode.cssExtname": "scss", // 默认 wxss,支持 styl sass scss less css// 指定 WXML 格式化工具"minapp-vscode.wxmlFormatter": "prettier",// 配置 prettier 代码规范"minapp-vscode.prettier": {"useTabs": false,"tabWidth": 2,"printWidth": 80},// ---------------- 以下是 [ 微信小程序助手-Y ] 插件配置 ----------------// 新增、删除小程序页面时,是否自动同步 app.json pages 路径配置,默认为 false"wechat-miniapp.sync.delete": true,// 设置小程序页面 wxss 样式文件的扩展名"wechat-miniapp.ext.style": "scss",// ---------------- 其他配置项 ----------------// 配置语言的文件关联,运行 .json 文件时写注释// 但在 app.json 和 page.json 中无法使用"files.associations": {"*.json": "jsonc"}
}

.prettierrc

{"semi": false,"singleQuote": true,"useTabs": false,"tabWidth": 2,"printWidth": 180,"trailingComma": "none","overrides": [{"files": "*.wxml","options": { "parser": "html" }},{"files": "*.wxss","options": { "parser": "css" }},{"files": "*.wxs","options": { "parser": "babel" }}]
}
配置项配置项含义
“semi”: false不要有分号
“singleQuote”: true使用单引号
“useTabs”: false缩进不使用 tab,使用空格
“tabWidth”: 2tab缩进为4个空格字符
“printWidth”: 80一行的字符数,如果超过会进行换行,默认为80
“trailingComma”: “none”尾随逗号问题,设置为none 不显示 逗号
“overrides”: []overrides 解析器:默认情况下,Prettier 会根据文件文件拓展名推断要使用的解析器

四、通用模块封装

封装后可以极大简化API的调用

1、消息提示模块封装

封装思路:

  1. 创建一个 toast 方法对 wx.showToast() 方法进行封装

  2. 调用该方法时,传递对象作为参数

    • 如果没有传递任何参数,设置一个空对象 {} 作为默认参数

    • 从对象中包含 titleicondurationmask 参数,并给参数设置默认值

  3. 在需要显示弹出框的时候调用 toast 方法,并传入相关的参数,有两种参数方式:

    • 不传递参数,使用默认参值
    • 传入部分参数,覆盖默认的参数

在这里插入图片描述

调用方式:

  1. 模块化的方式导入使用

    import { toast } from './extendApi'toast()
    toast({ title: '数据加载失败....', mask: true })
    
  2. 将封装的模块挂载到 wx 全局对象身上

    wx.toast()
    wx.toast({ title: '数据加载失败....', mask: true })
    

模块化方式导入使用
在这里插入图片描述
在这里插入图片描述

挂载到wx全局对象身上
在这里插入图片描述
在这里插入图片描述

2、模态对话框封装

封装思路:

  1. wx.showModal() 方法进行封装, 封装后的新方法叫 modal
  2. 调用该方法时,传递对象作为参数,对象的参数同 wx.showModal() 参数一致
  3. 封装的 modal 方法的内部通过 Promise 返回用户执行的操作(确定和取消,都通过 resolve 返回)
  4. 在需要显示模态对话框的时候调用 modal 方法,并传入相关的参数,有三种调用方式:
    • 不传递参数,使用默认参数
    • 传递参数,覆盖默认的参数

调用方式:

新封装的本地存储模块,我们依然希望有两种调用的方式:

  1. 模块化的方式导入使用
  2. 将封装的模块挂载到 wx 全局对象身上

挂载到wx上且传入新的参数案例:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3、封装本地存储API

export const setStorage = (key,value) => {try{wx.setStorageSync(key,value)}catch(error){console.error(`存储指定 ${key} 数据发生错误:`, err)}
}export const getStorage = (key) => {try{const value = wx.getStorageSync(key)if(value){return value}}catch(error){console.error(`读取指定 ${key} 数据发生错误:`, err)}
}export const removeStorage = (key) => {try {wx.removeStorageSync(key)} catch (error) {console.error(`移除指定 ${key} 数据发生错误:`, error)}
}export const clearStorage = () => {try {wx.clearStorageSync()} catch (error) {console.error("清空本地存储时发生错误:", error);}
}
import {setStorage,getStorage,removeStorage,clearStorage} from './utils/storage'
App({async onShow(){setStorage('name','tom')setStorage('age',10)const name = getStorage('name')console.log(name);removeStorage('name')clearStorage()}
})

4、封装异步存储API+优化代码

export const asyncSetStorage = (key,data) => {return new Promise((resolve) => {wx.setStorage({key,data,complete(res){resolve(res)}})})
}export const asyncGetStorage = (key) => {return new Promise((resolve) => {wx.getStorage({key,complete(res){resolve(res)}})})
}export const asyncRemoveStorage = (key) => {return new Promise((resolve) => {wx.removeStorage({key,complete(res){resolve(res)}})})
}export const asyncClearStorage = () => {return new Promise((resolve) => {wx.clearStorage({complete(res){resolve(res)}})})
}
import {asyncSetStorage,asyncGetStorage,asyncRemoveStorage,asyncClearStorage} from './utils/storage'
App({async onShow(){asyncSetStorage('name','Jerry').then((res)=>{console.log(res);})asyncGetStorage('name').then((res)=>{console.log(res);})asyncRemoveStorage('name').then((res)=>{console.log(res);})asyncClearStorage().then((res)=>{console.log(res);})}
})

五、网络请求封装

1、为什么要封装wx.request()

小程序大多数 API 都是异步 API,如 wx.request(),wx.login()等。这类 API 接口通常都接收一个 Object 对象类型的参数,参数中可以按需指定以下字段来接收接口调用结果:

参数名类型必填说明
successfunction调用成功的回调函数
failfunction调用失败的回调函数
completefunction调用结束的回调函数(调用成功、失败都会执行)

2、请求封装-request方法

在封装网络请求模块的时候,采用class类来进行封装,采用类的方式封装代码更具可复用性,也方便地添加新的方法和属性,提高代码的扩展性
在这里插入图片描述

WxRequest 类内部封装一个 request 实例方法

request 实例方法中需要使用 Promise 封装 wx.request,也就是使用 Promise 处理 wx.request 的返回结果

request 实例方法接收一个 options 对象作为形参,options 参数和调用 wx.request 时传递的请求配置项一致

  • 接口调用成功时,通过 resolve 返回响应数据
  • 接口调用失败时,通过 reject 返回错误原因
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

3、请求封装-设置请求参数

在发起网络请求时,需要配置一些请求参数,其中有一些参数我们可以设置为默认参数,例如:请求方法、超时时长等,因此在封装时我们要定义一些默认的参数。
我们还要允许在进行实例化的时候,传入参数,对默认的参数进行修改

// 对 WxRequest 进行实例化
const instance = new WxRequest({baseURL: 'https://gmall-prod.atguigu.cn/mall-api', // 请求基准地址timeout: 10000 // 微信小程序 timeout 默认值为 60000
})

在通过实例调用request实例方法时也会传入相关的请求参数

const res = await instance.request({url: '/index/findBanner',method: 'GET'
})

从而得出结论:请求参数的设置有三种方式:

  1. 默认参数:在 WxRequest 类中添加 defaults 实例属性来设置默认值
  2. 实例化时参数:在对 WxRequest 类进行实例化时传入相关的参数,需要在 constructor 构造函数形参进行接收
  3. 调用实例方法时传入请求参数

request.js
在这里插入图片描述
在这里插入图片描述
实例化时设置请求参数(被constructor形参接收)在这里插入图片描述调用实例方法时设置请求参数(被request实例方法的形参options接收)在这里插入图片描述

4、请求封装-封装请求快捷方法

在request()基础上封装一些快捷方法,简化request的调用
需要封装 4 个快捷方法,分别是 getdeletepostput,他们的调用方式如下:

instance.get('请求地址', '请求参数', '请求配置')
instance.delete('请求地址', '请求参数', '请求配置')
instance.post('请求地址', '请求参数', '请求配置')
instance.put('请求地址', '请求参数', '请求配置')

这 4 个请求方法,都是通过实例化的方式进行调用,所以需要 Request 类中暴露出来 getdeletepostput 方法。每个方法接收三个参数,分别是:接口地址、请求参数以及其他参数。

这 4 个快捷方法,本质上其实还是调用 request 方法,我们只要在方法内部组织好参数,调用 request 发送请求即可
与request实例方法同级
在这里插入图片描述
在这里插入图片描述

5、wx.request注意事项

在使用 wx.request 发送网络请求时,只要成功接收到服务器返回,无论statusCode是多少,都会进入 success 回调。开发者根据业务逻辑对返回值进行判断。
一般只有网络出现异常、请求超时等时候,才会走 fail 回调

6、请求封装-定义请求/响应拦截器

为了方便统一处理请求参数以及服务器响应结果,为WXRequest添加拦截器功能,拦截器包括请求拦截器响应拦截器

  • 请求拦截器本质上是在请求之前调用的函数,用来对请求参数进行新增和修改
  • 响应拦截器本质上是在响应之后调用的函数,用来对相响应数据做点什么

不管成功响应还是失败相应,都会执行响应拦截器
可以在WXRequest类内部定义interceptors实例属性,属性中需要包含request以及response方法
在这里插入图片描述
在这里插入图片描述
调用请求拦截器
在这里插入图片描述
调用响应拦截器
在这里插入图片描述

7、请求封装-完善请求/响应拦截器

目前不管请求成功 (success),还是请求失败(fail),都会执行响应拦截器
那么怎么判断是请求成功,还是请求失败呢 ?
封装需求:

  1. 如果请求成功,将响应成功的数据传递给响应拦截器,同时在传递的数据中新增 isSuccess: true 字段,表示请求成功
  2. 如果请求失败,将响应失败的数据传递给响应拦截器,同时在传递的数据中新增 isSuccess: false 字段,表示请求失败

在实例调用的响应拦截中,根据传递的数据进行以下的处理:

  • 如果isSuccess: true 表示服务器响应了结果,我们可以将服务器响应的数据简化以后进行返回
  • 如果isSuccess: false 表示是网络超时或其他网络问题,提示 网络异常,同时将返回即可

在这里插入图片描述
在这里插入图片描述

8、结合项目使用请求/响应拦截器

使用请求拦截器:
在发送请求时,购物车列表、收货地址、更新头像等接口,都需要进行权限验证,因此我们需要在请求拦截器中判断本地是否存在访问令牌 token ,如果存在就需要在请求头中添加 token 字段。

使用响应拦截器:
在使用 wx.request 发送网络请求时。只要成功接收到服务器返回,无论statusCode是多少,都会进入 success 回调。

因此开发者根据业务逻辑对返回值进行判断。

后端返回的业务状态码如下:

  1. 业务状态码 === 200, 说明接口请求成功,服务器成功返回了数据
  2. 业务状态码 === 208, 说明没有 token 或者 token 过期失效,需要登录或者重新登录
  3. 业务状态码 === 其他,说明请求或者响应出现了异常
// 配置请求拦截器(会覆盖默认的请求拦截器)
instance.interceptors.request=(config)=>{// 在请求发送之前做点什么// 在发送请求之前,需要先判断本地是否存在访问令牌token// 如果存在token,就需要在请求头中添加token字段const token = getStorage('token')if(token){config.header['token']=token}return config
}// 配置响应拦截器(会覆盖默认的响应拦截器)
instance.interceptors.response  = async (response)=>{// 对服务器响应的数据做点什么// 从response中解构isSuccess,如果isSuccess为false,说明执行了fail回调函数,需要给用户一个提示const {isSuccess,data} = responseif(!isSuccess){wx.showToast({title:'网络异常请重试',icon:'error'})return response}// 判断服务器响应的业务状态码switch(data.code){case 200:return datacase 208:// 说明没有token或者token失效,需要让用户登录或重新登录const res = await modal({content:'鉴权失败,请重新登录',showCancel:false // 不显示取消按钮})if(res){// 清除之前失效的token,同时清除本地存储的全部信息clearStorage()wx.navigateTo({url: '/pages/login/login',})}return Promise.reject(response)default:toast({title:'程序出现异常,请联系客服或稍后重试'})return Promise.reject(response)}
}

9、请求封装-添加并发请求

前端并发请求是指在前端页面同时向后端发起多个请求的情况。当一个页面需要请求多个接口获取数据时,为了提高页面的加载速度和用户体验,可以同时发起多个请求,这些请求之间就是并发的关系。

我们通过两种方式演示发起多个请求:

  1. 使用 asyncawait 方式
  2. 使用 Promise.all() 方式

async await方式
在这里插入图片描述
在这里插入图片描述
Promise.all()方法
在这里插入图片描述
在这里插入图片描述
在request.js中封装promise.all方法,在test.js中调用all()实例方法
在这里插入图片描述
在这里插入图片描述

10、添加loading

在封装时添加 loading 效果,从而提高用户使用体验

  1. 在请求发送之前,需要通过 wx.showLoading 展示 loading 效果

  2. 当服务器响应数据以后,需要调用 wx.hideLoading 隐藏 loading 效果

要不要把loading 添加到 WxRequest 内部 ?

  1. 在类内部进行添加,方便多个项目直接使用类提供的 loading 效果,也方便统一优化 wx.showLoading 使用体验。
    但是不方便自己来进行 loading 个性化定制。
  2. 如果想自己来控制 loading 效果,带来更丰富的交互体验,就不需要将 loading 封装到类内部,但是需要开发者自己来优化 wx.showLoading 使用体验,每个项目都要写一份。

添加loading效果在这里插入图片描述
在这里插入图片描述

11、完善loading效果

目前在发送请求时,请求发送之前会展示 loading,响应以后会隐藏 loading

但是 loading 的展示和隐藏会存在以下问题:

  1. 每次请求都会执行 wx.showLoading(),但是页面中只会显示一个,后面的 loading会将前面的覆盖
  2. 同时发起多次请求,只要有一个请求成功响应就会调用 wx.hideLoading,导致其他请求还没完成, loading也会隐藏
  3. 请求过快 或 一个请求在另一个请求后立即触发,这时候会出现 loading 闪烁问题

我们通过 队列 的方式解决这三个问题:首先在类中新增一个实例属性 queue,初始值是一个空数组

  1. 发起请求之前,判断 queue 如果是空数组则显示 loading ,然后立即向queue新增请求标识
  2. complete 中每次请求成功结束,从 queue 中移除一个请求标识,queue 为空时隐藏 loading
  3. 为了解决网络请求过快产生loading 闪烁问题,可以使用定时器来做判断即可

解决第一个问题:判断queue是否为空,如果是空,就显示loading,如果不是空就不调用wx.showLoading()
在这里插入图片描述
解决第二个问题:在每个请求结束以后都会执行complete,每次从queue队列中删除一个标识,并判断queue数组是否为空,如果为空说明并发请求完成了,需要隐藏loading如果不为空则说明还有请求未完成不需要隐藏loading
在这里插入图片描述

解决第三个问题:当一个②请求是依靠①请求后才能触发时

  • ①请求完毕pop删除queue中最后一个request标识后,再向queue中添加一个request标识
  • ②请求进来如果有定时器会首先清除定时器并添加一个request
  • ②请求完毕,删除一个request,此时queue中还剩余一个request
  • 接下来如果没有新的request了,就会执行定时器内操作,删除最后一个request,隐藏loading,最后关闭定时器
    在这里插入图片描述
    在这里插入图片描述

12、控制loading显示

在实际开发中,有的接口可能不需要显示loading效果,或者开发者希望自己来控制loading的样式与交互,那么就需要关闭默认的loading效果,这时候我们就需要一个开关来控制 loading 显示。

  1. 类内部设置默认请求参数 isLoading 属性,默认值是 true,在类内部根据 isLoading 属性做判断即可

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2. 某个接口不需要显示 loading 效果,可以在发送请求的时候,可以新增请求配置 isLoading 设置为 false
在这里插入图片描述
3. 整个项目都不需要显示loading 效果,可以在实例化的时候(http.js中),传入 isLoading 配置为 false

在这里插入图片描述
若设置整个项目都不需要loading效果,但是希望某一个接口用到默认loading效果,只要为那个接口新增请求配置isLoading:true

13、封装uploadFile

wx.uploadFile也是我们在开发中常用的一个api,用来将本地资源上传到服务器

wx.uploadFile({url: '', // 必填项,开发者服务器地址filePath: '', // 必填项,要上传文件资源的路径 (本地路径)name: '' // 必填项,文件对应的 key,开发者在服务端可以通过这个 key 获取文件的二进制内容
})

首先在 WxRequest 类内部创建 upload 实例方法,实例方法接收四个属性:


/**
* @description 文件上传接口封装
* @param { string } url 文件上传地址
* @param { string } filePath 要上传文件资源的路径
* @param { string } name 文件对应的 key
* @param { string } config 其他配置项
* @returns 
*/
upload(url, filePath, name, config = {}) {return this.request(Object.assign({ url, filePath, name, method: 'UPLOAD' }, config))
}

这时候我们需要在 request 实例方法中,对 method 进行判断,如果是 UPLOAD,则调用 wx.uploadFile 上传API

// request 实例方法接收一个对象类型的参数
// 属性值和 wx.request 方法调用时传递的参数保持一致
request(options) {// coding...// 需要使用 Promise 封装 wx.request,处理异步请求return new Promise((resolve, reject) => {if (options.method === 'UPLOAD') {wx.uploadFile({...options,success: (res) => {// 将服务器响应的数据通过 JSON.parse 转换为 JS 对象res.data = JSON.parse(res.data)const mergeRes = Object.assign({}, res, {config: options,isSuccess: true})resolve(this.interceptors.response(mergeRes))},fail: (err) => {const mergeErr = Object.assign({}, err, {config: options,isSuccess: true})reject(this.interceptors.response(mergeErr))},complete: () => {this.queue.pop()this.queue.length === 0 && wx.hideLoading()}})} else {wx.request({// coding...})}})
}

创建upload实例方法
在这里插入图片描述

  • url:文件的上传地址、接口地址
  • filePath:要上传的文件资源路径
  • name:文件对应的key
  • config:其他配置项

在这里插入图片描述

14、npm包发送请求

npm install mina-request

在这里插入图片描述

15、小程序设置环境变量

在实际开发中,不同的开发环境,调用的接口地址是不一样的。
例如:开发环境需要调用开发版的接口地址,生产环境需要调用正式版的接口地址
这时候,我们就可以使用小程序提供的 wx.getAccountInfoSync() 接口,用来获取当前账号信息,在账号信息中包含着 小程序 当前环境版本。

环境版本合法值
开发版develop
体验版trial
正式版release
// 配置当前小程序项目的环境变量 utils/env.js
const {miniProgram} = wx.getAccountInfoSync()
const {envVersion} = miniProgram
let env = {baseURL:'https://gmall-prod.atguigu.cn/mall-api'
}
switch(envVersion){// 开发版case 'develop':env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'break;// 体验版case 'trial':env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'break;// 正式版case 'release':env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'break;default:env.baseURL = 'https://gmall-prod.atguigu.cn/mall-api'break;
}
export {env}

16、接口调用方式说明

在开发中,我们会将所有的网络请求方法放置在 api 目录下统一管理,然后按照模块功能来划分成对应的文件,在文件中将接口封装成一个个方法单独导出,例如:
在这里插入图片描述
在这里插入图片描述

这样做的有以下几点好处:

  1. 易于维护:一个文件就是一个模块,一个方法就是一个功能,清晰明了,查找方便
  2. 便于复用:哪里使用,哪里导入,可以在任何一个业务组件中导入需要的方法
  3. 团队合作:分工合作
http://www.xdnf.cn/news/1105237.html

相关文章:

  • MoE混合专家模型:千亿参数的高效推理引擎与架构革命
  • 论容器化 | 分析Go和Rust做医疗的后端服务
  • Linux711 Mysql
  • 2025十大免费销售管理软件推荐
  • 《每日AI-人工智能-编程日报》--7月11日
  • Kafka-日常运维命令
  • 一个中层管理者应该看什么书籍?
  • TCP协议详解——初识
  • SQL开窗函数
  • Guava LoadingCache
  • LLM场景下的强化学习【GRPO】
  • 在线重装 Proxmox VE
  • DeepSeek模型分析及其在AI辅助蛋白质工程中的应用-文献精读148
  • C语言文件读写操作详解:fgetc与feof函数的应用
  • RestTemplate动态修改请求的url
  • C++前缀和与差分的深度探索
  • 信号量机制
  • Python-正则表达式-信息提取-滑动窗口-数据分发-文件加载及分析器-浏览器分析-学习笔记
  • Windows GNU Radio避坑
  • 【牛客刷题】dd爱科学1.0
  • 计算机网络第三章(6)——数据链路层《网桥交换机》
  • PHT-CAD 笔记
  • 深入MyBatis:CRUD操作与高级查询实战
  • Visual Studio Code 的 settings.json 配置指南
  • K8s Service 终极解析:源码、性能、故障排查全攻略
  • 深入解析 TCP 连接状态与进程挂起、恢复与关闭
  • ROS1学习第三弹
  • Web安全 - 基于 SM2/SM4 的前后端国产加解密方案详解
  • Web安全-Linux基础-01-初识Linux
  • 牛客周赛 Round 99