新闻详情

新闻详情

首页 / 资讯中心 / 详情

苍穹外卖项目开发实战:从零搭建Spring Boot后端骨架

发布时间:2026/9/30 11:32:33来源:尧图网络
苍穹外卖项目开发实战:从零搭建Spring Boot后端骨架
1. 从零梳理苍穹外卖项目的整体技术全貌很多人在看到苍穹外卖Day01的笔记时第一反应是“这不就是又一个CRUD项目嘛”但实际动手做下来你会发现它的价值恰恰在于把企业级开发中那些“约定俗成”的东西串起来了。Day01的核心任务不是写多少业务代码而是把项目骨架、技术栈选型、数据库设计思路、通用组件封装这些地基打牢。地基稳不稳直接决定后面几天做新增菜品、订单流转、店铺营业状态这些功能时会不会返工。先说技术选型。苍穹外卖用的是Spring Boot MyBatis MySQL Redis这套经典组合前端是Vue3 Element Plus前后端分离。Day01虽然还没到写前端页面的时候但从接口设计上已经在为前后端联调做准备了。这里我想特别强调一个点很多人会用Spring Boot自带的JAP或者MyBatis-Plus但苍穹外卖用的是原生MyBatis加XML映射。不是原生MyBatis更好而是这个项目刻意选择了SQL可控性更强的方案。外卖业务的查询场景非常复杂比如按条件分页查菜品、统计当日营业额SQL一旦复杂起来XML里手写SQL反而比框架自动生成的更好排查问题、更好优化索引。你把这个项目做完再去用MyBatis-Plus会理解为什么那么多公司在生成复杂报表时还是愿意回归XML写SQL。数据库设计上Day01首先登场的是employee员工表。这张表的设计带着明显的电商系统基因id主键、username唯一索引、password存的是加密后的密文、status字段控制启用禁用、create_time和update_time用datetime类型。create_user和update_user这两个字段特别值得注意它们是用来记录“这条数据是谁创建的、谁最后修改的”在后端通过拦截器从Token里解析出当前登录用户ID然后自动填充进去。这是一种很典型的审计字段设计思路在后端管理系统里几乎是标配。我当时第一次看到这个设计时没太在意直到后面做分类管理、菜品管理时需要追溯某个菜品是谁改的才发现这两个字段有多好用。Day01还有一个容易被忽略但非常重要的点项目采用的是RSA非对称加密来保证登录接口的密码传输安全。登录页面前端用公钥对密码加密后端用私钥解密而不是直接明文传输或只做一次MD5。原因很简单HTTP请求在网络上传输时是明文就算加了HTTPS也不能完全杜绝中间人抓包的风险。如果前端直接POST一个明文密码密码在浏览器开发者工具里一眼就能看到。RSA加密的代价是性能开销比对称加密大但登录接口本身频率不高这个开销完全可以接受。我第一次做这个项目时直接跳过了RSA用明文密码联调结果一上线就被同事怼了后来才老老实实补上。所以Day01的登录模块千万别为了图省事跳过RSA这套流程做完你对“接口安全设计”的理解会上一个台阶。简单总结一下Day01的完整链条搭建工程骨架 - 准备数据库 - 封装通用返回结果和异常处理 - 实现员工登录接口 - 用JWT生成Token - 配置拦截器校验登录状态。这条链路几乎就是所有管理系统后端的“标准开场白”。2. 环境准备与工程结构搭建2.1 开发环境版本选型苍穹外卖对开发环境的版本要求不算苛刻但版本不匹配会引发一堆莫名其妙的问题。我用的这套组合是实测稳的JDK 1.8项目本身基于Java 8语法Spring Boot 2.7.x也要求JDK 8以上。不要用JDK 17跑这个项目虽然能启动但Spring Boot 2.7对JDK 17的支持没那么完美可能出现一些奇怪的反射警告。Maven 3.6.3用IDEA自带Maven也行但建议用独立安装的并配置阿里云镜像源。原因很现实Maven中央仓库在国内拉取依赖的速度实在太慢尤其是第一次构建时要下载几百MB的依赖。MySQL 5.7或8.0建议直接用8.0连接驱动用mysql-connector-java 8.0.x注意在URL里加上useSSLfalse和serverTimezoneAsia/Shanghai避免时区和SSL握手导致的连接问题。Redis建议用Windows版或Docker起一个Day01的验证码存储和后期的店铺营业状态都会用到。一个新手容易踩的坑是项目里pom.xml用的spring-boot-starter-parent版本是2.7.x但自己本机的Maven settings.xml里配置了旧的镜像地址导致依赖下载失败。遇到这种问题不要慌先检查IDEA的Maven配置是不是指向了正确的settings.xml再看本地仓库路径下有没有下载失败的.lastUpdated文件。我一般会把IDEA的Maven Runner里的VM Options设成-Dmaven.wagon.http.connectionTimeout60000 -Dmaven.wagon.http.readTimeout60000防止网络波动导致构建中断。2.2 前后端分离的工程目录规划Day01会看到一个非常清晰的工程结构sky-pojo、sky-server、sky-common这三个模块。这不是随便分的而是按照“实体/DTO/VO”和“通用工具”的职责边界拆的。sky-pojo放实体类Entity、DTO、VO。这些类没有业务逻辑纯粹是数据的载体。DTO是前端传给后端的参数对象VO是后端返回给前端的视图对象。有些人会把DTO和VO混着用项目小的时候确实无所谓但苍穹外卖这种业务菜品查询要返回口味列表订单查询要返回用户地址DTO和VO分不清后期就是一团乱麻。sky-common放常量类、工具类、通用返回结果Result、异常处理类、JWT工具类。这个模块所有子模块都会依赖。sky-server放Controller、Service、Mapper、配置类、拦截器、启动类。就是真正的业务代码所在地。这种多模块设计的好处是当微服务拆分时sky-pojo和sky-common可以原封不动地抽离成独立的依赖包sky-server拆成多个微服务时直接引用即可。我第一次做单体架构的项目时没有分模块所有的类堆在一个工程里后面想提取公共代码只能靠复制粘贴。所以如果你之前没接触过多模块工程Day01的这个结构本身就是值得学习的知识点。在创建工程时IDEA会自动生成一个父pom.xml里面会统一管理依赖版本。子模块的pom里不需要还要再写一遍版本号直接依赖父模块的dependencyManagement。这一步非常关键所有子模块的Spring Boot版本必须统一否则会出现NoSuchMethodError这类难以排查的运行时异常。2.3 数据库初始化与连接配置注意事项Day01提供的sql脚本里除了employee表还有category、dish、flavor、orders等表结构。虽然第一天只用到employee表但我强烈建议把整个脚本都导入进去后面几天开发时就不用反复导表了。导入时要注意使用UTF-8编码导入防止中文乱码。命令行下用source命令导入时先执行set names utf8mb4。如果表已存在先DROP TABLE再导入否则会报Table already exists。注意employee表的password字段初始数据里存的是一个加密后的密文默认密码123456对应的就是这串密文。我们开发调试时可以直接用这个默认账号登录但生产环境务必改成随机强密码并重新加密。数据库连接配置在application.yml里用jdbc:mysql://localhost:3306/sky_take_out?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai这种格式。这里的serverTimezoneAsia/Shanghai一定要加否则数据库连接时会报“The server time zone value is unrecognized”错误。我第一遍做这个项目就卡在这折腾了半小时才想起来是时区问题。3. 员工登录与JWT鉴权完整实现3.1 Spring Boot工程创建与Maven依赖导入创建工程时可以选择Spring Initializr快速生成也可以直接手动建一个Maven工程。我建议用后者因为前者生成的目录结构和苍穹外卖的规范不完全一致后面要对齐工程结构反而麻烦。依赖方面Day01用到的核心依赖包括spring-boot-starter-web内置Tomcat提供MVC能力。mybatis-spring-boot-starterMyBatis与Spring Boot的集成。mysql-connector-javaMySQL驱动。lombok省略getter/setter。spring-boot-starter-data-redisRedis操作。jjwtJWT的生成与解析。hutoolJava工具类库用于生成UUID、Base64编码等。这里需要注意jjwt的版本。我早期用过0.9.1后来换了0.11.5。0.11.x的API变化较大比如Jwts.builder()要指定签名算法parseClaimsJws返回的Claims类型也不同。如果你照着老教程写代码在新版本里会编译报错。建议统一使用0.11.5并对齐官方文档中的用法。在pom.xml里配置完依赖后记得右键选择“Maven Reload Project”让IDEA重新解析依赖。然后检查一下Maven依赖树确认没有版本冲突。实际操作中spring-boot-starter-data-redis会引入lettuce-core而jjwt会引入jackson-databind两者在版本上一般不冲突但若有冲突以父pom中声明的版本为准即可。3.2 启动类与基础配置类编写主启动类放在sky-server模块的com.sky包下用SpringBootApplication注解。这里有一个小细节启动类的包路径一定要放在所有子包里。比如com.sky.SkyApplication那么com.sky.controller、com.sky.service这些子包都会被自动扫描到。如果你手滑把启动类放到了com.sky.server包下而Controller放在com.sky.controller包下Spring容器默认扫描的是启动类所在包及其子包就会扫描不到Controller导致接口404。这种问题不会报错只是请求全部404特别难排查。配置类方面Day01需要配置一个MyBatis的Mapper扫描注解MapperScan(com.sky.mapper)。这一步省去在每个Mapper接口上写Mapper的麻烦。同时配置一个Spring MVC的消息转换器让JSON序列化时将Long类型转成String返回给前端。为什么因为JavaScript的Number类型能精确表达的最大整数是2^53-1而Java的Long类型最大可到2^63-1雪花算法生成的ID会超出JS的安全整数范围前端拿到的ID会不精确。在实际面试中这个问题经常被问到答出来也是加分项。3.3 通用返回结果Result封装与全局异常处理器后端接口的返回格式需要统一苍穹外卖中封装了Result对象包含code、msg、data三个字段。code为1表示成功0表示失败。这样设计的好处是前端可以统一处理响应结果比如在axios拦截器里判断res.code 1再走业务逻辑否则弹出错误提示。很多人喜欢用HTTP状态码来区分业务成功和失败但在前后端分离的场景下HTTP状态码更适合表达“请求本身的状态”比如401表示未登录405表示请求方法不对。业务上的成功或失败用业务码更灵活。比如用户不存在、密码错误HTTP状态码都是200但业务code是0msg是“用户名或密码错误”。这种设计可以避免浏览器控制台里一堆红字的尴尬。全局异常处理用RestControllerAdvice ExceptionHandler实现。Day01主要处理两类异常业务异常SkyException和未捕获的Exception。SkyException是自定义的运行时异常在Service层里通过throw new SkyException(...)抛出。ExceptionHandler(SkyException.class)捕获后返回Result.error(e.getMessage())。而ExceptionHandler(Exception.class)则是兜底防止出现未捕获的异常直接堆栈抛给前端。这里要注意业务异常和系统异常分开处理非常重要否则用户会看到500 一长串堆栈信息体验极差。3.4 登录接口的业务逻辑与密码校验细节员工登录的Controller接口路径是POST /admin/employee/login参数用EmployeeLoginDTO接收包含username和password。登录逻辑看起来简单但有两个关键细节密码加密数据库中存的密码是BCrypt加密后的密文。前端传过来的明文密码先经过RSA私钥解密再交给BCrypt去校验。BCrypt的校验方式是bcrypt.matches(rawPassword, encodedPassword)而不是把数据库里的密文解密成明文来对比因为BCrypt本身是不可逆的。这一点初学者经常搞混以为密文能解开其实正是它不能解开才安全。禁用状态校验employee表里有status字段1表示启用0表示禁用。登录时查到用户后要判断status如果是0要抛异常“账号已被禁用”。很多初学者只校验了用户名密码漏了状态判断结果被禁用的用户还能登录操作一查日志才发现是这里漏了。登录成功后需要生成JWT令牌返回给前端。JWT的payload部分可以放id、username、当前时间等。签名密钥用HS256算法。密钥至少要32个字符否则jjwt会报“Keys.secretKeyFor”的异常。生成好的Token在后续每个请求中通过请求头Authorization携带后端拦截器解析并校验。3.5 JWT令牌的生成机制与拦截器配置JWT本质上是一个三段式的字符串Header.Payload.Signature。Header指定算法和类型Payload存放业务数据Signature用密钥对前两段做签名。服务端不保存Token状态所以JWT是无状态的。但这也带来一个问题无法主动让Token失效。比如用户修改密码后旧的Token依然有效除非我们在Redis里维护一个“无效Token集合”来拦截。苍穹外卖的这个项目暂时没有做Token撤销但在真实项目中务必要考虑。这是我做了几个项目之后才想明白的一点JWT不是万能药它的无状态特性在需要强制下线、踢人下线的场景下反而是短板。拦截器用HandlerInterceptor实现。在preHandle方法中从请求头Authorization中取出Token调用JwtUtil解析。解析失败或Token过期的直接返回401并阻止请求继续。放行的把当前用户ID放到ThreadLocal中供Service层使用。重点在拦截器的注册配置要继承WebMvcConfigurationSupport或实现WebMvcConfigurer的addInterceptors方法同时用addPathPatterns(/admin/**)拦截所有后台接口用excludePathPatterns(/admin/employee/login)排除登录接口。如果你漏掉了排除登录接口那么登录接口自己也要求登录这就形成了一个死循环。我第一次做的时候把登录接口也拦截了浏览器里一直报401愣是排查了半天才意识到拦截路径配错了。这里还有个小陷阱拦截器注册时如果你选择继承WebMvcConfigurationSupport会覆盖Spring Boot的自动配置导致静态资源映射失效。如果项目里有静态资源比如上传的图片预览会404。解决方案改为实现WebMvcConfigurer接口或者在继承WebMvcConfigurationSupport的同时手动配置静态资源映射。4. 通用返回结果与全局异常处理的细节设计4.1 Result对象的构建与前端约定Result对象在设计时一定要考虑三个维度成功与失败、返回数据、错误信息。苍穹外卖的Result定义如下字段含义示例code业务状态码1成功0失败msg提示信息“操作成功”或错误原因data返回的数据负载存放登录Token、对象列表等在Controller中返回数据时用Result.success(data)包装失败时用Result.error(msg)。前端拿到响应后会统一处理而不是每个接口单独判断。这个设计模式已经成了很多中大型项目的标配。如果你现在自己搭项目我建议直接在Common模块里定义好这个对象后面写接口时会非常省事。有一个细节需要注意Result中的静态方法名是success和error而不是ok和fail这是因为后端开发中success、error是更通用的词汇前端解析时也更好对应。另外data字段的类型建议用泛型T这样返回Result 时会带上类型信息接口文档也更好看。4.2 RestControllerAdvice统一异常捕获的完整策略RestControllerAdvice是一个组合注解包含ControllerAdvice和ResponseBody。它能把所有Controller抛出的异常统一收集经过ExceptionHandler指定的处理方法后以JSON格式返回给前端。Day01只需要处理两类异常即可业务异常SkyException和通用Exception。但后面几天随着功能增加还会遇到SQL异常、参数校验异常MethodArgumentNotValidException等。我给一个更完善的设计建议在全局异常处理器里增加对MethodArgumentNotValidException的处理这样DTO字段上的NotNull、NotBlank等校验注解生效时就不会返回一堆默认的英文错误信息而是返回我们自定义的字段描述。例如ExceptionHandler(MethodArgumentNotValidException.class) public ResultString handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) { String msg ex.getBindingResult().getFieldError().getDefaultMessage(); return Result.error(msg); }另外ExceptionHandler(Exception.class)这个兜底方法里一定要把完整的堆栈信息打印到日志中比如用log.error(系统异常, ex)。我之前见过有些同事在兜底方法里直接吞掉异常只返回“系统异常”结果生产环境出了问题一点日志都没有排查起来如同大海捞针。日志是程序员的眼睛千万别省。4.3 自定义业务异常SkyException的设计动机为什么要自定义一个业务异常而不是直接用RuntimeException好处有三点语义清晰看到new SkyException(用户名或密码错误)就知道这是业务逻辑主动抛出的错误而不是系统bug。统一处理全局异常处理器中可以对SkyException单独处理返回友好的msg对RuntimeException则统一返回“系统繁忙”之类的模糊提示避免把内部细节暴露给用户。灵活扩展后续可以在SkyException中添加错误码、错误级别等字段满足更复杂的错误处理需求。Service层抛出业务异常之后事务会怎样处理这涉及Spring事务的机制。默认情况下RuntimeException会触发事务回滚SkyException继承RuntimeException所以也会回滚。如果是受检异常比如直接继承Exception则不会自动回滚需要手动标记Transactional(rollbackFor Exception.class)。这是很多新手容易忽略的点如果你在Service里自定义了一个继承Exception的异常类而方法上又加了Transactional事务不会回滚数据就脏了。5. 实操心得与踩坑记录5.1 从零构建后端接口时的注意事项清单我总结了Day01最容易踩的坑把它们列成一份自查清单你在开发时逐一对照确认MySQL连接URL是否带serverTimezone参数否则报时区错误。确认application.yml里的MyBatis配置是否正确映射到XML文件路径包括mapper-locations。确认启动类所在包路径包含所有要扫描的包Controller是否被扫描到。确认Controller接口路径和方法上的PostMapping/GetMapping是否与前端请求一致。确认统一返回结果Result里的字段名是不是code、msg、data前端是否按这个约定解析。确认在拦截器里放行的URL是否正确登录接口必须放行。确认ThreadLocal在当前请求结束后要remove否则线程池复用时会数据串线。ThreadLocal这块值得展开说一下。Spring Boot内置的Tomcat是线程池复用的处理完一个请求后线程不会销毁而是放回线程池。如果你在拦截器里set了ThreadLocal而没有在afterCompletion里remove下一次请求复用同一个线程时ThreadLocal里的旧数据就会被读到造成非常隐蔽的bug。我自己就遇到过用户A登录后用户B的请求里居然读取到了A的ID排查了好几个小时最后发现就是ThreadLocal没清理干净。5.2 Day01中的数据库操作细节与MyBatis映射建议Day01中查询员工信息用到了Mapper接口加XML映射。在XML里注意字段名与数据库列名的映射用resultMap或驼峰命名转换map-underscore-to-camel-case: true。MyBatis的驼峰转换只能把数据库的snake_case转成Java的camelCase即employee表里的create_time转成createTime。如果你的实体类字段命名和数据库列名不是一一对应就要手动在XML中写清楚。分页查询时Day01用的是PageHelper插件。PageHelper的原理是拦截即将执行的SQL在SQL末尾拼接limit语句。要注意PageHelper.startPage()必须在查询方法之前调用并且同一线程内只能生效一次。如果你在循环里调用startPage会引发分页错乱。插入数据时如果数据库表的主键是自增的可以加useGeneratedKeystrue keyPropertyid让MyBatis把自增主键回填到实体类的id字段。这个技巧在后面批量添加菜品时非常有用。MyBatis的XML映射文件路径默认放在resources/mapper目录下。如果你把XML放到了java目录的com.sky.mapper包里默认是不会编译到target/classes的运行时会报Invalid bound statement (not found)错误。解决办法是在pom.xml里配置resources资源路径把src/main/java下所有xml文件也包含进去。这个坑几乎是每个MyBatis新手都会踩到的我再强调一遍。5.3 密码加密方案对比从MD5到BCrypt的选型逻辑不少新手在写登录模块时习惯用MD5加盐来加密密码。MD5确实很快但已经被证实可以暴力碰撞破解而且彩虹表攻击更是让它形同虚设。BCrypt的设计是故意放慢哈希速度默认10次迭代让暴力破解的成本成倍上升。虽然BCrypt每次哈希耗时大约100毫秒比MD5慢很多但对于登录这种低频率操作来说完全可接受。更关键的是BCrypt每次生成的结果都不一样同一个明文每次加密得到的密文都不同因为内置了随机盐。这意味着你无法通过对比密文来猜测两个用户是否用了相同密码。所以无论是苍穹外卖项目还是以后你自己从零搭项目密码加密直接选BCrypt不要再用MD5了。在实际使用中BCrypt的密码校验不建议在数据库中保存盐因为BCrypt的盐已经编码在密文字符串中了。这一点和MD5需要单独存储盐不一样省去了很多麻烦也是它成为现代密码存储标准方案的重要原因。5.4 联调常见问题排查思路从401到500逐一击破Day01做完登录接口后通常会先用Postman或Apifox做一次冒烟测试。这里我分享一下实际联调时的问题排查顺序先看请求能不能到达Controller。如果请求直接404优先检查Controller路径是否写错、启动类扫描包是否覆盖。如果请求返回401说明被拦截器拦截了检查请求头是否没有带Authorization或者Token解析报错。如果请求返回500优先看后端日志找到具体异常堆栈。前面提到过不要把日志吞掉否则这里就会停住。大部分情况下问题出在Token的解析上。JWT解析失败的原因有几种Token过期、密钥不对前后端密钥必须一致、Token被截断了。如果使用jjwt 0.11.5版本解析代码大致是Claims claims Jwts.parserBuilder() .setSigningKey(secretKey) .build() .parseClaimsJws(token) .getBody();注意旧版本是parser()新版本改成了parserBuilder()。版本不同代码写法不同报错信息也会不一样。遇到版本问题时最直接的办法就是把pom.xml里的版本号统一锁定然后重新加载依赖不要自己猜API。5.5 使用Postman和Apifox调试接口的实操要点用Postman调试后端接口时建议把登录接口单独放在一个环境变量里集中管理。例如在Postman的Environment中设置baseUrl为http://localhost:8080登录接口返回的Token用Tests脚本自动写入环境变量var jsonData pm.response.json(); pm.environment.set(token, jsonData.data);这样后续所有需要鉴权的接口只需要在Authorization选项卡中选择Bearer Token并引用{{token}}变量即可。省去了每次登录后手动复制Token的麻烦。如果你更习惯国产工具Apifox也是不错的选择。它的优势在于可以自动从Swagger/OpenAPI导入接口文档并且支持Mock服务。前端还没开始写的时候后端可以先Mock出接口数据两边并行开发。我在实际团队协作中常把Apifox作为前后端对接的唯一入口所有接口文档都在上面维护避免出现代码写好了但文档没更新导致前端对接出错的尴尬。Day01的接口虽然不多但从一开始就养成“接口文档与代码同步更新”的习惯后面做十几张表的增删改查时会非常从容。6. 第一天学习内容的心得体会回到开头说的那句话苍穹外卖这个项目的价值不只是教你写一个外卖系统而是让你把Spring Boot开发中的套路练熟。Day01这条链路——搭建工程、配置数据库、封装公共组件、实现登录、集成JWT——几乎是所有后端管理系统的共通骨架。你在Day01里花的时间越足后面做菜品管理、订单管理时就越轻松。因为那时候你不需要再纠结Result怎么封装、异常怎么处理、拦截器怎么配这些问题都已经在前面解决了。我个人实际敲代码的感受是Day01最花时间的反而是工程搭建和环境配置真正的业务代码其实很少。但恰恰是这部分最容易劝退新手因为它不像写业务逻辑那样有立竿见影的成就感。我的建议是遇到环境问题先别急着删了重来先查日志再猜原因。很多时候所谓的“玄学问题”背后就是版本不一致、路径写错、配置遗漏这些非常具体的原因。如果你也是跟着课程或文档学这个项目记住一个原则Day01的代码不要只是复制粘贴跑通就算了一定要亲手敲一遍哪怕是看着代码敲也行。敲的过程中你才会发现原来RestControllerAdvice不会自动生效需要Spring扫描到原来MapperScan不加的话Mapper代理bean根本创建不出来原来配置文件里少一个冒号整个服务都起不来。这些经验复制的代码永远给不了你。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

