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

背景
写这篇文章的初衷是因为国产化项目临近结束,在做项目总结时,回想起了代码管理的相关问题。虽然工程搭建及管理是一个高级工程师必备的能力,但似乎每次一个新项目立项,都是从老的项目中拷贝工程框架,再进行定制修改。并没有认真了解其原理及整体工程管理和维护思想。
虽然大部分的软件工程,都具备一定的通用性,修修改改也能用起来,但是我觉得工程架构,应该要和软件架构相匹配,将软件架构思想贯彻到底。本文介绍我个人如何通过软件架构来构建工程架构的以及cmake常用的接口。
如何构思工程框架
以T-BOX项目为例,其软件架构如下:

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

理论上业务层只能调用抽象层接口,而抽象层只能调用基础层接口。
问题一:开源库如何管理?集成库还是集成生成物?
这是大家可能常会考虑的问题,我一般倾向于集成开源库的生成物,原因有以下几点:
- 空间浪费。开源库应该是具有通用性的,也就是说不同的项目都可以使用,若集成源代码,则多个不同项目,则会在git仓库中有多份。
- 编译时间增长。比如
openssl开源库的编译需要十几分钟。这就导致第一次编译时,时间非常长。 - 便于维护。有时候,开源库也存在一些bug,需要我们去修复,若是单独用仓库保存仅需要维护一处即可。再通知各个项目组,让他们主动更新。
当然,不同的角色,其考虑角度不一样,比如模组商应该更偏向于将开源库放在项目中。因为他们一般是需要将源码交付给客户的。
问题二: 如何剥离对外部或硬件依赖?
在之前的工作中,我们经常会面临的一个问题:软件已经具备联调条件,但是外部却迟迟不能配合,无法验证内部逻辑。比如模组的sdk或硬件未提供。于是我们的思路是:在x86环境下,可以进行部分业务自测。对于外部依赖的sdk,可以自己模拟接口,配置相关应答。
问题三:如何将我们的程序集成到模组中?
我们工程编译的最终产物一般是可执行程序、动态库、配置文件。如何让其在模组中运行就需要解决两点:
- 如何让我们的程序开机启动。解决方式:需要修改启动脚本,再生成文件系统。
- 如何将我们的程序集成到模组中。解决方式:模组商一般会提供一个单独的分区,需要将我们的程序生成单独的ubi文件,进行烧录。
大致流程:
- 将我们的生成物,按照模组商的提供的指导文档,生成对应的
oem_data.ubi。 - 将修改的文件,替换到模组上的文件系统中,再生成文件系统
root.squashfs。 - 将
oem_data.ubi、root.squashfs替换到整包中,进行烧录。
问题四: 模组商的SDK如何管理?
正如上面讨论的:foundation->oem->arm目录、模组商的rootfs、升级整包、交叉工具链等,都应该是模组上提供的SDK的部分生成物。如何管理模组商的SDK呢?我的建议是单独上库处理。原因有以下几点:
- 模组商的SDK太大,一般有1G多。且编译时间很长,首次编译一般需要几个小时。
- 后续迭代,方便跟踪,溯源。
现在,我们的项目的工程目录大致如下:

