news 2026/9/8 4:05:08

Spring Boot + Spring Security + Redis 后台管理系统实战全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring Boot + Spring Security + Redis 后台管理系统实战全解析

简介:一个整合 Spring Boot、MyBatis、Spring MVC、Spring Security 与 Redis 的网站后台管理系统项目,适合正在学习 Java 企业级开发、需要积累完整项目经验的初中级开发者。项目中 Spring Boot 通过自动配置和内置 Tomcat 简化部署,MyBatis 支持定制化 SQL 与高级映射,Spring Security 负责登录认证、角色授权、CSRF 防护,Redis 则用于缓存热点数据、提高接口响应能力。压缩包共 326 个文件,包括 95 份 Java 源代码、前端 HTML/JS/CSS 资源、XML/yml 配置文件及 SQL 初始化脚本,整体只有 1.76MB,目录划分清晰,便于聚焦模块阅读。目前已有 957 人学习浏览。通过研究源码和配置,可以理解后台系统从请求接收、权限校验到数据持久化的完整流程,体会各框架在实际工程中的集成方式,从而提升 Spring 生态综合运用与排错能力。这一项目还适合作为课程设计或毕业设计的参考资料,能帮助巩固企业级开发技能。 做后台管理系统这件事,我前前后后摸爬滚打了好几年。前阵子刚完整落地了一套技术栈为Spring Boot + MyBatis + Spring MVC + Spring Security + Redis的网站后台管理系统,从需求梳理、环境搭建到权限设计、缓存治理,一路踩坑一路填坑,最后把整个项目稳定交付上线。这套组合在Java Web领域非常经典,适合大部分中后台业务场景:用户权限管理、运营后台、企业管理系统、内容管理平台等等,只要涉及多角色、多菜单、需要会话状态和数据缓存,这套方案基本都能兜住。

这篇文章我会把这套系统从0到1的全过程讲透,包括技术选型背后的理由、核心模块的实现套路、Spring Security + JWT + Redis这套认证授权链路怎么串、MyBatis使用中的缓存和动态SQL细节,以及我自己实际开发中遇到的几个典型问题和排查思路。不管你是刚接触后台管理系统开发的新手,还是想快速搭建一套可复用脚手架的老手,这篇文章都能给你一个可以直接“抄作业”的参考样本。

1. 整体设计与技术选型:为什么是这套技术栈

先说设计思路。后台管理系统看起来就是一堆增删改查页面,但真正做起来,难点从来不在CRUD本身,而在权限模型、会话管理、数据缓存、接口安全这几块。一个合格的网站后台,至少得满足这几个要求:不同角色登录后看到不同菜单、接口不能被越权调用、用户会话状态不能丢、高频数据不能被数据库压垮。

1.1 这套组合的具体分工

Spring Boot负责快速搭建和自动配置,把传统SSH或SSM时期繁琐的XML配置大幅度简化;Spring MVC承担Web层的请求分发,虽然Spring Boot已经内嵌了MVC能力,但理解它的控制器、拦截器、参数绑定机制依然是排查Web问题的基本功;MyBatis负责数据访问,半ORM的特性让你在复杂SQL面前有绝对掌控力,不像JPA那样遇到多表关联就头疼;Spring Security是认证授权框架,结合JWT可以做到无状态认证,再配合Redis存储token或权限信息,整套权限体系就非常牢固;Redis作为高性能内存数据库,承担会话缓存、权限缓存、数据字典缓存、分布式锁等多个角色。

这五个组件不是简单堆砌,是各司其职。Spring Boot是底盘,Spring MVC是入口管道,MyBatis是数据通道,Spring Security是门禁,Redis是加速层。这种组合在中小型项目中非常成熟,网上资料多、招聘需求里也常见,维护成本远比自研权限框架低。

1.2 为什么不用更“新”的架构

