【项目总结】工程构建思路分析与分享

news2024/11/21 1:35:35

在这里插入图片描述

背景

写这篇文章的初衷是因为国产化项目临近结束,在做项目总结时,回想起了代码管理的相关问题。虽然工程搭建及管理是一个高级工程师必备的能力,但似乎每次一个新项目立项,都是从老的项目中拷贝工程框架,再进行定制修改。并没有认真了解其原理及整体工程管理和维护思想。

虽然大部分的软件工程,都具备一定的通用性,修修改改也能用起来,但是我觉得工程架构,应该要和软件架构相匹配,将软件架构思想贯彻到底。本文介绍我个人如何通过软件架构来构建工程架构的以及cmake常用的接口。

如何构思工程框架

以T-BOX项目为例,其软件架构如下:

分析:

该软件架构采用分层结构,从上到下分为表现层、通讯层、业务层、抽象层、基础层。其中我们主要关心业务层、抽象层、基础层;

  • 业务层。用于实现t-box自身业务,比如gnss定位、数据拨号、TSP平台、自升级、片间通信等。它内部又分为基础能力和扩展能力两类业务。
  • 抽象层。该层的目的是进程底层硬件的依赖剥离,方便进行平台移植。
  • 基础层。主要是一些基础的功能库。它内部又分为三类:
    • 模组商SDK。比如数据拨号、gnss、低功耗等接口
    • 开源库。数据库、日志库、MQTT等
    • 系统库等

理论上业务层只能调用抽象层接口,而抽象层只能调用基础层接口

问题一:开源库如何管理?集成库还是集成生成物?

这是大家可能常会考虑的问题,我一般倾向于集成开源库的生成物,原因有以下几点:

  1. 空间浪费。开源库应该是具有通用性的,也就是说不同的项目都可以使用,若集成源代码,则多个不同项目,则会在git仓库中有多份。
  2. 编译时间增长。比如openssl开源库的编译需要十几分钟。这就导致第一次编译时,时间非常长。
  3. 便于维护。有时候,开源库也存在一些bug,需要我们去修复,若是单独用仓库保存仅需要维护一处即可。再通知各个项目组,让他们主动更新。

当然,不同的角色,其考虑角度不一样,比如模组商应该更偏向于将开源库放在项目中。因为他们一般是需要将源码交付给客户的。

问题二: 如何剥离对外部或硬件依赖?

在之前的工作中,我们经常会面临的一个问题:软件已经具备联调条件,但是外部却迟迟不能配合,无法验证内部逻辑。比如模组的sdk或硬件未提供。于是我们的思路是:在x86环境下,可以进行部分业务自测。对于外部依赖的sdk,可以自己模拟接口,配置相关应答。

问题三:如何将我们的程序集成到模组中?

我们工程编译的最终产物一般是可执行程序、动态库、配置文件。如何让其在模组中运行就需要解决两点:

  1. 如何让我们的程序开机启动。解决方式:需要修改启动脚本,再生成文件系统。
  2. 如何将我们的程序集成到模组中。解决方式:模组商一般会提供一个单独的分区,需要将我们的程序生成单独的ubi文件,进行烧录。

大致流程:

  1. 将我们的生成物,按照模组商的提供的指导文档,生成对应的oem_data.ubi
  2. 将修改的文件,替换到模组上的文件系统中,再生成文件系统root.squashfs
  3. oem_data.ubiroot.squashfs 替换到整包中,进行烧录。

问题四: 模组商的SDK如何管理?

正如上面讨论的:foundation->oem->arm目录、模组商的rootfs、升级整包、交叉工具链等,都应该是模组上提供的SDK的部分生成物。如何管理模组商的SDK呢?我的建议是单独上库处理。原因有以下几点:

  1. 模组商的SDK太大,一般有1G多。且编译时间很长,首次编译一般需要几个小时。
  2. 后续迭代,方便跟踪,溯源。

现在,我们的项目的工程目录大致如下:

且有三个git仓库:

如何使用cmake实现各个仓库框架

因为不同仓库源码来源不同,管理方式不同,因此cmake 也存在一些差异。