SpringBoot2+Vue3图书电商系统:源码解析与部署实战 2026/9/30 12:15:17

SpringBoot2+Vue3图书电商系统:源码解析与部署实战

1. 项目定位与核心价值拆解先把话说在前头:这个项目标题——“Java Web 图书电子商务网站系统源码-SpringBoot2Vue3MyBatis-PlusMySQL8.0【含文档】”,我第一眼看到就知道它背后对应的是什么样的需求。它不是那种“玩具级”的CRUD demo,而是一…

阅读更多 →
wangEditor Word图片自动粘贴上传:从剪贴板到服务器的完整实现 2026/9/30 12:15:16

wangEditor Word图片自动粘贴上传:从剪贴板到服务器的完整实现

做内容管理系统的前端,基本躲不开富文本编辑器。wangEditor是我用得比较多的一款,尤其是v5版本,配置灵活、API清晰,社区也活跃。不过有一个需求几乎每次都会碰到:用户从Word文档里复制一段图文内容,粘贴到编…

阅读更多 →
C++ inline内联函数完全指南:原理、使用场景与性能权衡 2026/9/30 12:15:09

C++ inline内联函数完全指南:原理、使用场景与性能权衡

C inline:真正搞懂内联函数,看这一篇就够了很多学C的朋友对inline一直有种“好像会了,但一细问就露馅”的感觉。网上的教程要么只告诉你“在函数前面加inline可以把函数嵌入调用点”,要么直接丢给你一堆编译链接报错让你自己去踩坑…

阅读更多 →
HTML转PDF实战:html2canvas与jsPDF的清晰度、分页与跨域问题全解 2026/9/30 12:15:09

HTML转PDF实战:html2canvas与jsPDF的清晰度、分页与跨域问题全解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
重新审视TensorFlow:2024年安装、使用与部署实战指南 2026/9/30 12:15:09

重新审视TensorFlow:2024年安装、使用与部署实战指南

1. 站在2024年重新审视TensorFlow:它还值得学吗如果你是一个刚入门的深度学习爱好者,打开招聘网站或社区论坛,大概率会被“PyTorch现在是主流框架”的声音包围。我自己的社群后台,几乎每周都会收到类似提问:2024年了Te…

阅读更多 →
佳易王棋牌桌球计时计费管理系统:从选型到实操全解析 2026/9/30 12:15:09

佳易王棋牌桌球计时计费管理系统:从选型到实操全解析

棋牌室和桌球厅这类按时间收费的营业场所,最怕的不是生意不好,而是生意好了账却乱了。开台时间靠人记、加钟靠口头喊、会员折扣靠翻手机备忘录,月底一对账单,老板和前台各执一词,这种场景我见过太多。佳易王棋牌桌球计…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