且有三个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" ]
thenexport PROJECT_BUILD_TYPE=${TYPES_ARM}
elseexport PROJECT_BUILD_TYPE=$1
fi
echo "PROJECT_BUILD_TYPE:${PROJECT_BUILD_TYPE}"# 判断交叉编译链是否存在
if [[ "$1" == $TYPES_ARM ]]; thenif [ ! -d $SRC_CODE_DIR/toolchain ]; thenecho "目录 $SRC_CODE_DIR/toolchain 不存在"exitfi# 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" ]; thenecho $?echo "build foundation successful"
elseecho $?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 thesystem variable OPENSSL_ROOT_DIR (missing: OPENSSL_CRYPTO_LIBRARY) (foundversion "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" ]; thenplatform=arm
elseplatform=$1
fiif [[ "$platform" == "arm" ]]; thenif [ ! -d $TOPDIR/toolchain ]; thenecho "目录 $TOPDIR/toolchain 不存在"exitfisource t108_compile_tool_env_init
fi# 创建目录和编译
rm -rf $TOPDIR/build
mkdir $TOPDIR/build# 编译,并传入平台
cd $TOPDIR/build
cmake -DENV_PLATFORM=$platform ..
make -j8if [ "$?" == "0" ]; thenecho $?echo "build $PROJECT_NAME successful"
elseecho $?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/librm -rf $EXTERNAL_OUTPUT_EXEmkdir -p $EXTERNAL_OUTPUT_EXE
mkdir -p $EXTERNAL_OUTPUT_EXE_BIN
mkdir -p $EXTERNAL_OUTPUT_EXE_ETC
mkdir -p $EXTERNAL_OUTPUT_EXE_LIBcp -rf $TOPDIR/build/common/libsaFoundation.so $EXTERNAL_OUTPUT_EXE_LIB
cp -rf $TOPDIR/build/abstract/libsaEngine.so $EXTERNAL_OUTPUT_EXE_LIBcp -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_BINcp -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_LIBRARIESadmdebugcjsoneasyloggeriniparsermosquitto_staticsqlite3${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_LIBRARIESadmdebugcjsoneasyloggeriniparsermosquitto_staticsqlite3#lyqrlynq_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}PRIVATEsaFoundation
)
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.csaInitBasicModule.c#其它服务源码
)#添加头文件查找路径。理论上若abstruct层做的足够优秀,业务层是不会直接调用foundation层接口的。
include_directories(${CMAKE_SOURCE_DIR}/abstract/inc/
)# 设置库依赖
target_link_libraries(${project_name}PRIVATEsaFoundationsaEngine)#设置库查找路径
target_link_directories(${project_name} PRIVATE ${FOUNDATION_LIBDIR})
编译成功之后,就是需要构建整包了。package.sh 内容如下:
#!/bin/bash
export PROJECT_ROOT=$(cd "$(dirname "$0")";pwd)# 创建输出目录
if [ ! -d $PROJECT_ROOT/package ];thenmkdir $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_COMMITIDmkdir $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,人生尽是坦途