有人会问,现在微服务、Spring Cloud Alibaba那么火,为什么不用?说实话,百分之七八十的后台管理系统根本不需要微服务。多个服务拆分、注册中心、配置中心、分布式事务,这一套引入后,光基础设施的维护成本就够喝一壶。单体应用 + 合理模块拆分,在几千人使用的内部系统或几十万日活的业务后台里,完全扛得住。真到了性能瓶颈,Redis缓存、读写分离、分库分表都能平滑扩展。技术选型不是越新越好,是越匹配业务越好。

1.3 模块划分与系统边界

我这次做的系统功能上包括用户管理、角色管理、菜单管理、操作日志、登录认证、数据字典、文件上传、定时任务等标准模块。整体按“系统管理”和“业务支撑”两条线划分,系统管理线的核心是RBAC权限模型,业务支撑线则是各业务方自定义的增删改查。这种划分能让代码结构保持清爽,后续按模块迭代也不会互相打架。

2. 项目骨架搭建:环境、依赖与关键配置

这套系统落地前,环境准备非常关键。Java版本我用的JDK 11,Spring Boot用的2.7.x,为什么不直接上Spring Boot 3?当时考虑到Spring Security 5.x的稳定生态和部分第三方适配,2.7.x更稳妥。数据库用的MySQL 8.0,Redis用的6.x,Maven管理依赖。

2.1 核心依赖怎么加

在pom.xml中,基础依赖清单如下:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.18</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.3.2</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> </dependencies>

这里有个细节:Spring Boot 2.7.x的spring-boot-starter-data-redis默认使用Lettuce客户端,而不是老牌的Jedis。Lettuce基于Netty,支持异步和响应式,默认线程安全,在大多数场景下性能都比Jedis好。如果你没特殊理由,直接用默认的Lettuce就行。

2.2 YAML配置里容易踩的坑

application.yml的写法有点讲究,尤其是Redis和MyBatis的配置。我贴一份关键配置:

spring: datasource: url: jdbc:mysql://localhost:3306/admin_system?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 password: database: 0 timeout: 3000ms lettuce: pool: max-active: 8 max-idle: 8 min-idle: 0 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.admin.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case这个配置强烈建议打开,否则数据库的create_time映射到Java实体createTime会全部变成null。log-impl设置为StdOutImpl,开发阶段能直接在控制台打印SQL语句,排查问题非常方便;但是上线时记得关掉或改成Slf4jImpl,不然日志里全是被打印的SQL,影响性能也吵得慌。

另外,数据库连接串里serverTimezone=Asia/Shanghai这个参数必须加。MySQL 8.0驱动默认时区读取可能和系统不一致,不加的话查询时间字段会差8个小时,这种问题排查起来非常隐蔽。

2.3 统一返回结构和全局异常处理

后台管理系统,尤其是前后端分离模式下,接口返回格式必须统一。我习惯定义Result对象,包含code、message、data三个字段。业务正常返回code为200,参数错误返回400,未登录返回401,无权限返回403,服务器内部错误返回500。

全局异常处理用@RestControllerAdvice+@ExceptionHandler。我重点处理的异常有:自定义业务异常、参数校验异常、Spring Security认证授权异常(AuthenticationException和AccessDeniedException)、兜底Exception。这些统一处理后,前端拿到结构一致的JSON,做提示逻辑就非常省心。

注意:异常处理器里要区分“已知业务异常”和“未知系统异常”,已知异常直接把业务提示返回给前端,未知异常只返回“系统繁忙,请稍后重试”,并把完整堆栈打印到服务端日志。千万别把SQL异常堆栈直接抛给前端,容易泄露表结构。

3. 认证授权链路:Spring Security + JWT + Redis怎么串起来

这是整套系统里最核心的部分,也是我花时间最多的地方。后台管理系统的安全体系,基本围绕“认证”和“授权”两个关键词展开。认证解决“你是谁”的问题,授权解决“你能干什么”的问题。

