change package name,fixes error

This commit is contained in:
2025-08-05 14:44:03 +08:00
parent 9832336b89
commit 69700c8fe1
22 changed files with 46 additions and 39 deletions

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View 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 // 保持设备一致性
);
}
}

View 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;
}
}

View File

@@ -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;
// }
}

View File

@@ -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);
}

View File

@@ -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;
}
}

View 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;
}
}

View File

@@ -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 + '\'' + '}';
}
}

View File

@@ -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));
}
}

View 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;
}
}

View File

@@ -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();
}
}

View 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 Tokenkey: user:refresh:<username>, value: refreshId
redisTemplate.opsForValue().set(
"user:refresh:" + username,
refreshId,
refreshExpire,
TimeUnit.MILLISECONDS
);
return new TokenPair(accessToken, refreshToken, accessExpire, refreshExpire, deviceId);
}
// Token解析与校验
}

View File

@@ -0,0 +1,7 @@
package com.onekeycall.videotablet.utils;
public class TextUtils {
public static boolean isEmpty(CharSequence str) {
return str == null || str.length() == 0;
}
}