模组商SDK仓库

如上所述,该仓库的主要作用是记录后续模组商SDK变更记录,方便后续追溯。一般该SDK有自己的一套管理方式,不建议修改。我一般的做法是完整上库,不做修改它工程结构。

开源库仓库

该仓库的作用是将一些依赖的开源库进行编译,供上层使用。因此就可能存在多个开源依赖库。为了方便管理,我将所有的开源库,放到同一目录下。目录结构大致如下:

#build.sh
PROJECT_NAME=foundation
TOPDIR=$(pwd)
SRC_CODE_DIR=$TOPDIR

#表示编译的平台,因为项目仓库需要支持arm环境和x86环境,因此底层依赖库需要提供两个版本。
export TYPES_ARM="arm"  
export TYPES_X86="x86"
TYPES=($TYPES_ARM $TYPES_X86)

# 默认编译arm平台
if [ -z "$1" ]
then
	export PROJECT_BUILD_TYPE=${TYPES_ARM}	
else
	export PROJECT_BUILD_TYPE=$1
fi
echo "PROJECT_BUILD_TYPE:${PROJECT_BUILD_TYPE}"

# 判断交叉编译链是否存在
if [[ "$1" == $TYPES_ARM ]]; then
    if [ ! -d $SRC_CODE_DIR/toolchain ]; then
        echo "目录 $SRC_CODE_DIR/toolchain 不存在"
        exit
    fi
    # source 环境变量
    source t108_compile_tool_env_init
fi
# 注:交叉编译链及环境变量,一般都是模组上提供的。编译时,我们需要放到指定目录下


# 创建编译目录
rm -rf $SRC_CODE_DIR/build 
mkdir $SRC_CODE_DIR/build
cd $SRC_CODE_DIR/build

# 构建项目编译环境,并将编译平台传入
cmake -DENV_PLATFORM=${PROJECT_BUILD_TYPE} ..

# 执行多线程编译
make -j8

#若编译失败,则退出。若成功,则执行后续操作
if [ "$?" == "0" ]; then
    echo $?
    echo "build foundation successful"    
else
    echo $?
    echo "build foundation error"    
    exit 1
fi

# 执行安装指令
make install