3.1 为什么选JWT + Spring Security而不是Session

传统Session方案在分布式部署下需要额外的Session共享机制,而JWT是无状态的,服务端不保存用户会话信息,JWT令牌里直接携带用户ID、用户名、过期时间等关键信息,后端验签通过即可信任。当然,JWT有个老生常谈的缺点:无法主动失效。比如管理员把某个用户禁用了,但这个用户手里的JWT在过期之前依然有效。

解决这个问题,我引入了Redis。方案是这样的:用户登录成功后,生成JWT返回给前端,同时在Redis里以login:token:{userId}为key存储这个token,设置和JWT一样的过期时间。每次请求经过过滤器时,先校验JWT签名和过期时间,再检查Redis里是否存在这个token,如果Redis里没有,说明用户已经被强制下线或token已被删除,直接返回401。这样既保留了JWT的无状态优点,又规避了无法主动失效的缺陷。

3.2 Spring Security核心配置详解

Spring Security的配置核心是SecurityFilterChain。我把关键配置拆出来看一下:

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers("/api/auth/login", "/api/auth/captcha", "/error").permitAll() .antMatchers("/api/admin/**").hasRole("ADMIN") .antMatchers("/api/order/**").hasAnyRole("ADMIN", "OPERATOR") .anyRequest().authenticated() .and() .exceptionHandling() .authenticationEntryPoint(customAuthenticationEntryPoint) .accessDeniedHandler(customAccessDeniedHandler) .and() .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }

几个关键点展开说一下:

第一,csrf().disable()。如果做前后端分离且使用JWT,CSRF防护基本可以关闭,因为JWT不依赖Cookie,CSRF攻击的主要载体失效了。

第二,SessionCreationPolicy.STATELESS。告诉Spring Security不要创建HttpSession,所有请求都靠JWT令牌识别,这是无状态认证的核心配置。

第三,过滤器的位置。我的jwtAuthenticationFilter必须加在UsernamePasswordAuthenticationFilter之前,这样请求进来的第一时间就做token解析,而不是先走表单登录逻辑。这个顺序错了,整个认证链路就乱了。

第四,antMatchers的授权规则是“先声明优先”的,所以更具体的路径要放前面,越通用的放后面。我把登录接口和图形验证码接口放最前面允许匿名访问,然后把admin开头和order开头的接口按角色区分,最后兜底anyRequest().authenticated()

3.3 JWT令牌生成与解析实现细节

JWT工具类的设计也有讲究。我用jjwt 0.9.1实现,核心逻辑包括生成token和解析token两部分:

public String createToken(Long userId, String username, String roleCode) { Date now = new Date(); Date expireDate = new Date(now.getTime() + expireTime); return Jwts.builder() .setHeaderParam("typ", "JWT") .setSubject(String.valueOf(userId)) .claim("username", username) .claim("role", roleCode) .setIssuedAt(now) .setExpiration(expireDate) .signWith(SignatureAlgorithm.HS512, secretKey) .compact(); } public Claims parseToken(String token) { return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token) .getBody(); }

私钥secretKey不能写死在代码里,要放在配置文件中,并且长度必须足够长。HS512算法要求密钥至少64字节,否则启动时会报弱密钥异常。这个问题我遇到过,当时图省事随手写了个短字符串,结果服务跑不起来,排查了半天才反应过来是密钥长度问题。

JWT过滤器中的核心逻辑是:从请求头Authorization中拿到Bearer xxx字符串,截取token部分,先解析Claims,再查询Redis中的token与当前token是否一致,然后把用户信息和权限列表封装成UsernamePasswordAuthenticationToken,放到SecurityContextHolder中。这一步做好后,业务接口里直接用@AuthenticationPrincipal就能拿当前登录用户信息,非常方便。

3.4 权限控制落地的两种方式

