手拉手后端Springboot整合JWT
环境介绍
技术栈 | springboot+mybatis-plus+mysql+java-jwt |
软件 | 版本 |
mysql | 8 |
IDEA | IntelliJ IDEA 2022.2.1 |
JDK | 1.8 |
Spring Boot | 2.7.13 |
mybatis-plus | 3.5.3.2 |
Json Web令牌简称JWT
Token是在服务端产生的一串字符串是客户端访问资源接口(AP)时所需要的资源凭证。
Token认证
Token是在服务端产生的一串字符串是客户端访问资源接口(AP)时所需要的资源凭证。
Token认证流程
1、客户端使用用户名跟密码请求登录,服务端收到请求,验证用户名与密码验证成功后,服务端会签发一个 token并把这个 token发送给客户端,客户端收到 token后,会把它存储起来,比如放在cookie里或者localStorage里
2、客户端每次向服务端请求资源的时候需要带着服务端签发的 token
3、服务端收到请求,然后去验证客户端请求里面带着的 token,如果验证成功就向客户端返回请求的数据
token用户认证是一种服务端无状态的认证方式,服务端不用存放token数据。
用解析 token的计算时间换取 session的存储空间,从而减服务器的力,减少频繁的查询数据库
token完全由应用管理,所以它可以避开同源策略
JWT的使用
JSON Web Token(简称JWT)是一个 token的具体实现方式,是目前最流行的跨域认证解决方案。JWT的原理是:服务器认证以后,生成一个JSON对象,发回给用户。
{
“name” :”张三”,
“time”:”2022年10月10日”
}
用户与服务端通信时,都要发回该JSON对象。服务器完全只靠这个对象认定用户身份。
为防止用户篡改数据,服务器在生成对象时,会加上签名
JWT由三个部分组成:Header(头部)、Payload(负载)、Signature(签名)
Header.Payload.Signature
官方描述
Header
JWT头是一个描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ属性表示令牌的类型,JWT令牌统一写为JWT。最后,使用Base64 URL算法将上述JSON对象转换为字符串保存
{
"alg": "HS256",
"typ": "JWT"
}
Payload
有效载荷部分,是JWT的主体内容部分,也是一个JSON对象,包含需要传递的数据。 JWT指定七个默认字段供选择
iss:发行人
exp:到期时间
sub:主题
aud:用户
nbf:在此之前不可用
iat:发布时间
jti:JWT ID用于标识该JWT
Signature
签名哈希部分是对上面两部分数据签名,需要使用base64编码后的header和payload数据,通过指定的算法生成哈希,以确保数据不会被篡改。
加入依赖
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.3.0</version>
</dependency>
数据库
实体类
package com.example.domain;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;/*** * @TableName user*/
@TableName(value ="user")
@Data
public class User implements Serializable {/*** 用户id*/@TableId(type = IdType.AUTO)private Integer uid;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 盐值*/private String salt;/*** 电话号码*/private String phone;/*** 电子邮箱*/private String email;/*** 性别:0-女,1-男*/private Integer gender;/*** 头像*/private String avatar;/*** 是否删除:0-未删除,1-已删除*/private Integer isDelete;/*** 日志-创建人*/private String createdUser;/*** 日志-创建时间*/private Date createdTime;/*** 日志-最后修改执行人*/private String modifiedUser;/*** 日志-最后修改时间*/private Date modifiedTime;@TableField(exist = false)private static final long serialVersionUID = 1L;
}
mapper(dao)
package com.example.mapper;import com.example.domain.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;import java.util.Date;
@Mapper
public interface UserMapper extends BaseMapper<User> {/*** 插入用户数据* @param user 用户数据* @return 受影响的行数*/int insert(User user);/*** 根据用户名查询用户是否存在* @param username* @return 成功返回单个用户数据,否返回null*/User findByUserName(@Param("username") String username);/*** 根据uid查询* @param uid* @return*/User findByUid(@Param("uid") Integer uid);/*** 更新用户个人资料信息* @param user* @return*/Integer updateUserInfoByUid(User user);/*** 根据用户id修改密码* @param uid* @return password=?,modified_user=?,modified_time=?*/Integer updatePasswordByUid(@Param("uid")Integer uid,@Param("password")String password,@Param("modified_user")String modified_user,@Param("modified_time") Date modified_time);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper"><resultMap id="UserPojoMap" type="com.example.domain.User"><id column="uid" property="uid"></id><result column="is_delete" property="isDelete"></result><result column="created_user" property="createdUser"></result><result column="created_time" property="createdTime"></result><result column="modified_user" property="modifiedUser"></result><result column="modified_time" property="modifiedTime"></result></resultMap><!-- useGeneratedKeys="true" 开启主键自增 keyProperty="uid" 指定uid字段--><insert id="insert" useGeneratedKeys="true" keyProperty="uid">insert into user(username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time)values(#{username},#{password},#{salt},#{phone},#{email},#{gender},#{avatar},#{isDelete},#{createdUser},#{createdTime},#{modifiedUser},#{modifiedTime})</insert><select id="findByUserName" resultMap="UserPojoMap">select * from user where username=#{username}</select><select id="findByUid" resultMap="UserPojoMap">select * from user where uid=#{uid}</select><update id="updatePasswordByUid">update user set password=#{password},modified_user=#{modified_user},modified_time=#{modified_time} where uid=#{uid}</update><update id="updateUserInfoByUid">update user set<if test="phone!=null">phone=#{phone},</if><if test="email!=null">email=#{email},</if><if test="gender!=null">gender=#{gender},</if>modified_user=#{modifiedUser}, modified_time=#{modifiedTime} where uid=#{uid}</update><sql id="Base_Column_List">uid,username,password,salt,phone,email,gender,avatar,is_delete,created_user,created_time,modified_user,modified_time</sql>
</mapper>
service
package com.example.service;import com.example.domain.User;
import com.baomidou.mybatisplus.extension.service.IService;
public interface UserService extends IService<User> {/*** 用户注册方法* @param user*/void reg(User user);/*** 用户登入方法* @param username* @param password* @return*/User login(String username,String password);/*** 根据uid查询* @param uid* @return User*//**** @param uid* @param username* @param oldPassword* @param newPassword*/void changePassword(Integer uid,String username,String oldPassword,String newPassword);/*** 通过uid获取用户数据* @param uid* @return*/User getUserInfoByUid(Integer uid);/*** 修改用户信息* @param user* @return*/void changeUserInfo(Integer uid,String username,User user);}
ServiceImpl
package com.example.service.impl;import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.domain.User;
import com.example.service.UserService;
import com.example.mapper.UserMapper;
import com.example.service.exception.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.util.Date;
import java.util.UUID;
@Service
@DS("wms")
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements UserService{@Autowiredprivate UserMapper userDao;@Overridepublic void reg(User user) {//判断用户是否被注册过String username = user.getUsername();User byUserName = userDao.findByUserName(username);if (byUserName == null){//密码的加密处理:MD5算法//盐值+password+盐值String oldPassword =user.getPassword();//获取盐值String salt = UUID.randomUUID().toString().toUpperCase();//保存盐值user.setSalt(salt);String newPassword = getMD5Password(oldPassword,salt);user.setPassword(newPassword);//用户注册// is_delete INT COMMENT '是否删除:0-未删除,1-已删除',// created_user VARCHAR(20) COMMENT '日志-创建人',// created_time DATETIME COMMENT '日志-创建时间',// modified_user VARCHAR(20) COMMENT '日志-最后修改执行人',// modified_time DATETIME COMMENT '日志-最后修改时间',Date nowTime=new Date();user.setIsDelete(0);user.setCreatedUser(user.getUsername());user.setCreatedTime(nowTime);user.setModifiedUser(user.getUsername());user.setModifiedTime(nowTime);Integer rows = userDao.insert(user);if (rows == 0 ){throw new InsertException("注册失败(未知失败)请重新注册");};}else {throw new UsernameOccupyException("用户名被占用");}}@Overridepublic User login(String username,String password) {User UserLogin = userDao.findByUserName(username);//盐值认证String md5Password = getMD5Password(password, UserLogin.getSalt());if (UserLogin == null){throw new UserNullException("用户不存在");}//检测密码是否匹配if (!UserLogin.getPassword().equals(md5Password)){throw new PasswordNotMatchException("用户密码错误");}//判断is_delete字段值是否为1表示被标记为删除if (UserLogin.getIsDelete() == 1){throw new UserNullException("用户已被删除");}User user = new User();user.setUid(UserLogin.getUid());user.setUsername(UserLogin.getUsername());user.setAvatar(UserLogin.getAvatar());//返回用户数据,是为了辅助页面return user;}@Overridepublic void changePassword(Integer uid, String username, String oldPassword, String newPassword) {User user = userDao.findByUid(uid);if (user ==null){throw new UserNullException("用户不存在");}if (user.getIsDelete() ==1){throw new UserDeletedException("用户不存在或已被删除");}//密码对比String md5Password = getMD5Password(oldPassword, user.getSalt());if (!user.getPassword().equals(md5Password)){throw new PasswordNotMatchException("原密码错误");}//更新passwordString newPasswordMd5 = getMD5Password(newPassword, user.getSalt());Integer rows = userDao.updatePasswordByUid(uid, newPasswordMd5,username,new Date());if (rows !=1){throw new PasswordUpdateException("修改密码未知异常");}}//根据id获取userInfo@Overridepublic User getUserInfoByUid(Integer uid) {User result = userDao.findByUid(uid);if (result == null || result.getIsDelete() ==1){throw new UserNullException("用户不存在");}User user = new User();user.setUsername(result.getUsername());user.setUid(result.getUid());user.setPhone(result.getPhone());user.setEmail(result.getEmail());user.setGender(result.getGender());return user;}//修改用户信息@Overridepublic void changeUserInfo(Integer uid, String username, User user) {User result = userDao.findByUid(uid);if (result == null || result.getIsDelete() ==1){throw new UserNullException("用户不存在");}user.setUid(uid);user.setModifiedUser(username);user.setModifiedTime(new Date());Integer rows = userDao.updateUserInfoByUid(user);if (rows != 1){throw new InfoUpdateException("修改用户信息未知异常");}}//password加密方法private String getMD5Password(String password,String salt){for (int i =0;i<5;i++){password = DigestUtils.md5DigestAsHex((salt + password + salt).getBytes()).toUpperCase();}//返回加密之后的密码return password;}
}
JWT工具类
public class JWTUtil {private static final String TOKENKey="qgs12345";/*** 生成token* @param map* @return 返回token*/public static String getToken(Map<String,String> map){Calendar instance = Calendar.getInstance();instance.add(Calendar.DATE,7);//7天过期//添加payloadJWTCreator.Builder builder = JWT.create();map.forEach((k,v)->{builder.withClaim(k,v);});builder.withExpiresAt(instance.getTime());//设置令牌过期时间//生成并返回tokenreturn builder.sign(Algorithm.HMAC256(TOKENKey)).toString();}/*** 验证token* @param token*/public static void verify(String token){JWT.require(Algorithm.HMAC256(TOKENKey)).build().verify(token);}/*** 获取token中payload* @param token* @return*/public static DecodedJWT getTokenInfo(String token){return JWT.require(Algorithm.HMAC256(TOKENKey)).build().verify(token);}
}
UserController
@RestController
@RequestMapping("/users")
@CrossOrigin //表示都允许跨域访问
public class UserController extends BaseController{@Autowiredprivate UserService userModuleService;@RequestMapping("/login")public Map<String,Object> login(String username, String password){Map<String,Object> map =new HashMap<>();try {User data = userModuleService.login(username, password);Map<String,String> payload =new HashMap<>();payload.put("username",data.getUsername());//生成JWT令牌String token =JWTUtil.getToken(payload);map.put("state",true);map.put("msg","登入成功");map.put("token",token);}catch (Exception e){map.put("state",false);map.put("msg","登入失败");}return map;}}
登录,产生token
验证token方式一
认证代码中写token认证流程,过多的认证请求会导致代码冗余
@RestController
@RequestMapping("/users")
@CrossOrigin //表示都允许跨域访问
public class UserController extends BaseController{@Autowiredprivate UserService userModuleService;@RequestMapping("/login")//验证token@RequestMapping("/loginVerify")public Map<String,Object> loginVerify(String token){System.out.println(token);Map<String,Object> map =new HashMap<>();try {//生成JWT令牌JWTUtil.verify(token);map.put("state",true);map.put("msg","验证成功");}catch (Exception e){map.put("state",true);map.put("msg","验证失败");}return map;}}
验证token方式二 JWT拦截器
抛弃方式一的代码冗余
public class JWTInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Map<String,Object> map =new HashMap<>();// 获取请求头中的JWT令牌String token = request.getHeader("token");// 进行JWT令牌的验证逻辑try {//生成JWT令牌JWTUtil.verify(token);return true;//放行请求}catch (Exception e){map.put("state",false);map.put("msg","token验证失败");//map转jsonString msg =new ObjectMapper().writeValueAsString(map);response.setContentType("application/json;charset=utf-8");response.getWriter().println(msg);}return false;}
}
@Component
public class InterceptorConfig implements WebMvcConfigurer {public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new JWTInterceptor()).addPathPatterns("/users//loginVerify")//拦截路径,根据实际情况进行配置.excludePathPatterns("/users/login","/reg") ;//放行路径}}
简化loginVerify后
//验证token
@RequestMapping("/loginVerify")
public Map<String,Object> loginVerify(HttpServletRequest request){Map<String,Object> map =new HashMap<>();String token =request.getHeader("token");System.out.println(token);map.put("state",true);map.put("msg","验证成功");return map;
}
请求头携带token效果
相关文章:

手拉手后端Springboot整合JWT
环境介绍 技术栈 springbootmybatis-plusmysqljava-jwt 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 Json Web令牌简称JWT Token是在服务端产生的一串字符串是客户端访问资源接口(AP)时所需要的资源凭证。…...

小狐狸GPT付费2.4.9 去除授权弹窗版
后台安装步骤: 1、在宝塔新建个站点,php版本使用7.2 、 7.3 或 7.4,把压缩包上传到站点根目录,运行目录设置为/public 2、导入数据库文件,数据库文件是 /db.sql 3、修改数据库连接配置,配置文件是/.env 4、…...

Scrapy爬虫中合理使用time.sleep和Request
概述 在Scrapy爬虫中,我们需要深入分析time.sleep和Request对象对并发请求的影响。time.sleep函数用于在发起请求之前等待一段时间,而Request对象用于发送HTTP请求。我们必须仔细考虑这些操作对其他并发请求的潜在影响,以及在异步情况下可能…...

在Spring Cloud中使用Ribbon完成一个简单的负载均衡demo
Spring Cloud系列断更了有一段时间了,这段时间最近都在忙着项目上的事,天天修复bug以及调整需求,反正各种操劳,了解业务需求,然后开发相关功能,很久都没碰Spring Cloud系列的相关文章了,最近回头…...
mysql-5.6.16的内存泄漏问题
一、背景 有一台物理机上一个版本为5.6.16的从库出现了内存的增高,观测其日志可以发现,这台数据库已经oom很多次了,并且stop slave的时候会卡住非常长的时间才能停止 二、根本原因 上述的现象可以看到是一个明显的内存泄漏现象,…...

相机内参标定理论篇------相机模型选择
相机种类: 当拿到一款需要标定内参的相机时,第一个问题就是选择那种的相机模型。工程上相机类型的划分并不是十分严格,一般来说根据相机FOV可以把相机大概分为以下几类: 长焦相机:< 标准相机:~&…...

java设计模式学习之【状态模式】
文章目录 引言状态模式简介定义与用途实现方式 使用场景优势与劣势在Spring框架中的应用状态示例代码地址 引言 设想你正在使用一个在线视频播放器观看电影。随着你的互动,播放器可能处于不同的状态:播放、暂停、缓冲或结束。每个状态下,播放…...
使用aspose.Words更新表格列宽
public static bool UpdateTableStyle(string filePath) { try { Document doc new Document(); //样式 Style style doc.Styles.Add(StyleType.Paragraph, "cellStyle"); style.Font.Name "simsun"; PageSetup pageSet doc.Sections[0].PageSetup; pa…...

pycharm 工具栏不见了
新版pycharm后, 菜单栏和工具栏不见了 目录 我发现的解决方法: 其他旧版的解决方法: 我发现的解决方法: 其他旧版的解决方法: 另外,一些使用pycharm的新手可能会由于不熟悉软件的功能而误操作ÿ…...

图灵日记之java奇妙历险记--类和对象
目录 类的定义和使用类的定义格式 类的实例化类和对象的说明 this引用this引用的特性 对象的构造及初始化就地初始化构造方法 封装包导入包中的类自定义包 static成员static修饰成员变量static修饰成员方法 代码块代码块概念及分类构造代码块静态代码块 匿名对象 类的定义和使用…...
Kotlin 枚举类
使用 enum 修饰符;每个枚举常量都是一个对象,枚举常量以逗号分隔 // 枚举类 enum class Direction {NORTH, SOUTH, WEST, EAST }// 每一个枚举都是枚举类的实例,所以可以这样初始化 enum class Color(val rgb: Int) {RED(0xFF0000),GREEN(0x…...

可运营的Leadshop开源商城小程序源码 +H5公众号+带视频教程
源码简介 Leadshop是一款出色的开源电商系统,具备轻量级、高性能的特点,并提供持续更新和迭代服务。该系统采用前后端分离架构(uniappyii2.0),以实现最佳用户体验为目标。 前端部分采用了uni-app、ES6、Vue、Vuex、V…...
Qt底层机制之对象树总结
Qt对象树是Qt框架中的一个重要概念,它用于管理对象之间的关系和生命周期。除了常规的对象树结构,Qt还提供了一些特殊的用法来扩展对象树的功能和灵活性。 1. 父子关系:Qt对象树通过设置父对象来建立父子关系。父对象负责管理子对象的内存分配和释放。这种关系可以通过`setP…...
QT C++ TCP Socket 请求心知天气
0.0 相关连接代码部分头文件具体实现 相关连接 心知天气官方天气图标 心知天气官网 代码部分 头文件 #include <QtNetwork> #include <QNetworkAccessManager> #include <QDebug> #include <QJsonValue> #include <QJsonArray> #include &l…...
双向链表的实现及头尾插入删除
双链表的增删查改 一.双向链表的初始化二.创建返回链表的头结点三.双向链表销毁四. 双向链表打印五.双向链表尾插六. 双向链表尾删七. 双向链表头插八.双向链表头删九.双向链表的查找十.双向链表在pos的前面进行插入十一. 双向链表删除pos位置的节点 一.双向链表的初始化 Lis…...

C语言—每日选择题—Day62
第一题 1. 在使用标准C库时,下面哪个选项使用只读模式打开文件? A:fopen("foo.txt", "r") B:fopen("foo.txt", "r") C:fopen("foo.txt", "w") D…...

基于 Sentry 的前端监控系统搭建(Linux)
一、前言 随着技术这几年的发展与沉淀,线上数据指标监控也变得尤为重要,研发人员和运营人员需要对线上的产品指标有所感知,同时风险也需要及时暴露,很多公司开始自建监控系统,但对于一些定制化要求不是特别高的团队&a…...

【C++入门到精通】Lock_guard与Unique_lock C++11 [ C++入门 ]
阅读导航 引言一、RAII机制1. 概念2. 原理3. 优点 二、Lock_guard1. 官方文档2. 概念3. 底层类模版4. 使用示例 三、Unique_lock1. 官方文档2. 概念及底层3. 使用示例 四、总结温馨提示 引言 在C11标准中,为了更方便地使用互斥锁(Mutex)来保…...

电路设计(8)——计时器的multism仿真
1.功能设计 这是一个计时电路,在秒脉冲的驱动下,计时器开始累加,6个数码管分别显示计时的 时:分:秒。 仿真图如下所示: 左边的运放构成了振荡电路,可以产生脉冲波。这个脉冲波给计时电路提供基准…...

Jmeter测试实践:文件下载接口
一 Jmeter步骤 1.打开jmeter4.0,新建测试计划,添加线程组。根据实际情况配置线程属性。 2.添加HTTP请求。根据接口文档进行配置。 Basic部分修改如下,Advanced部分保持默认。这里的参数id是文件的id,我进行了参数化,…...

HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...

STM32F4基本定时器使用和原理详解
STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...

Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...

成都鼎讯硬核科技!雷达目标与干扰模拟器,以卓越性能制胜电磁频谱战
在现代战争中,电磁频谱已成为继陆、海、空、天之后的 “第五维战场”,雷达作为电磁频谱领域的关键装备,其干扰与抗干扰能力的较量,直接影响着战争的胜负走向。由成都鼎讯科技匠心打造的雷达目标与干扰模拟器,凭借数字射…...

初学 pytest 记录
安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

实战三:开发网页端界面完成黑白视频转为彩色视频
一、需求描述 设计一个简单的视频上色应用,用户可以通过网页界面上传黑白视频,系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观,不需要了解技术细节。 效果图 二、实现思路 总体思路: 用户通过Gradio界面上…...
comfyui 工作流中 图生视频 如何增加视频的长度到5秒
comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗? 在ComfyUI中实现图生视频并延长到5秒,需要结合多个扩展和技巧。以下是完整解决方案: 核心工作流配置(24fps下5秒120帧) #mermaid-svg-yP…...