#打包
cd ${TOPDIR}
rm -rf out
mkdir -p out
mkdir -p out/include
mkdir -p out/bin
mkdir -p out/etc
mkdir -p out/lib
cp -rf $SRC_CODE_DIR/build/deploy/include/* out/include/
cp -rf $SRC_CODE_DIR/build/deploy/sbin/mosquitto out/bin/
cp -rf $SRC_CODE_DIR/build/deploy/etc/* out/etc/
cp -rf $SRC_CODE_DIR/build/deploy/lib/*.a out/lib/

顶层CMakeList.txt分析:

cmake_minimum_required(VERSION 3.10.0)
project(foundation C CXX)

# 暂时屏蔽编译警告
# 对于开源库的编译警告,我的原则是不处理。而我们自己编写的代码是不可以有编译警告的。为了编译美观,因此忽略。
add_compile_options( -Wno-conversion -Wno-sign-conversion -Wno-parentheses -Wno-unused-variable -Wno-format -Wno-implicit-function-declaration -Wno-incompatible-pointer-types -Wno-int-conversion)

# 设置安装指令的相关变量,便于统一管理
# CMAKE_INSTALL_PREFIX。指定了安装的目标路径前缀,即所有安装的目标文件都会被放置在这个路径下
# CMAKE_INSTALL_BINDIR。指定了可执行文件(二进制文件)的安装路径。若没有设置,默认的安装路径将是 ${CMAKE_INSTALL_PREFIX}/bin。
# CMAKE_INSTALL_LIBDIR。指定了库文件的安装路径,若没有设置,默认的安装路径通常是 ${CMAKE_INSTALL_PREFIX}/lib,但在64位系统上,可能会是 ${CMAKE_INSTALL_PREFIX}/lib64
#CMAKE_INSTALL_INCLUDEDIR。CMAKE_INSTALL_INCLUDEDIR,若没有设置,默认的安装路径是 ${CMAKE_INSTALL_PREFIX}/include。
set(CMAKE_INSTALL_PREFIX ${CMAKE_SOURCE_DIR}/build/deploy)
set(CMAKE_INSTALL_BINDIR ${CMAKE_INSTALL_PREFIX}/bin)
set(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX}/lib)
set(CMAKE_INSTALL_INCLUDEDIR ${CMAKE_INSTALL_PREFIX}/include)


# 设置foundation 版本信息
# 获取编译日志
EXECUTE_PROCESS(COMMAND date +%Y%m%d OUTPUT_VARIABLE COMPILE_DATE OUTPUT_STRIP_TRAILING_WHITESPACE)

# 获取当前最新的commitID
EXECUTE_PROCESS(COMMAND git rev-parse --short HEAD OUTPUT_VARIABLE GIT_COMMITID OUTPUT_STRIP_TRAILING_WHITESPACE)

# 定义版本名程: foundation_platform_data_commitID,并将该信息以ADM_FOUNDATION_SDK_VERSION宏传递给编译选项,这样源文件中就可以进行访问,并进行打印记录。
SET(PROJECT_VERSION ${PROJECT_NAME}_${ENV_PLATFORM}_${ADM_SDK_VERSION}_${COMPILE_DATE}_${GIT_COMMITID})
ADD_DEFINITIONS(-DADM_FOUNDATION_SDK_VERSION="${PROJECT_VERSION}" -D${PROJECT_VERSION})

# 因为开源库mosquitto依赖openssl库,因此首先需要确保我们的编译环境中能够找到对应依赖。相当于环境编译检查。
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
    include_directories(${OPENSSL_INCLUDE_DIR})
endif()

# 将需要编译的开源库添加到构建过程中来
add_subdirectory(package/iniparser)
add_subdirectory(package/mosquitto)
add_subdirectory(package/log)
add_subdirectory(package/sqlite3)
add_subdirectory(package/cJSON)

在编译过程中,遇到了一个现象:编译x86环境时,可以正常编译。但是编译arm平台会报如下错误:

CMake Error at /home/xieyihua/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.29/Modules/FindPackageHandleStandardArgs.cmake:230 (message):
  Could NOT find OpenSSL, try to set the path to OpenSSL root folder in the
  system variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY) (found
  version "1.1.1f")
Call Stack (most recent call first):
  /home/xieyihua/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.29/Modules/FindPackageHandleStandardArgs.cmake:600 (_FPHSA_FAILURE_MESSAGE)
  /home/xieyihua/.local/lib/python3.8/site-packages/cmake/data/share/cmake-3.29/Modules/FindOpenSSL.cmake:686 (find_package_handle_standard_args)
  CMakeLists.txt:43 (find_package)

分析:其原因就是在做环境检查时,发现工程依赖openSSL,但是却没有找到相关依赖。因为我的x86环境默认安装了openSSL,且系统$PATH环境变量中添加了该路径,所以能找到相关依赖。但是可以提供的环境变量并没有相关设置,所以找不到。

# t108_compile_tool_env_init
export SDKPATH=$(cd $(dirname ${BASH_SOURCE[0]}) && pwd)
BUILD_TOOLCHAIN_DIR=$SDKPATH/toolchain
export CC=$BUILD_TOOLCHAIN_DIR/linux64/bin/arm-openwrt-linux-gcc
export CXX=$BUILD_TOOLCHAIN_DIR/linux64/bin/arm-openwrt-linux-g++

因此,需要在环境变量中,手动添加工具链中openSSL的相关路径。export PATH=$BUILD_TOOLCHAIN_DIR/linux64/bin:$BUILD_TOOLCHAIN_DIR/linux64/usr/lib:$PATH

项目仓库

由上图可知,项目本身CMakeLists.txt布局如下,并逐一分析:

build.sh 内容如下:

PROJECT_NAME=itu2.0
TOPDIR=$(pwd)

# 若没有设置平台,则默认编译arm
#!/bin/bash
if [ -z "$1" ]; then
    platform=arm
else
    platform=$1
fi

if [[ "$platform" == "arm" ]]; then
    if [ ! -d $TOPDIR/toolchain ]; then
        echo "目录 $TOPDIR/toolchain 不存在"
        exit
    fi

    source t108_compile_tool_env_init
fi


# 创建目录和编译
rm -rf $TOPDIR/build
mkdir $TOPDIR/build

# 编译,并传入平台
cd $TOPDIR/build
cmake -DENV_PLATFORM=$platform ..
make -j8

if [ "$?" == "0" ]; then
    echo $?
    echo "build $PROJECT_NAME successful"
else
    echo $?
    echo "build $PROJECT_NAME error"
    exit 1
fi

#回到顶层目录
cd ${TOPDIR}

#将生成物拷贝到输出目录
OUT_DIR=$TOPDIR/out
EXTERNAL_OUTPUT_EXE=$OUT_DIR
EXTERNAL_OUTPUT_EXE_BIN=$OUT_DIR/bin
EXTERNAL_OUTPUT_EXE_ETC=$OUT_DIR/etc
EXTERNAL_OUTPUT_EXE_LIB=$OUT_DIR/lib

rm -rf $EXTERNAL_OUTPUT_EXE

mkdir -p $EXTERNAL_OUTPUT_EXE
mkdir -p $EXTERNAL_OUTPUT_EXE_BIN
mkdir -p $EXTERNAL_OUTPUT_EXE_ETC
mkdir -p $EXTERNAL_OUTPUT_EXE_LIB

cp -rf $TOPDIR/build/common/libsaFoundation.so $EXTERNAL_OUTPUT_EXE_LIB
cp -rf $TOPDIR/build/abstract/libsaEngine.so $EXTERNAL_OUTPUT_EXE_LIB

cp -rf $TOPDIR/build/application/appcore/basicApp/basicApp $EXTERNAL_OUTPUT_EXE_BIN
cp -rf $TOPDIR/build/application/appcore/extendApp/extendApp $EXTERNAL_OUTPUT_EXE_BIN
cp -rf $TOPDIR/application/appcore/daemon/shineAutoDaemon $EXTERNAL_OUTPUT_EXE_BIN
cp -rf $TOPDIR/common/3rd_libs/$platform/bin/mosquitto $EXTERNAL_OUTPUT_EXE_BIN

cp -rf $TOPDIR/rawdata/saTboxConfig.ini $EXTERNAL_OUTPUT_EXE_ETC
cp -rf $TOPDIR/common/3rd_libs/$platform/etc/mosquitto/mosquitto.conf.example $EXTERNAL_OUTPUT_EXE_ETC

CMakeLists.txt内容如下:


cmake_minimum_required(VERSION 3.10.0)
project(ITU2.0 C CXX)

# 设置编译参数
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -rdynamic -funwind-tables -ffunction-sections -std=gnu11")
#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -rdynamic -funwind-tables -ffunction-sections -std=c++14 ")
# 打开所有警告。组内要求,编译过程不可以有任何警告
add_compile_options(-Wall -Wextra)

# 判断平台合法性
if((${ENV_PLATFORM} MATCHES "x86"))
    message(STATUS "ENV_PLATFORM = x86")
elseif(${ENV_PLATFORM} MATCHES "arm")
    message(STATUS "ENV_PLATFORM = arm")
else()
	message(FATAL_ERROR "ENV_PLATFORM ERROR x86 arm")
endif()

find_package(OpenSSL REQUIRED)

# 根据不同平台,设置库查找路径及依赖库
if(${ENV_PLATFORM} MATCHES "x86")

    # 设置x86平台 库查找路径
    set(FOUNDATION_LIBDIR ${CMAKE_SOURCE_DIR}/common/3rd_libs/${ENV_PLATFORM}/lib)

    # 添加依赖库
    set(FOUNDATION_LIBRARIES
        admdebug
        cjson
        easylogger
        iniparser
        mosquitto_static
        sqlite3
        ${FOUNDATION_LIBRARIES}
    )
elseif(${ENV_PLATFORM} MATCHES "arm")
    # 设置arm平台 库查找路径
    set(FOUNDATION_LIBDIR ${CMAKE_SOURCE_DIR}/common/3rd_libs/${ENV_PLATFORM}/lib)
    
    # arm 平台需要添加模组商自身的库,以及跨平台的其它库
    list(APPEND FOUNDATION_LIBDIR ${CMAKE_SOURCE_DIR}/common/oem/libs-lyqr)
    list(APPEND FOUNDATION_LIBDIR ${CMAKE_SOURCE_DIR}/toolchain/linux64/usr/lib)


    # 配置编译链接路径
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wl,-rpath-link,${CMAKE_SOURCE_DIR}/toolchain/linux64/usr/lib")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wl,-rpath-link,${CMAKE_SOURCE_DIR}/toolchain/linux64/lib")
    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}  -Wl,-rpath-link,${CMAKE_SOURCE_DIR}/common/oem/libs-lyqr")

    # 添加arm 平台依赖库
    set(FOUNDATION_LIBRARIES
        admdebug
        cjson
        easylogger
        iniparser
        mosquitto_static
        sqlite3
        #lyqr
        lynq_lib

        ${FOUNDATION_LIBRARIES}
    )
else()
    message(FATAL_ERROR "ENV_PLATFORM ERROR x86 arm")
endif()

# 逐层编译
## 1. 编译基础库
add_subdirectory(foundation)

## 2. 编译抽象层
add_subdirectory(abstract)

## 3. 编译业务层代码
add_subdirectory(application)

foundation/CMakeLists.txt内容如下。理论上foundation目录下都是已经配置文件、动态库、头文件。是不需要编译的。但是由于我们需要兼容x86环境,需要自定义模组商接口,用于调试。

cmake_minimum_required(VERSION 3.10.0)
project(lynq_lib C CXX)

set(project_name lynq_lib)



# x86环境下,依赖接口编译
if("${ENV_PLATFORM}" STREQUAL "x86" )
    file(GLOB engine_src
        "oem/x86/src/*"
    )

    file(GLOB engine_inc
    "oem/x86/inc"
    "oem/x86/src"
    )
endif()

add_library(${project_name} SHARED
    ${engine_src}
)

# 设置链接库路径,因为是底层foundation库,因此主要依赖交叉工具链
target_link_directories(${project_name} PRIVATE ${FOUNDATION_LIBDIR})

abstruct/CMakeLists.txt内容如下:该层是对foundation的抽象,因此肯定会对foundation相关进行依赖。

cmake_minimum_required(VERSION 3.10.0)
project(engine C CXX)

set(project_name saEngine)

file(GLOB engine_src
    #rpc
    "rpc/src/*"

    #network
    "network/src/*"

    #gnss
    "gnss/src/*"

    #se
    "se/src/fdw/*"

    #lpm
    "lpm/src/*"

    #sms
    "sms/src/*"

    #voice_call
    "voice_call/src/*"

    #添加其它抽象层代码
)

file(GLOB engine_inc
    "*/inc"
    "*/src"
)