第一种是Spring Security内置的注解,比如@PreAuthorize("hasRole('ADMIN')"),在Controller方法上标注,需要开启@EnableGlobalMethodSecurity(prePostEnabled = true)。适合静态、固定的权限校验。

第二种是动态权限校验,适合菜单和按钮权限经常变化的后台。我把所有接口和权限编码的映射关系存在数据库里,在JWT过滤器之后再加一个权限过滤器,从当前用户角色查询出所有权限编码集合,再与当前请求所需权限对比,不匹配就抛出403。MyBatis在这个环节的用途非常明显:动态SQL查询用户权限列表、按角色关联菜单树,这种场景用XML写SQL更直观灵活。

4. 数据访问层设计:MyBatis的使用细节与性能优化

后台管理系统的大部分工作都是数据操作。MyBatis在这里面的地位很重,但用好它需要掌握不少细节。

4.1 数据库表结构设计要点

权限模型我用的是标准RBAC:用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。用户表和角色表是多对多,角色表和菜单表是多对多。这样设计的好处是灵活性极高,后续新增菜单、调整角色权限都不需要改表结构。

关键表结构大致如下:

  • sys_user:id、username、password(加密存储)、nickname、avatar、status、create_time
  • sys_role:id、role_name、role_code、description
  • sys_menu:id、parent_id(树形结构)、menu_name、menu_type(目录/菜单/按钮)、path、permission_code
  • sys_user_role:user_id、role_id
  • sys_role_menu:role_id、menu_id

密码存储绝对不能明文。我用的BCrypt加密,Spring Security自带BCryptPasswordEncoder,每次加密结果都带随机盐,同一密码两次加密结果不同,安全性很高。

4.2 MyBatis XML动态SQL的实战姿势

MyBatis最强大的地方在于动态SQL。我做用户列表的分页条件查询时,用到了<if><where><foreach>标签:

<select id="selectUserPage" resultType="com.example.admin.entity.User"> SELECT u.*, r.role_name FROM sys_user u LEFT JOIN sys_user_role ur ON u.id = ur.user_id LEFT JOIN sys_role r ON ur.role_id = r.id <where> <if test="username != null and username != ''"> AND u.username LIKE CONCAT('%', #{username}, '%') </if> <if test="status != null"> AND u.status = #{status} </if> <if test="startTime != null"> AND u.create_time &gt;= #{startTime} </if> <if test="endTime != null"> AND u.create_time &lt;= #{endTime} </if> </where> ORDER BY u.create_time DESC </select>

注意<where>标签会自动去掉多余的AND/OR,这个设计非常聪明。还有比较符号在XML里不能直接写小于号<,要用&lt;转义,不然XML解析直接报错,这是新手特别容易踩的坑。

4.3 分页查询的正确打开方式

分页我用的是PageHelper,这个插件在MyBatis生态里很成熟,使用非常简单:

PageHelper.startPage(pageNum, pageSize); List<User> userList = userMapper.selectUserList(userQuery); PageInfo<User> pageInfo = new PageInfo<>(userList);

PageHelper.startPage之后紧跟的第一条查询会被自动加上LIMIT,然后用PageInfo包装就能拿到总记录数、总页数、当前页数等分页信息。但这里有个大坑:startPage必须紧挨着查询语句,中间不能夹带其他SQL操作,否则分页会应用到错误的查询上,导致数据错乱。我在项目里专门封装了一个PageUtils工具类,统一处理分页参数的转换和分页结果的构建,避免业务代码里直接散落PageHelper调用。

4.4 MyBatis缓存那些事

MyBatis默认开启一级缓存,也就是SqlSession级别的缓存。在Spring集成环境下,每次Mapper调用都是一个新的SqlSession,事务结束后SqlSession关闭,所以一级缓存的作用范围非常有限,基本可以忽略。二级缓存是Mapper级别的,默认关闭,我劝你别轻易开,尤其在做多表关联查询时,一旦某个表的数据变更,缓存依赖的刷新机制很难保证一致性,很容易出现脏读。

如果确实需要缓存,我更推荐用Redis做应用层缓存。比如数据字典、角色权限列表这种热点数据,查询时先查Redis,没有再查数据库,然后写回Redis并设置过期时间。MyBatis负责从数据库拿数据,Redis负责扛住高频访问,两者各干各的,互不干扰,逻辑清晰。

5. 常见问题与排查技巧实录

最后把这套系统开发过程中遇到的典型问题汇总一下,这些问题非常典型,覆盖率很高,遇到直接查表。

5.1 问题速查表

现象根本原因解决思路
启动后访问任意接口返回401JWT过滤器没有放行登录接口,或者过滤器顺序错误检查SecurityConfig中permitAll配置和addFilterBefore位置
登录成功后再次请求仍401Redis中token过期,或Redis未配置序列化导致token存储格式不对检查Redis中token是否存在,验证token规则是否统一
查询时间字段比数据库少8小时JDBC连接串缺少serverTimezone参数连接串增加serverTimezone=Asia/Shanghai
数据库字段create_time查询为nullmybatis的map-underscore-to-camel-case未配置开启下划线转驼峰配置
返回给前端的数据中密码字段泄露实体类没有加@JsonIgnore或返回对象直接暴露实体在密码字段加@JsonIgnore,或用VO对象过滤敏感字段
Redis保存中文乱码RedisTemplate默认JdkSerializationRedisSerializer序列化导致统一配置StringRedisSerializer和Jackson2JsonRedisSerializer
限流或并发下用户信息串了SecurityContextHolder中用户信息使用ThreadLocal,但线程池复用导致上下文串线在跨线程调用时手动传递用户信息,或禁止在子线程使用SecurityContext
修改角色权限后用户不生效权限缓存Redis未及时清理修改角色菜单后同步删除对应用户的权限缓存key
分页查询数据重复或漏数据PageHelper startPage与不相关查询混用强制约定startPage紧跟目标查询,不使用则立刻clearPage

5.2 一个印象深刻的Redis序列化坑

Redis部分最让我记忆深刻的是RedisTemplate的序列化问题。默认配置下,RedisTemplate使用JdkSerializationRedisSerializer,存入Redis的key会变成\xAC\xED\x00\x05t\x00...这样的二进制前缀,可读性极差,而且key的实际存储格式和肉眼看到的不一致。上次排查一个“Redis中明明设置了token却查不到”的问题,打开Redis Desktop Manager一看,key全是乱码,当时就愣住了。

后来统一配置RedisTemplate:

@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class); template.setKeySerializer(stringRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setValueSerializer(jacksonSerializer); template.setHashValueSerializer(jacksonSerializer); template.afterPropertiesSet(); return template; }

key统一用String序列化,value用JSON,这样Redis中的数据可读性高,排查问题也方便。这个配置看起来不起眼,但重要性极高,强烈建议每个项目一开始就配好。

5.3 权限改完不生效的缓存陷阱

还有一个让我印象深刻的问题:角色权限修改后,用户重新登录依然看到旧菜单。排查后发现问题出在权限缓存。我把用户的权限集缓存到了Redis,key是user:permissions:{userId},但修改角色菜单后只更新了数据库,没清理Redis里的权限缓存,导致用户拿到的还是旧权限。这个问题的解决方式是在角色菜单变更的Service里,主动删除相关用户的所有权限缓存key。如果你的用户量大,删除操作可以采用批量扫描删除,或者给权限缓存设置较短过期时间,比如30分钟,保证最终一致性。

提示:后台管理系统的缓存设计,建议遵循“读取多、变化少”的数据才放缓存的原则。权限数据、数据字典、系统参数适合Redis缓存,业务单据类数据除非有明确的性能瓶颈,否则不要盲目套缓存,缓存一致性带来的复杂度有时候比性能收益还大。