相关文章:
【项目总结】工程构建思路分析与分享
背景 写这篇文章的初衷是因为国产化项目临近结束,在做项目总结时,回想起了代码管理的相关问题。虽然工程搭建及管理是一个高级工程师必备的能力,但似乎每次一个新项目立项,都是从老的项目中拷贝工程框架,再进行定制修…...
3、AI测试辅助-测试计划编写(自动生成任务甘特图)
AI测试辅助-测试计划编写 一、创建测试计划1.1 使用ChatGpt生成测试计划1.1.1 测试计划细化-测试任务排期1.1.2 测试计划细化-利用plantuml输出甘特图(不推荐)1.1.3 测试计划细化-利用mermaid 生成甘特图(推荐) 一、创建测试计划 …...
MySQL:进阶巩固-触发器
目录 一、基本概述二、语法结构三、基本使用3.1 新增的触发器3.2 修改的触发器3.3 删除的触发器 一、基本概述 触发器是与表有关的数据对象,在INSERT/UPDATE/DELETE之前或者是之后,触发并执行触发器中定义的SQL语句的集合,触发器的这种特性可…...
python获取百度地图路径规划
自制语音助手想做一个查询交通路线的功能,于是就想到了百度地图。在百度地图的服务器端应用中,提供了“轻量级路线规划”API接口,正好可以满足需求。要用这个接口,需要注册百度地图账号并创建一个服务器端的应用。有关开通百度地图…...
Spring Boot 进阶- Spring Boot 自定义拦截器详解
上篇文章中我们创建了一个RestFul风格的接口,并且介绍了如何对其结果进行格式化的方式。这篇文章我们来讲在SpringMVC中常用到的另一个概念拦截器。 什么是拦截器? 在Spring MVC中拦截器有点类似于在Servlet中的过滤器的概念,主要是用来拦截用户求,并且对用户请求进行相应…...
ACM第三次考核题解
ACM第三次考核题解 题目序号难度题目编号题目考察知识点1签到题A这是一道很难的题!!!输出2迷之难度F神说要有光,于是有了手电筒贪心3简单BThis is a real English problem!思维 英语4简单C玩具简单排序5简单I“近义词…...
基于Vue3内置的lodash函数库实现防抖节流
前言 防抖节流是可以说是一种优化组件性能的技巧,可以有效减少组件中的渲染次数和计算量,从而提高组件的响应速度和用户体验。在Vue3中可以使用lodash库中的debounce和throttle函数来分别实现防抖和节流。当然也可以自行设计实现防抖节流函数࿰…...
ls -a有什么作用
ls -a 是一个在类Unix操作系统(如Linux、macOS)中使用的命令,用于列出目录中的所有文件和目录,包括那些以.开头的隐藏文件。 具体作用如下: ls: 这是一个列出目录内容的命令。 -a: 这是一个选项,表示"…...
LLM - 使用 RAG (检索增强生成) 多路召回 实现 精准知识问答 教程
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/142629289 免责声明:本文来源于个人知识与公开资料,仅用于学术交流,欢迎讨论,不支持转载。 RAG (R…...
编程语言图书创作要注意的事情有哪些?
编程语言图书的创作是一项复杂且具有挑战性的任务,需要作者深入理解技术、清晰表达,并考虑读者的学习体验。一本优秀的编程书籍不仅能够教授技术知识,更能引导读者逐步深入,激发他们的思考和实际应用能力。以下将详细探讨编程语言…...
主流高级编程语言的推出时间及年份
1.下表一些主流高级编程语言的推出时间及年份: 高级语言 推出时间 岁数 FORTRAN 1957 67 LISP 1959 65 COBOL 1961 63 BASIC 1964 60 Pascal 1970 54 C 1972 52 MATLAB 1978 46 SQL 1978 46 Objective-C 1983 41 C 1983 41 Perl …...
qt 模仿简易的软狗实现
我们在写软件的时候,希望我们的软件只在固定的机器上运行,其他机器上运行不了,那我们应该如何做呢? 1 首先我们需要得到运行机器的mac地址,这样可以简易的判断是否是我们授权的机器。 那我们首先定义一个授权mac机器…...
荣业食品销售费用每年上亿元:主要产品收入大降,电商占比过低
《港湾商业观察》黄懿 今年3月,广东荣业食品有限公司的控股公司Wing Yip Food Holdings Group Limited(下称“荣业食品”)向美国SEC递交了纳斯达克上市申请。 据悉,2023年11月,商务部宣布移除了一批共计55家因长期经…...
数据结构:并查集
数据结构:并查集 并查集原理实现框架初始化合并查询获取成员路径压缩其它 总代码 并查集 在生活中,经常会出现分组问题。比如一个班级分为多个小组,打篮球分为两方等等。在同一个组中的所有成员,就构成一个集合。对这种一个群体分…...
微信小程序实战教程:轻松实现列表批量选择功能
在许多场景下,用户需要对列表中的多项内容进行操作,如批量删除、批量下载等。为了满足这一需求,我们需要在微信小程序中实现列表批量选择功能。具体要求如下: 用户可以逐个选择列表项,也可通过全选按钮快速选择所有列表…...
企业微信:开启客户联系和配置
前言 客户联系是企业微信的一项非常实用且自定义化配置丰富的功能,使企业内的授权员工可以添加外部客户(企业微信联系人和微信联系人)进行工作沟通,并且还可以建立客户群,甚至发表内容到客户朋友圈! 由于功…...
Python发送邮件教程:如何实现自动化发信?
Python发送邮件有哪些方法?如何利用python发送邮件? 无论是工作汇报、客户通知还是个人提醒,邮件都能快速传递信息。Python发送邮件的自动化功能就显得尤为重要。AokSend将详细介绍如何使用Python发送邮件,实现自动化发信&#x…...
一周热门|苏姿丰:芯片行业不能只盯着 GPU;Gartner:GenAI 即将越过期望膨胀期
大模型周报将从【企业动态】【技术前瞻】【政策法规】【专家观点】四部分,带你快速跟进大模型行业热门动态。 01 企业动态 Open AI 计划从非营利组织向营利组织转型 日前,路透社报道称,OpenAI 正在制定一项计划,将其核心业务重…...
Failed to load WebView provider: No WebView installed
1、问题 使用webview加载网页,在应用运行时,报了如下错误:android.webkit.WebViewFactory$MissingWebViewPackageException: Failed to load WebView provider: No WebView installed2、分析 通过查看项目的修改记录,确实安装了We…...
java日志框架之Log4j
文章目录 一、Log4j简介二、Log4j组件介绍1、Loggers (日志记录器)2、Appenders(输出控制器)3、Layout(日志格式化器) 三、Log4j快速入门四、Log4j自定义配置文件输出日志1、输出到控制台2、输出到文件3、输出到数据库 五、Log4j自…...
ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
基于Java+MySQL实现(GUI)客户管理系统
客户资料管理系统的设计与实现 第一章 需求分析 1.1 需求总体介绍 本项目为了方便维护客户信息为了方便维护客户信息,对客户进行统一管理,可以把所有客户信息录入系统,进行维护和统计功能。可通过文件的方式保存相关录入数据,对…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
【p2p、分布式,区块链笔记 MESH】Bluetooth蓝牙通信 BLE Mesh协议的拓扑结构 定向转发机制
目录 节点的功能承载层(GATT/Adv)局限性: 拓扑关系定向转发机制定向转发意义 CG 节点的功能 节点的功能由节点支持的特性和功能决定。所有节点都能够发送和接收网格消息。节点还可以选择支持一个或多个附加功能,如 Configuration …...
实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