message(STATUS "engine_inc = ${engine_inc}")
message(STATUS "engine_src = ${engine_src}")

# 生成目标动态库 libsaEngine.so
add_library(${project_name} SHARED
    ${engine_src}
   
)

# 设置头文件查找路径。因为抽象层是对否foundation的抽象,理论上它应该只依赖foundation才对。
target_include_directories(${project_name}
    PRIVATE
    ${engine_inc}
    ${CMAKE_SOURCE_DIR}/foundation/3rd_libs/${ENV_PLATFORM}/include/
    ${CMAKE_SOURCE_DIR}/foundation/libs/include/
    ${CMAKE_SOURCE_DIR}/foundation/3rd_libs/${ENV_PLATFORM}/include/log/
    ${CMAKE_SOURCE_DIR}/foundation/oem/inc/lynq/
    ${CMAKE_SOURCE_DIR}/foundation/oem/inc/mbtk/
)

# 添加依赖库libFoundation.so
target_link_libraries(
    ${project_name}
    PRIVATE
    saFoundation
)

bussniss/basic/CMakeLists.txt内容如下:同理bussniss/extend/CMakeLists.txt也类似。

cmake_minimum_required(VERSION 3.10.0)
project(basic C CXX)

set(project_name basicApp)

# 设置目标文件名称
add_executable(${project_name}
    main.c
    saInitBasicModule.c
    #其它服务源码
)

