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

鲜花网站模板下载中国交通建设集团官方网站

鲜花网站模板下载,中国交通建设集团官方网站,秦皇岛网站关键词推广,拷贝字体到wordpress背景 写这篇文章的初衷是因为国产化项目临近结束#xff0c;在做项目总结时#xff0c;回想起了代码管理的相关问题。虽然工程搭建及管理是一个高级工程师必备的能力#xff0c;但似乎每次一个新项目立项#xff0c;都是从老的项目中拷贝工程框架#xff0c;再进行定制修… 背景 写这篇文章的初衷是因为国产化项目临近结束在做项目总结时回想起了代码管理的相关问题。虽然工程搭建及管理是一个高级工程师必备的能力但似乎每次一个新项目立项都是从老的项目中拷贝工程框架再进行定制修改。并没有认真了解其原理及整体工程管理和维护思想。 虽然大部分的软件工程都具备一定的通用性修修改改也能用起来但是我觉得工程架构应该要和软件架构相匹配将软件架构思想贯彻到底。本文介绍我个人如何通过软件架构来构建工程架构的以及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_NAMEfoundation TOPDIR$(pwd) SRC_CODE_DIR$TOPDIR#表示编译的平台因为项目仓库需要支持arm环境和x86环境因此底层依赖库需要提供两个版本。 export TYPES_ARMarm export TYPES_X86x86 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_NAMEitu2.0 TOPDIR$(pwd)# 若没有设置平台则默认编译arm #!/bin/bash if [ -z $1 ]; thenplatformarm 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 errorexit 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_ETCCMakeLists.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 -stdgnu11) #set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -g -rdynamic -funwind-tables -ffunction-sections -stdc14 ) # 打开所有警告。组内要求编译过程不可以有任何警告 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_srcoem/x86/src/*)file(GLOB engine_incoem/x86/incoem/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#rpcrpc/src/*#networknetwork/src/*#gnssgnss/src/*#sese/src/fdw/*#lpmlpm/src/*#smssms/src/*#voice_callvoice_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 --comprzlib -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人生尽是坦途
http://www.tj-hxxt.cn/news/140921.html

相关文章:

  • 设计师网站pin网站模板 茶叶响应式
  • 营销型网站重要特点是?商城网站建设清单
  • 阿里云可以几个网站食品行业网站建设
  • 免费行情软件app网站不下载wordpress wp_head()在哪个文件中
  • 如何防止网站被注入黑链紫色 网站
  • 官方网站下载地址移动互联网开发是做什么的?
  • 内蒙古建设项目环保备案网站昆山网站建设昆山
  • 有没有一些帮做名片的网站游戏开发团队
  • 郑州网站推广优化公司青岛建筑
  • 深圳趣网站建设高密网站建设
  • 丹阳网站建设报价wordpress阅读量怎么查看
  • 惠东网站建设网站排名在哪里优化
  • 内部网站 建设方案管理咨询公司招聘条件
  • 商城网站页面设计做pc端网站方案
  • 网站更换备案吗中国网建设频道网站logo
  • 固安建设局网站漳州开企网
  • 哪些是门户网站网络营销视频
  • 东莞商城网站开发郑州公司注册网上核名
  • 在东莞找工作上哪个网站市场营销计划书模板
  • 购物网站线下推广方案千万不要去电商公司上班
  • 德阳网站建设推广小程序开发公司哪家正规
  • 太原哪里做网站正规外加工平台
  • 网站建设教程学习开发小程序和app开发哪个贵
  • 做电影网站的工具wordpress rest
  • 免费制作太原seo网站排名
  • ui培训的机构小红书seo排名帝搜软件
  • 免费网站如何做推广wordpress 中文版下载
  • 单页网站模板wap上海工商网一窗通
  • 品牌网站建设 结构好的响应式网站有哪些
  • 建设企业外贸网站常州专业网站建设费用