写在最后的一点个人体会

这套系统从开发到上线,前后迭代了差不多两个月。期间最大的感触是,后台管理系统并不复杂,但涉及的知识面非常宽:Spring Boot的自动配置和启动流程、Spring Security的过滤器链、MyBatis的动态SQL和缓存机制、Redis的数据结构和序列化方案,任何一个环节理解不到位,排查问题的时候都会多花几倍时间。

如果非要给正在做类似项目的人一些建议,我想说三件事。第一,认证授权链路一定要在项目第一天就搭好,后面再补会牵一发动全身;第二,统一返回结构和全局异常处理尽早设计,这决定了前后端联调的效率;第三,Redis的序列化配置和MyBatis的日志配置属于开发体验类的投入,看似不起眼,实际排查问题时能省一大半时间。

后续这套系统还可以继续扩展:登录时接入图形验证码和登录失败次数限制,操作日志用MQ异步写入,文件上传对接对象存储,定时任务集成Quartz或XXL-JOB。后台管理系统从来不是一锤子买卖,而是跟着业务需求一点点长起来的。希望这篇实战记录能帮你少走点弯路,也欢迎在落地过程中多交流。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 4:02:38

从AST到图查询:代码知识图谱的构建原理与落地实践

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

作者头像 李华
网站建设 2026/9/8 3:59:16

整蛊视频技术拆解:TTS语音合成与Android模拟来电实现

“假装打电话说丈夫感兴趣的事&#xff0c;看他会不会凑上来&#xff0c;一听是自己喜欢的事瞬间精神了”——如果你最近刷短视频&#xff0c;大概率刷到过这类整蛊内容。表面看是一个家庭搞笑段子&#xff0c;但拆开看&#xff0c;这里面藏着三条相互叠加的技术链路&#xff1…

作者头像 李华
网站建设 2026/9/8 3:59:14

2026国内建陶行业口碑较好的品牌有哪些?家装瓷砖十大品牌一览

瓷砖是建筑装饰工程中常用的饰面材料&#xff0c;广泛应用于室内外墙面、地面铺装场景。国内建筑陶瓷产业主要集中于佛山地区&#xff0c;行业内企业数量众多&#xff0c;产品品类、设计款式、应用场景各有不同。本文客观整理国内十家建陶企业基础资料&#xff0c;仅做行业信息…

作者头像 李华
网站建设 2026/9/8 3:59:03

MCP协议底层机制与JSON-RPC 2.0完整生命周期解析

先说个有意思的现象&#xff1a;很多人一搜“MCP 生命周期”&#xff0c;结果出来一半是 Vue 生命周期&#xff0c;一半是 Rust 的所有权生命周期&#xff0c;反而把真正想问的 MCP 协议本身给淹没了。MCP&#xff08;Model Context Protocol&#xff09;确实是现在 AI 工具链里…

作者头像 李华
网站建设 2026/9/8 3:58:05

数控铣削开粗加工全流程指南:刀具参数与刀路策略精讲

做机加工和数控编程这些年&#xff0c;我最大的体会是&#xff1a;开粗这个环节虽然听起来粗犷&#xff0c;但恰恰是整个零件加工中最容易出问题、也最考验工艺功底的部分。零件能不能按期交、刀具寿命高不高、精加工是否稳定&#xff0c;往往在开粗阶段就已经决定了。很多初学…

作者头像 李华
网站建设 2026/9/8 3:56:59

微信小程序纯前端GIF生成:canvas帧采集与编码器实践

简介&#xff1a;面向微信小程序开发者与前端爱好者的 GIF 动画制作项目&#xff0c;依托小程序端实现动图编辑、预览与一键分享&#xff0c;解决了移动端轻量化制作 GIF 动画的需求&#xff0c;无需依赖笨重的桌面软件&#xff0c;门槛较低。压缩包共 69 个文件、约 1.85MB&am…

作者头像 李华