change package name,fixes error
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.onekeycall.videotablet;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class VideoTabletApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(VideoTabletApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.onekeycall.videotablet.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
|
||||
@Configuration
|
||||
public class CommonConfig {
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package com.onekeycall.videotablet.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// Key序列化为字符串
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
// Value序列化为JSON
|
||||
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
|
||||
// 对于Hash结构,根据需要配置
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
return template;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.onekeycall.videotablet.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final UserDetailsService userDetailsService;
|
||||
|
||||
@Autowired
|
||||
public SecurityConfig(UserDetailsService userDetailsService) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/public/**").permitAll()
|
||||
.requestMatchers("/admin/**").hasRole("ADMIN")
|
||||
.anyRequest().authenticated()
|
||||
);
|
||||
return http.build();
|
||||
}
|
||||
|
||||
// 添加AuthenticationManager bean定义
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
|
||||
return authConfig.getAuthenticationManager();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.onekeycall.videotablet.controller;
|
||||
|
||||
import com.onekeycall.videotablet.result.Result;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class HelloController {
|
||||
//引入 redis
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@GetMapping("/public/hello")
|
||||
public Result getMethodName() {
|
||||
return Result.ok().message("Welcome to Yijiantong");
|
||||
}
|
||||
|
||||
/**
|
||||
* 存储
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/public/set")
|
||||
public Result setRedis(@RequestParam(value = "username") String username) {
|
||||
//存储 key-value 键值对: "username"-"jaychou"
|
||||
stringRedisTemplate.opsForValue().set("username", username);
|
||||
return Result.ok().message("redis 存储成功!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/public/get")
|
||||
public Result getRedis(@RequestParam(value = "username") String username) {
|
||||
//通过 key 值读取 value
|
||||
String result = stringRedisTemplate.opsForValue().get(username);
|
||||
return Result.ok().data("username", result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
package com.onekeycall.videotablet.controller;
|
||||
|
||||
import com.onekeycall.videotablet.entity.User;
|
||||
import com.onekeycall.videotablet.service.UserService;
|
||||
import com.onekeycall.videotablet.utils.TextUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@RestController
|
||||
public class LoginController {
|
||||
|
||||
private final UserService userService;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
public LoginController(UserService userService, AuthenticationManager authenticationManager) {
|
||||
this.userService = userService;
|
||||
this.authenticationManager = authenticationManager;
|
||||
}
|
||||
|
||||
@PostMapping("/public/register")
|
||||
public ResponseEntity<?> registerUser(@RequestBody RegisterRequest registerRequest) {
|
||||
try {
|
||||
userService.registerUser(registerRequest.getUsername(), registerRequest.getPassword());
|
||||
return new ResponseEntity<>("User registered successfully", HttpStatus.CREATED);
|
||||
} catch (RuntimeException e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/public/login")
|
||||
public ResponseEntity<?> login() {
|
||||
// 登录逻辑由Spring Security自动处理
|
||||
return ResponseEntity.ok("Login successful");
|
||||
}
|
||||
|
||||
// 注册请求参数类
|
||||
public static class RegisterRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
// Getters and Setters
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@PostMapping("/public/registerByPhone")
|
||||
public ResponseEntity<?> registerByPhone(@RequestBody PhoneRequest request) {
|
||||
String requestVerifyKey = request.getVerifyKey();
|
||||
if (TextUtils.isEmpty(requestVerifyKey)) {
|
||||
return new ResponseEntity<>("verify key is empty", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String phone = request.getPhone();
|
||||
Map<String, Object> map = (Map<String, Object>) redisTemplate.opsForValue().get(phone);
|
||||
if (map != null) {
|
||||
String verifyKey = (String) map.get("verifyKey");
|
||||
if (!Objects.equals(verifyKey, requestVerifyKey)) {
|
||||
return new ResponseEntity<>("verify key is not same", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
String code = map.get("code").toString();
|
||||
if (!Objects.equals(code, request.getCode())) {
|
||||
return new ResponseEntity<>("code is not same", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
User user = userService.registerByPhone(request.getPhone(), request.getCode(), new Date());
|
||||
return new ResponseEntity<>(user, HttpStatus.CREATED);
|
||||
} catch (RuntimeException e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
} else {
|
||||
return new ResponseEntity<>("verify key is expired", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/public/loginByPhone")
|
||||
public ResponseEntity<?> loginByPhone(@RequestBody PhoneRequest request) {
|
||||
String requestVerifyKey = request.getVerifyKey();
|
||||
if (TextUtils.isEmpty(requestVerifyKey)) {
|
||||
return new ResponseEntity<>("verify key is empty", HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
try {
|
||||
User user = userService.loginByPhone(request.getPhone(), request.getCode());
|
||||
// 生成并返回JWT令牌(实际项目中需要实现JWT逻辑)
|
||||
|
||||
return ResponseEntity.ok("Login successful: " + user.getUsername());
|
||||
} catch (RuntimeException e) {
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static class PhoneRequest {
|
||||
private String phone;
|
||||
private String code;
|
||||
private String verifyKey;
|
||||
|
||||
// Getters and Setters
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public String getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(String code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getVerifyKey() {
|
||||
return verifyKey;
|
||||
}
|
||||
|
||||
public void setVerifyKey(String verifyKey) {
|
||||
this.verifyKey = verifyKey;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.onekeycall.videotablet.controller;
|
||||
|
||||
import com.aliyun.dysmsapi20170525.models.SendSmsResponse;
|
||||
import com.aliyun.tea.TeaException;
|
||||
import com.onekeycall.videotablet.result.Result;
|
||||
import com.onekeycall.videotablet.sms.SendSms;
|
||||
import com.onekeycall.videotablet.utils.TextUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@RestController
|
||||
public class SmsController {
|
||||
private static final int LOGIN_CODE_TTL = 5;
|
||||
|
||||
//引入 redis
|
||||
@Autowired
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
@Autowired
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@GetMapping("/public/verify_code")
|
||||
public Result getMethodName(@RequestParam("phone") String phone) {
|
||||
if (TextUtils.isEmpty(phone)) {
|
||||
return Result.error().message("phone number is empty");
|
||||
}
|
||||
Map<String, Object> map = (Map<String, Object>) redisTemplate.opsForValue().get(phone);
|
||||
if (map != null) {
|
||||
String oldCode = (String) map.get("code");
|
||||
long sentTime = (Long) map.get("sentAt");
|
||||
if (System.currentTimeMillis() - sentTime < Duration.ofMinutes(1).toMillis()) {
|
||||
return Result.error().message("code has been sent, please try again after 1 minute");
|
||||
}
|
||||
if (TextUtils.isEmpty(oldCode)) {
|
||||
String code = SendSms.generatedcode(6);
|
||||
return sendCode(phone, code, false);
|
||||
} else {
|
||||
return sendCode(phone, oldCode, true);
|
||||
}
|
||||
} else {
|
||||
String code = SendSms.generatedcode(6);
|
||||
return sendCode(phone, code, false);
|
||||
}
|
||||
}
|
||||
|
||||
private Result sendCode(String phone, String code, boolean sent) {
|
||||
try {
|
||||
com.aliyun.dysmsapi20170525.Client client = createClient();
|
||||
com.aliyun.dysmsapi20170525.models.SendSmsRequest sendSmsRequest = new com.aliyun.dysmsapi20170525.models.SendSmsRequest()
|
||||
.setSignName("同同的小站")
|
||||
.setTemplateCode("SMS_468370574")
|
||||
.setPhoneNumbers(phone)
|
||||
.setTemplateParam("{\"code\":\"" + code + "\"}");
|
||||
com.aliyun.teautil.models.RuntimeOptions runtime = new com.aliyun.teautil.models.RuntimeOptions();
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
SendSmsResponse sendSmsResponse = client.sendSmsWithOptions(sendSmsRequest, runtime);
|
||||
if ("OK".equals(sendSmsResponse.getBody().getCode())) {
|
||||
String randomString = RandomStringUtils.randomAlphanumeric(32);
|
||||
long now = System.currentTimeMillis();
|
||||
long expireAt = now + Duration.ofMinutes(LOGIN_CODE_TTL).toMillis();
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("code", code);
|
||||
map.put("sms", sendSmsResponse.getBody().getMessage());
|
||||
map.put("verifyKey", randomString);
|
||||
map.put("sentAt", now);
|
||||
map.put("expireAt", expireAt);
|
||||
//4.保存验证码到Redis,并且设置有效期5分钟
|
||||
if (!sent) {
|
||||
redisTemplate.opsForValue().set(phone, map, Duration.ofMinutes(5));
|
||||
}
|
||||
return Result.ok().data(map);
|
||||
} else {
|
||||
return Result.error().message(sendSmsResponse.getBody().getMessage());
|
||||
}
|
||||
} catch (TeaException error) {
|
||||
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
// 错误 message
|
||||
System.out.println(error.getMessage());
|
||||
// 诊断地址
|
||||
System.out.println(error.getData().get("Recommend"));
|
||||
com.aliyun.teautil.Common.assertAsString(error.message);
|
||||
return Result.error().data("sms", error.message);
|
||||
} catch (Exception _error) {
|
||||
TeaException error = new TeaException(_error.getMessage(), _error);
|
||||
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
// 错误 message
|
||||
System.out.println(error.getMessage());
|
||||
// 诊断地址
|
||||
System.out.println(error.getData().get("Recommend"));
|
||||
com.aliyun.teautil.Common.assertAsString(error.message);
|
||||
return Result.error().data("sms", error.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 使用AK&SK初始化账号Client
|
||||
*
|
||||
* @return Client
|
||||
* @throws Exception
|
||||
*/
|
||||
public com.aliyun.dysmsapi20170525.Client createClient() throws Exception {
|
||||
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
|
||||
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378657.html。
|
||||
com.aliyun.teaopenapi.models.Config config = new com.aliyun.teaopenapi.models.Config()
|
||||
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
|
||||
.setAccessKeyId("LTAI5tEVYLeg7U3bP58n3Xkj")
|
||||
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
||||
.setAccessKeySecret("bbPSlxkaGgkhUPaCOP7CANaNNY1b2p");
|
||||
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
|
||||
config.endpoint = "dysmsapi.aliyuncs.com";
|
||||
return new com.aliyun.dysmsapi20170525.Client(config);
|
||||
}
|
||||
}
|
||||
91
src/main/java/com/onekeycall/videotablet/dto/TokenPair.java
Normal file
91
src/main/java/com/onekeycall/videotablet/dto/TokenPair.java
Normal file
@@ -0,0 +1,91 @@
|
||||
package com.onekeycall.videotablet.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 双Token认证令牌对(AccessToken + RefreshToken)
|
||||
* 设计要点:
|
||||
* 1. 访问令牌短期有效(30分钟),刷新令牌长期有效(7天)
|
||||
* 2. 绑定设备ID防止跨设备滥用[1](@ref)
|
||||
* 3. 精确控制双Token过期时间
|
||||
*/
|
||||
@Data
|
||||
public class TokenPair {
|
||||
// 访问令牌(用于API请求认证)
|
||||
private String accessToken;
|
||||
// 刷新令牌(用于获取新AccessToken)
|
||||
private String refreshToken;
|
||||
// AccessToken过期时间戳(毫秒)
|
||||
private long accessExpiresAt;
|
||||
// RefreshToken过期时间戳(毫秒)
|
||||
private long refreshExpiresAt;
|
||||
// 关联设备指纹(防御中间人攻击)[1](@ref)
|
||||
private String deviceId;
|
||||
|
||||
/**
|
||||
* 全参数构造器(安全增强版)
|
||||
* @param accessToken JWT格式访问令牌
|
||||
* @param refreshToken JWT格式刷新令牌
|
||||
* @param accessExpireMs AccessToken有效期(毫秒)
|
||||
* @param refreshExpireMs RefreshToken有效期(毫秒)
|
||||
* @param deviceId 客户端设备指纹
|
||||
*/
|
||||
public TokenPair(String accessToken, String refreshToken,
|
||||
long accessExpireMs, long refreshExpireMs,
|
||||
String deviceId) {
|
||||
this.accessToken = accessToken;
|
||||
this.refreshToken = refreshToken;
|
||||
this.accessExpiresAt = System.currentTimeMillis() + accessExpireMs;
|
||||
this.refreshExpiresAt = System.currentTimeMillis() + refreshExpireMs;
|
||||
this.deviceId = deviceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 快速创建方法(推荐)
|
||||
* @param accessToken 访问令牌
|
||||
* @param refreshToken 刷新令牌
|
||||
* @param deviceId 设备指纹
|
||||
* @return 初始化过期时间的TokenPair
|
||||
*/
|
||||
public static TokenPair create(String accessToken, String refreshToken, String deviceId) {
|
||||
return new TokenPair(
|
||||
accessToken,
|
||||
refreshToken,
|
||||
30 * 60 * 1000, // 30分钟有效期
|
||||
7 * 24 * 60 * 60 * 1000, // 7天有效期
|
||||
deviceId
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查AccessToken是否过期
|
||||
* @return true=已过期,false=有效
|
||||
*/
|
||||
public boolean isAccessExpired() {
|
||||
return System.currentTimeMillis() > accessExpiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查RefreshToken是否过期
|
||||
* @return true=已过期,false=有效
|
||||
*/
|
||||
public boolean isRefreshExpired() {
|
||||
return System.currentTimeMillis() > refreshExpiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全刷新令牌(生成新TokenPair)
|
||||
* @param newAccessToken 新访问令牌
|
||||
* @param newRefreshToken 新刷新令牌
|
||||
* @return 更新后的TokenPair(保留原设备ID)
|
||||
*/
|
||||
public TokenPair refresh(String newAccessToken, String newRefreshToken) {
|
||||
return new TokenPair(
|
||||
newAccessToken,
|
||||
newRefreshToken,
|
||||
this.accessExpiresAt - System.currentTimeMillis(), // 剩余时间延续
|
||||
this.refreshExpiresAt - System.currentTimeMillis(),
|
||||
this.deviceId // 保持设备一致性
|
||||
);
|
||||
}
|
||||
}
|
||||
109
src/main/java/com/onekeycall/videotablet/entity/User.java
Normal file
109
src/main/java/com/onekeycall/videotablet/entity/User.java
Normal file
@@ -0,0 +1,109 @@
|
||||
package com.onekeycall.videotablet.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User implements UserDetails {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true)
|
||||
private String username;
|
||||
|
||||
@Column()
|
||||
private String password;
|
||||
|
||||
@Column(unique = true)
|
||||
private String email;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String phone;
|
||||
|
||||
@Column(name = "create_time",unique = true, nullable = false)
|
||||
private Date creatTime;
|
||||
|
||||
// Getters and Setters
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Getters and Setters for id, username, password, email
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public String getEmail() {
|
||||
return email;
|
||||
}
|
||||
|
||||
public void setEmail(String email) {
|
||||
this.email = email;
|
||||
}
|
||||
|
||||
public String getPhone() {
|
||||
return phone;
|
||||
}
|
||||
|
||||
public void setPhone(String phone) {
|
||||
this.phone = phone;
|
||||
}
|
||||
|
||||
public Date getCreatTime() {
|
||||
return creatTime;
|
||||
}
|
||||
|
||||
public void setCreatTime(Date creatTime) {
|
||||
this.creatTime = creatTime;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.onekeycall.videotablet.handler;
|
||||
|
||||
import com.onekeycall.videotablet.result.Result;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.validation.FieldError;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.servlet.resource.NoResourceFoundException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一异常处理
|
||||
*
|
||||
* @author 爷爷的茶七里香
|
||||
* @date 2022/05/30 ControllerAdvice注解的含义是当异常抛到controller层时会拦截下来
|
||||
*/
|
||||
@ControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 使用ExceptionHandler注解声明处理Exception异常
|
||||
*
|
||||
* @param e e
|
||||
* @return {@link Result}
|
||||
*/
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
|
||||
@ExceptionHandler(Exception.class)
|
||||
public Result handlerException(Exception e) {
|
||||
// 控制台打印异常
|
||||
e.printStackTrace();
|
||||
// 返回错误格式信息
|
||||
return Result.error().message(e.getMessage());
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public Result handlerMissingServletRequestParameterException(MissingServletRequestParameterException e) {
|
||||
// 控制台打印异常
|
||||
e.printStackTrace();
|
||||
return Result.error().message("缺少参数");
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.NOT_FOUND)
|
||||
@ExceptionHandler(NoResourceFoundException.class)
|
||||
public Result handleNoResourceFoundException(NoResourceFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
// 新版本 Spring(推荐)我没有这个方法
|
||||
// return ResponseEntity.notFound().body(error);
|
||||
// 旧版本 Spring(兼容写法)
|
||||
return Result.notFound().message("检查网址是否正确");
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public Result handleValidationExceptions(MethodArgumentNotValidException ex) {
|
||||
ex.printStackTrace();
|
||||
Map<String, Object> errors = new HashMap<>();
|
||||
ex.getBindingResult().getAllErrors().forEach(error -> {
|
||||
String fieldName = ((FieldError) error).getField();
|
||||
String errorMessage = error.getDefaultMessage();
|
||||
errors.put(fieldName, errorMessage);
|
||||
});
|
||||
return Result.error().message("参数不能为空").data(errors);
|
||||
}
|
||||
|
||||
// @ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||
// @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class})
|
||||
// @ResponseBody
|
||||
// public Map<String, String> handleValidationException(Exception ex) {
|
||||
// BindingResult result = ex instanceof MethodArgumentNotValidException
|
||||
// ? ((MethodArgumentNotValidException) ex).getBindingResult()
|
||||
// : ((BindException) ex).getBindingResult();
|
||||
//
|
||||
// Map<String, String> errors = new HashMap<>();
|
||||
// result.getFieldErrors().forEach(error -> {
|
||||
// errors.put(error.getField(), error.getDefaultMessage());
|
||||
// });
|
||||
// return errors;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.onekeycall.videotablet.repository;
|
||||
|
||||
import com.onekeycall.videotablet.entity.User;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface UserRepository extends JpaRepository<User, Long> {
|
||||
Optional<User> findByUsername(String username);
|
||||
boolean existsByUsername(String username);
|
||||
boolean existsByEmail(String email);
|
||||
Optional<User> findByPhone(String phone);
|
||||
boolean existsByPhone(String phone);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package com.onekeycall.videotablet.result;
|
||||
|
||||
/**
|
||||
* 接口返回工具类
|
||||
*/
|
||||
public class JsonData {
|
||||
private int code;
|
||||
private Object data;
|
||||
private String msg;
|
||||
|
||||
public JsonData() {
|
||||
}
|
||||
|
||||
public JsonData(int code, Object data) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public JsonData(int code, Object data, String msg) {
|
||||
this.code = code;
|
||||
this.data = data;
|
||||
this.msg = msg;
|
||||
}
|
||||
|
||||
public static JsonData buildSuccess(Object data) {
|
||||
return new JsonData(0, data);
|
||||
}
|
||||
|
||||
public static JsonData buildError(String msg) {
|
||||
return new JsonData(-1, "", msg);
|
||||
}
|
||||
|
||||
public static JsonData buildError(int code, String msg) {
|
||||
return new JsonData(code, "", msg);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(int code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public Object getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Object data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public String getMsg() {
|
||||
return msg;
|
||||
}
|
||||
|
||||
public void setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
129
src/main/java/com/onekeycall/videotablet/result/Result.java
Normal file
129
src/main/java/com/onekeycall/videotablet/result/Result.java
Normal file
@@ -0,0 +1,129 @@
|
||||
package com.onekeycall.videotablet.result;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 统一返回格式类
|
||||
*
|
||||
* @author 爷爷的茶七里香
|
||||
* @date 2022/05/30
|
||||
*/
|
||||
public class Result {
|
||||
|
||||
/**
|
||||
* 是否成功
|
||||
*/
|
||||
private Boolean success;
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
private Integer code;
|
||||
|
||||
/**
|
||||
* 返回的消息
|
||||
*/
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* 放置响应的数据
|
||||
*/
|
||||
private Map<String, Object> data = new HashMap<>();
|
||||
|
||||
public Result() {
|
||||
}
|
||||
|
||||
/** 以下是定义一些常用到的格式,可以看到调用了我们创建的枚举类 */
|
||||
|
||||
public static Result ok() {
|
||||
Result r = new Result();
|
||||
r.setSuccess(ResultCodeEnum.SUCCESS.getSuccess());
|
||||
r.setCode(ResultCodeEnum.SUCCESS.getCode());
|
||||
r.setMessage(ResultCodeEnum.SUCCESS.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static Result error() {
|
||||
Result r = new Result();
|
||||
r.setSuccess(ResultCodeEnum.UNKNOWN_REASON.getSuccess());
|
||||
r.setCode(ResultCodeEnum.UNKNOWN_REASON.getCode());
|
||||
r.setMessage(ResultCodeEnum.UNKNOWN_REASON.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static Result notFound() {
|
||||
Result r = new Result();
|
||||
r.setSuccess(ResultCodeEnum.NOT_FOUND.getSuccess());
|
||||
r.setCode(ResultCodeEnum.NOT_FOUND.getCode());
|
||||
r.setMessage(ResultCodeEnum.NOT_FOUND.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
public static Result setResult(ResultCodeEnum resultCodeEnum) {
|
||||
Result r = new Result();
|
||||
r.setSuccess(resultCodeEnum.getSuccess());
|
||||
r.setCode(resultCodeEnum.getCode());
|
||||
r.setMessage(resultCodeEnum.getMessage());
|
||||
return r;
|
||||
}
|
||||
|
||||
public Result success(Boolean success) {
|
||||
this.setSuccess(success);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result message(String message) {
|
||||
this.setMessage(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result code(Integer code) {
|
||||
this.setCode(code);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result data(String key, Object value) {
|
||||
this.data.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Result data(Map<String, Object> map) {
|
||||
this.setData(map);
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 以下是get/set方法,如果项目有集成lombok可以使用@Data注解代替 */
|
||||
|
||||
public Boolean getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public void setSuccess(Boolean success) {
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public void setCode(Integer code) {
|
||||
this.code = code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Map<String, Object> getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(Map<String, Object> data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.onekeycall.videotablet.result;
|
||||
|
||||
/**
|
||||
* 状态码
|
||||
*
|
||||
* @author 爷爷的茶七里香
|
||||
* @date 2022/05/30
|
||||
*/
|
||||
public enum ResultCodeEnum {
|
||||
|
||||
SUCCESS(true, 20000, "成功"),
|
||||
|
||||
UNKNOWN_REASON(false, 20001, "未知错误"),
|
||||
NOT_FOUND(true, 20004, "没有数据");
|
||||
|
||||
|
||||
private final Boolean success;
|
||||
|
||||
private final Integer code;
|
||||
|
||||
private final String message;
|
||||
|
||||
ResultCodeEnum(Boolean success, Integer code, String message) {
|
||||
this.success = success;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public Boolean getSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public Integer getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResultCodeEnum{" + "success=" + success + ", code=" + code + ", message='" + message + '\'' + '}';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.onekeycall.videotablet.service;
|
||||
|
||||
import com.onekeycall.videotablet.entity.User;
|
||||
import com.onekeycall.videotablet.repository.UserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.Map;
|
||||
|
||||
@Service
|
||||
public class UserService implements UserDetailsService {
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Autowired
|
||||
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, RedisTemplate<String, Object> redisTemplate) {
|
||||
this.userRepository = userRepository;
|
||||
this.passwordEncoder = passwordEncoder;
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
public User registerUser(String username, String password) {
|
||||
if (userRepository.existsByUsername(username)) {
|
||||
throw new RuntimeException("Username already exists");
|
||||
}
|
||||
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setPassword(passwordEncoder.encode(password));
|
||||
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public User registerByPhone(String phone, String code, Date createTime) {
|
||||
// 1. 验证验证码
|
||||
Map<String, Object> codeMap = (Map<String, Object>) redisTemplate.opsForValue().get(phone);
|
||||
if (codeMap == null || !code.equals(codeMap.get("code").toString())) {
|
||||
throw new RuntimeException("Invalid verification code");
|
||||
}
|
||||
|
||||
// 2. 检查手机号是否已注册
|
||||
if (userRepository.existsByPhone(phone)) {
|
||||
throw new RuntimeException("Phone number already registered");
|
||||
}
|
||||
|
||||
// 3. 创建新用户
|
||||
User user = new User();
|
||||
user.setPhone(phone);
|
||||
user.setCreatTime(createTime);
|
||||
|
||||
return userRepository.save(user);
|
||||
}
|
||||
|
||||
public User loginByPhone(String phone, String code) {
|
||||
// 1. 验证验证码
|
||||
Map<String, Object> codeMap = (Map<String, Object>) redisTemplate.opsForValue().get(phone);
|
||||
if (codeMap == null || !code.equals(codeMap.get("code").toString())) {
|
||||
throw new RuntimeException("Invalid verification code");
|
||||
}
|
||||
|
||||
// 2. 查询用户
|
||||
return userRepository.findByPhone(phone)
|
||||
.orElseThrow(() -> new RuntimeException("User not found with phone: " + phone));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
return userRepository.findByUsername(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found with username: " + username));
|
||||
}
|
||||
}
|
||||
95
src/main/java/com/onekeycall/videotablet/sms/SendSms.java
Normal file
95
src/main/java/com/onekeycall/videotablet/sms/SendSms.java
Normal file
@@ -0,0 +1,95 @@
|
||||
package com.onekeycall.videotablet.sms;
|
||||
|
||||
// This file is auto-generated, don't edit it. Thanks.
|
||||
|
||||
import com.aliyun.auth.credentials.Credential;
|
||||
import com.aliyun.auth.credentials.provider.StaticCredentialProvider;
|
||||
import com.aliyun.sdk.service.dysmsapi20170525.AsyncClient;
|
||||
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsRequest;
|
||||
import com.aliyun.sdk.service.dysmsapi20170525.models.SendSmsResponse;
|
||||
import com.google.gson.Gson;
|
||||
import darabonba.core.client.ClientOverrideConfiguration;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class SendSms {
|
||||
public static void sendTest(String number) throws Exception {
|
||||
|
||||
// HttpClient Configuration
|
||||
/*HttpClient httpClient = new ApacheAsyncHttpClientBuilder()
|
||||
.connectionTimeout(Duration.ofSeconds(10)) // Set the connection timeout time, the default is 10 seconds
|
||||
.responseTimeout(Duration.ofSeconds(10)) // Set the response timeout time, the default is 20 seconds
|
||||
.maxConnections(128) // Set the connection pool size
|
||||
.maxIdleTimeOut(Duration.ofSeconds(50)) // Set the connection pool timeout, the default is 30 seconds
|
||||
// Configure the proxy
|
||||
.proxy(new ProxyOptions(ProxyOptions.Type.HTTP, new InetSocketAddress("<your-proxy-hostname>", 9001))
|
||||
.setCredentials("<your-proxy-username>", "<your-proxy-password>"))
|
||||
// If it is an https connection, you need to configure the certificate, or ignore the certificate(.ignoreSSL(true))
|
||||
.x509TrustManagers(new X509TrustManager[]{})
|
||||
.keyManagers(new KeyManager[]{})
|
||||
.ignoreSSL(false)
|
||||
.build();*/
|
||||
|
||||
// Configure Credentials authentication information, including ak, secret, token
|
||||
StaticCredentialProvider provider = StaticCredentialProvider.create(Credential.builder()
|
||||
// Please ensure that the environment variables ALIBABA_CLOUD_ACCESS_KEY_ID and ALIBABA_CLOUD_ACCESS_KEY_SECRET are set.
|
||||
.accessKeyId("LTAI5tEVYLeg7U3bP58n3Xkj")
|
||||
.accessKeySecret("bbPSlxkaGgkhUPaCOP7CANaNNY1b2p")
|
||||
//.securityToken(System.getenv("ALIBABA_CLOUD_SECURITY_TOKEN")) // use STS token
|
||||
.build());
|
||||
|
||||
// Configure the Client
|
||||
AsyncClient client = AsyncClient.builder()
|
||||
.region("cn-shenzhen") // Region ID
|
||||
//.httpClient(httpClient) // Use the configured HttpClient, otherwise use the default HttpClient (Apache HttpClient)
|
||||
.credentialsProvider(provider)
|
||||
//.serviceConfiguration(Configuration.create()) // Service-level configuration
|
||||
// Client-level configuration rewrite, can set Endpoint, Http request parameters, etc.
|
||||
.overrideConfiguration(
|
||||
ClientOverrideConfiguration.create()
|
||||
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
|
||||
.setEndpointOverride("dysmsapi.aliyuncs.com")
|
||||
//.setConnectTimeout(Duration.ofSeconds(30))
|
||||
)
|
||||
.build();
|
||||
|
||||
// Parameter settings for API request
|
||||
SendSmsRequest sendSmsRequest = SendSmsRequest.builder()
|
||||
.signName("同同的小站")
|
||||
.templateCode("SMS_467570437")
|
||||
.phoneNumbers(number)
|
||||
.templateParam("{\"code\":\"" + generatedcode(4) + "\"}")
|
||||
// Request-level configuration rewrite, can set Http request parameters, etc.
|
||||
// .requestConfiguration(RequestConfiguration.create().setHttpHeaders(new HttpHeaders()))
|
||||
.build();
|
||||
|
||||
// Asynchronously get the return value of the API request
|
||||
CompletableFuture<SendSmsResponse> response = client.sendSms(sendSmsRequest);
|
||||
// Synchronously get the return value of the API request
|
||||
SendSmsResponse resp = response.get();
|
||||
System.out.println(new Gson().toJson(resp));
|
||||
// Asynchronous processing of return values
|
||||
/*response.thenAccept(resp -> {
|
||||
System.out.println(new Gson().toJson(resp));
|
||||
}).exceptionally(throwable -> { // Handling exceptions
|
||||
System.out.println(throwable.getMessage());
|
||||
return null;
|
||||
});*/
|
||||
|
||||
// Finally, close the client
|
||||
client.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成?位的数字类型的短信验证码
|
||||
*
|
||||
* @param count
|
||||
* @return
|
||||
*/
|
||||
public static String generatedcode(int count) {
|
||||
String code = String.valueOf((int) ((Math.random() * 9 + 1) * Math.pow(10, count - 1)));
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.onekeycall.videotablet.utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class HashUtils {
|
||||
public static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
FileInputStream fis = new FileInputStream(file);
|
||||
byte[] dataBytes = new byte[1024];
|
||||
|
||||
int bytesRead;
|
||||
while ((bytesRead = fis.read(dataBytes)) != -1) {
|
||||
md.update(dataBytes, 0, bytesRead);
|
||||
}
|
||||
|
||||
byte[] mdBytes = md.digest();
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte mdByte : mdBytes) {
|
||||
sb.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String calculateSHA1(File file) throws NoSuchAlgorithmException, IOException {
|
||||
InputStream inputStream = new FileInputStream(file);
|
||||
return calculateSHA1(inputStream);
|
||||
}
|
||||
|
||||
public static String calculateSHA1(InputStream inputStream) throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
byte[] hashBytes = digest.digest();
|
||||
return bytesToHex(hashBytes, true);
|
||||
}
|
||||
|
||||
public static String calculateSHA256(File file) throws NoSuchAlgorithmException, IOException {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
byte[] buffer = new byte[4096];
|
||||
int bytesRead;
|
||||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||
digest.update(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
byte[] hashBytes = digest.digest();
|
||||
return bytesToHex(hashBytes);
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes) {
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
String hex = String.format("%02x", b & 0xFF);
|
||||
/*大写*/
|
||||
// String hex = String.format("%02X", b & 0xFF);
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
|
||||
private static String bytesToHex(byte[] bytes, boolean upcase) {
|
||||
String format = upcase ? "%02X" : "%02x";
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : bytes) {
|
||||
String hex = String.format(format, b & 0xFF);
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
}
|
||||
60
src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java
Normal file
60
src/main/java/com/onekeycall/videotablet/utils/JwtUtil.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package com.onekeycall.videotablet.utils;
|
||||
|
||||
import com.onekeycall.videotablet.dto.TokenPair;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
@Autowired
|
||||
private StringRedisTemplate redisTemplate;
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
@Value("${jwt.access-expire}")
|
||||
private Long accessExpire;
|
||||
@Value("${jwt.refresh-expire}")
|
||||
private Long refreshExpire;
|
||||
|
||||
// 生成双Token(关联设备指纹)
|
||||
public TokenPair generateTokenPair(String username, String deviceId) {
|
||||
String accessToken = Jwts.builder()
|
||||
.subject(username)
|
||||
.claim("type", "ACCESS")
|
||||
.claim("deviceId", deviceId) // 绑定设备
|
||||
.issuedAt(new Date())
|
||||
.expiration(new Date(System.currentTimeMillis() + accessExpire))
|
||||
.signWith(Keys.hmacShaKeyFor(secret.getBytes()))
|
||||
.compact();
|
||||
|
||||
String refreshId = UUID.randomUUID().toString();
|
||||
String refreshToken = Jwts.builder()
|
||||
.subject(username)
|
||||
.claim("type", "REFRESH")
|
||||
.claim("refreshId", refreshId)
|
||||
.expiration(new Date(System.currentTimeMillis() + refreshExpire))
|
||||
.signWith(Keys.hmacShaKeyFor(secret.getBytes()))
|
||||
.compact();
|
||||
|
||||
// Redis存储Refresh Token(key: user:refresh:<username>, value: refreshId)
|
||||
redisTemplate.opsForValue().set(
|
||||
"user:refresh:" + username,
|
||||
refreshId,
|
||||
refreshExpire,
|
||||
TimeUnit.MILLISECONDS
|
||||
);
|
||||
|
||||
return new TokenPair(accessToken, refreshToken, accessExpire, refreshExpire, deviceId);
|
||||
}
|
||||
|
||||
// Token解析与校验(略)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.onekeycall.videotablet.utils;
|
||||
|
||||
public class TextUtils {
|
||||
public static boolean isEmpty(CharSequence str) {
|
||||
return str == null || str.length() == 0;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user