#添加头文件查找路径。理论上若abstruct层做的足够优秀,业务层是不会直接调用foundation层接口的。
include_directories(
    ${CMAKE_SOURCE_DIR}/abstract/inc/
)

# 设置库依赖
target_link_libraries(
    ${project_name}
    PRIVATE
    saFoundation
	saEngine
  
)

#设置库查找路径
target_link_directories(${project_name} PRIVATE ${FOUNDATION_LIBDIR})

编译成功之后,就是需要构建整包了。package.sh 内容如下:

#!/bin/bash
export PROJECT_ROOT=$(cd "$(dirname "$0")";pwd)

# 创建输出目录
if [ ! -d $PROJECT_ROOT/package ];then
    mkdir $PROJECT_ROOT/package
fi

# 生成 oem_data.ubi。build.sh 脚本会将项目生成物保存到out目录下
mkdir $PROJECT_ROOT/tmp
$PROJECT_ROOT/tools/mkfs.ubifs  -m 2048 -e 126976 -c 507 -F  --space-fixup   --compr=zlib -o $PROJECT_ROOT/tmp/oem_data.ubifs -d $PROJECT_ROOT/out/
cd $PROJECT_ROOT/tmp/
$PROJECT_ROOT/tools/ubinize  -m 2048 -p 131072 -s 2048  -o $PROJECT_ROOT/package/openwrt-mmp-asr1806-oem_data.ubi $PROJECT_ROOT/tools/ubinize-oem.cfg 
cd $PROJECT_ROOT/
rm  -rf $PROJECT_ROOT/tmp


# 生成 root.squashfs
mkdir $PROJECT_ROOT/tmp
cp  -rf $PROJECT_ROOT/sdk-lynq/* $PROJECT_ROOT/tmp/
cp  -rf $PROJECT_ROOT/sdk-shineauto/root-mmp/etc/init.d/mbtk_boot_last  $PROJECT_ROOT/tmp/root-mmp/etc/init.d/mbtk_boot_last

$PROJECT_ROOT/tools/mksquashfs4 $PROJECT_ROOT/tmp/root-mmp $PROJECT_ROOT/package/openwrt-mmp-asr1806-root.squashfs -nopad -noappend -root-owned -no-exports -comp xz -Xpreset 8 -Xe -Xlc 0 -Xlp 2 -Xpb 2 -Xbcj arm -b 64k -p '/dev d 755 0 0' -p '/dev/console c 600 0 0 5 1' -no-xattrs
rm  -rf $PROJECT_ROOT/tmp

# 生成整包
TIMESTAMP=$(date +'%Y%m%d')
GIT_COMMITID=$(git rev-parse --short HEAD)
echo $TIMESTAMP
echo $GIT_COMMITID

mkdir $PROJECT_ROOT/tmp
cp  -rf $PROJECT_ROOT/wholePackage/* $PROJECT_ROOT/tmp
cp  -rf $PROJECT_ROOT/package/openwrt-mmp-asr1806-oem_data.ubi  $PROJECT_ROOT/tmp
cp  -rf $PROJECT_ROOT/package/openwrt-mmp-asr1806-root.squashfs  $PROJECT_ROOT/tmp

# 构建fota包
$PROJECT_ROOT/tools/mkotafbf -f  $PROJECT_ROOT/tmp/asr1806_p301_QSPINAND_Trusted_SDTIM_LPDDR2_OTA_BLF -o $PROJECT_ROOT/package/shineauto_$TIMESTAMP\_$GIT_COMMITID.bin -v "$TIMESTAMP\_$GIT_COMMITID"
$PROJECT_ROOT/tools/ota_update -f $PROJECT_ROOT/package/shineauto_$TIMESTAMP\_$GIT_COMMITID.bin -v $PROJECT_ROOT/tmp/
rm  -rf $PROJECT_ROOT/tmp

总结

本文介绍了如何通过软件架构构建工程架构,并使用CMake进行项目管理。以T-BOX项目为例,详细分析了其软件架构,包括分层结构、业务层、抽象层和基础层。还探讨了如何管理开源库、剥离外部依赖、集成程序到模组中以及如何管理模组商的SDK。希望思路能够帮助到需要的朋友
若我的内容对您有所帮助,还请关注我的公众号。不定期分享干活,剖析案例,也可以一起讨论分享。
我的宗旨:
踩完您工作中的所有坑并分享给您,让你的工作无bug,人生尽是坦途
在这里插入图片描述

在这里插入图片描述

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

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

相关文章

【C++】C++17中可以存储任意类型数据的对象——any类的使用与设计思想

目录 引言 any类的使用 构造 号运算符重载 std::any::swap std::any::has_value std::any::type std::any::reset std::any_cast any类的设计思想 个人主页:东洛的克莱斯韦克-CSDN博客 C专栏:C_东洛的克莱斯韦克的博客-CSDN博客 引言 一提到存…

按键 tab 则 切换输入框, 按键 ↑↓ 则 加减数值

1) 页面内输入框,按键 tab 则 切换输入框, 按键 ↑↓ 则 加减数值 2) 思路 1、按键 tab切换input框,默认tab切换会聚焦到浏览器地址栏(F12时会聚焦到开发者工具选项),我们需要阻止该默认事件。 2、进入页面就聚焦到第一个输入框…

【JAVA基础】lombok的@Data会生成什么?和无参构造器有关系吗

问题: 前提:实体中有三个属性;问题一、lombok在实体上如果只加Data会生成哪些构造器。问题二 编译器默认生成无参构造器,如果自己写了有一个参数的构造器,还会生成默认的无参构造器吗 ? 问题一解答 当在Jav…

免费送源码:Java+B/S+ssm+MySQL 公众养老服务网上预订系统 计算机毕业设计原创定制

摘 要 本论文主要论述了如何使用JAVA语言开发一个公众养老服务网上预订系统,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,SSM框架进行开发。在引言中,作者将论述公众养老服务网上预订系统的当前背景以及系统开发…

本地生活服务项目有哪些:如何利用本地生活市场,打开线下流量!

随着各大互联网公司在本地生活服务板块的布局力度持续加大,越来越多的人都开始意识到了它背后所蕴含着的发展前景和收益潜力,进而纷纷打听起了与之相关的消息。而就小编与多位创业者的交流情况而言,在众多问题中,属本地生活服务项…

巨控协议转换网关GRM321GRM322GRM323应用场景

巨控工业协议网关GRM321,GRM322,GRM323是网口型网关,支持各种PLC的TCP协议,具备多路RS485,RS232和三个TCP网口。能实现RS485,RS232和TCP的工业协议的零代码无缝转换,能极大节约工程师编程时间,保障数据采集的可靠性。 …

遥感图像分割

遥感图像分割是一种应用于遥感图像的计算机视觉技术,用于将图像划分为不同的区域,每个区域代表地表的不同特征,如水体、森林、城市区域等。这种分割帮助我们更好地理解和分析地球表面的变化,对于环境监测、城市规划、农业、灾害管…

阿里云ACP云计算高级工程师知识点,超详细,看完就够了!

本文以访问控制章节知识点为例,不说废话直接上干货! 考法1:VPC访问控制方式对比 1.VPC可以通过安全组、防火墙、RDS白名单、SLB白名单等方式进行访问控制。 2.专有网络内的ECS使用安全组防火墙进行三层网络访问控制,ACL进行二层…

基于STM32的无人驾驶汽车路径规划与视觉识别系统

目录 引言项目背景环境准备 硬件准备软件安装与配置系统设计 系统架构关键技术代码示例 摄像头图像采集与处理路径规划算法实现实时视觉障碍物检测电机控制与执行应用场景结论 1. 引言 无人驾驶技术是当前自动化和人工智能领域的热门课题之一,涉及到复杂的感知、…

Python数据分析和可视化详解

Python数据分析和可视化详解 Python 是当前最受欢迎的数据分析和可视化工具之一。凭借其简单的语法和强大的第三方库,Python 为数据科学家、分析师和工程师提供了广泛的工具,用于处理、分析和展示数据。本文将介绍如何使用 Python 进行数据分析与可视化…

【NLP修炼系列之玩转LLM】基于 P-Tuning的高效微调ChatGLM方法

引言 上周给大家介绍了另一种基于LORA的高效微调ChatGLM-6B模型的方法。本周分享一下另一种高效的微调方法——P-Tuning v2方法,同时在文章的最后对比一下两种高效微调方法的效果怎么样,只有自己动手做实验了才能很客观的看出哪种方法效果更好&#xff…

超详细超实用!!!AI编程之cursor编写设计模式迪米特法则实例(八)

云风网 云风笔记 云风知识库 一、设计模式迪米特法则定义 只与你的直接朋友交谈,不跟“陌生人”说话 其含义是:如果两个软件实体无须直接通信,那么就不应当发生直接的相互调用,可以通过第三方转发该调用。其目的是降低类之间的耦…

leetcode面试题 03.04. 化栈为队

实现一个MyQueue类,该类用两个栈来实现一个队列。 示例: MyQueue queue new MyQueue();queue.push(1); queue.push(2); queue.peek(); // 返回 1 queue.pop(); // 返回 1 queue.empty(); // 返回 false 说明: 你只能使用标准的栈操作 -…

SpringBoot学习笔记(2)

1.静态文件访问 使用IDEA创建Spring Boot项目,会默认创建出classpath:/static/目录,静态资源一般放在这个目录下即可。 如果默认的静态资源过滤策略不能满足开发需求,也可以自定义静态资源过滤策略。 1.1直接访问 在application.properties中…

在线远程考试|基于springBoot的在线远程考试系统设计与实现(附项目源码+论文+数据库)

私信或留言即免费送开题报告和任务书(可指定任意题目) 目录 一、摘要 二、相关技术 三、系统设计 四、数据库设计 五、核心代码 六、论文参考 七、源码获取 一、摘要 信息数据从传统到当代,是一直在变革当中,突…

增强免疫力的9种食物,秋冬尤其要多吃,营养美味又健康!

随着秋风渐起,冬日的脚步也越来越近!这时候,咱们的身体可是需要更多的关爱和呵护。说到秋冬养生,增强免疫力是头等大事。今天就来跟大家聊聊,那些既营养美味,又能帮我们提升免疫力的9种超级食物&#xff0c…

关于将inet引入的相关问题

🏆本文收录于《全栈Bug调优(实战版)》专栏,主要记录项目实战过程中所遇到的Bug或因后果及提供真实有效的解决方案,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&am…

Android 简单实现联系人列表+字母索引效果

效果如上图。 Main Ideas 左右两个列表左列表展示人员数据,含有姓氏首字母的 header item右列表是一个全由姓氏首字母组成的索引列表,点击某个item,展示一个气泡组件(它会自动延时关闭), 左列表滚动并显示与点击的索引列表item …

UDS_1_基础知识

一. 概述 什么是UDS UDS: Unified Diagnostic Service, 统一诊断服务。 UDS是一个在整个汽车系统上经常使用的设备维护协议。其主要遵循:ISO-15765、ISO-14229 等协议。经常应用在整车的各种ECU上面。是一个在整车ECU应用层开发常用的协议之一。 UDS用途: 可以通过诊断…

【Python基础(二)】面向对象

学习分享 1、初始对象1.1、类的定义和使用1.2、类的私有成员和方法1.3、类的构造方法 2、继承的实现和属性的使用 1、初始对象 1.1、类的定义和使用 1.2、类的私有成员和方法 class Clock:id Noneprice Nonedef ring(self):import winsoundwinsound.Beep(2000,3000)clock Clo…