feat: user related logic refactor

This commit is contained in:
merlin
2025-11-27 17:42:31 +08:00
parent 23cb31d4fe
commit 0ea6e13064
28 changed files with 738 additions and 52 deletions

2
mvnw.cmd vendored
View File

@@ -23,7 +23,7 @@
@REM
@REM Optional ENV vars
@REM MVNW_REPOURL - repo url base for downloading maven distribution
@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven
@REM MVNW_USERNAME/MVNW_PASSWORD - userinfo and password for downloading maven
@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output
@REM ----------------------------------------------------------------------------

View File

@@ -28,11 +28,11 @@ public class SecurityConfig {
.authorizeHttpRequests(authz -> authz
.requestMatchers(
"/error",
"/login",
"/register",
"/shadow/**",
"/health",
"/code/**",
"/v3/api-docs/**"
"/v3/api-docs/**",
"/account/mail/verify/**"
).permitAll()
.anyRequest().authenticated()
)

View File

@@ -0,0 +1,57 @@
package xin.merlin.myplayerbackend.controller;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
import xin.merlin.myplayerbackend.entity.http.Code;
import xin.merlin.myplayerbackend.service.impl.AccountServiceImpl;
import xin.merlin.myplayerbackend.utils.result.Response;
/*
* 账户相关接口:
* 修改邮箱
* 修改密码
* */
@Slf4j
@RestController
@RequestMapping("/account")
@RequiredArgsConstructor
public class AccountController {
private final AccountServiceImpl accountService;
private final PasswordEncoder passwordEncoder;
@PostMapping("/change/email")
Response changeEmail(@RequestBody Code code, @RequestParam String email){
return accountService.changeEmail(code,email);
}
@PostMapping("/mail/verify/{encode}")
Response verifyEmail(@PathVariable String encode){
return accountService.verifyEmail(encode);
}
@PostMapping("/change/password")
Response changePassword(@RequestBody Code code){
return accountService.resetPassword(code);
}
// @PostMapping("/init")
// Response init(@RequestBody Account account) {
// try {
// if(account==null||account.getAccount()==null) return Response.success(ResultCode.ACCOUNT_INFO_LOST);
// account.setPassword(passwordEncoder.encode(account.getPassword()));
// accountService.init(account);
// return Response.success(ResultCode.SUCCESS);
// } catch (Exception e) {
// log.error(e.getMessage());
// return Response.fail(ResultCode.SERVER_ERROR);
// }
// }
}

View File

@@ -0,0 +1,80 @@
package xin.merlin.myplayerbackend.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import xin.merlin.myplayerbackend.entity.Audit;
import xin.merlin.myplayerbackend.service.UploadService;
import xin.merlin.myplayerbackend.service.impl.AuditServiceImpl;
import xin.merlin.myplayerbackend.utils.JwtUtil;
import xin.merlin.myplayerbackend.utils.result.Response;
import xin.merlin.myplayerbackend.utils.result.ResultCode;
import java.io.IOException;
@Slf4j
@RestController
@RequestMapping("/audit")
@RequiredArgsConstructor
public class AuditController {
private final AuditServiceImpl auditService;
private final UploadService uploadService;
private final JwtUtil jwtUtil;
@PostMapping("/upload/avatar")
@Transactional
Response uploadAvatar(@RequestParam("avatar") MultipartFile file,
@RequestParam("type") Integer type,
@RequestParam("applicant") Integer applicant,
@RequestParam("influence") Integer influence) throws IOException {
try {
String target = uploadService.uploadAvatar(file,type,influence);
Audit oldAudit = auditService.getOne(
Wrappers.<Audit>lambdaQuery().eq(Audit::getType, type)
.eq(Audit::getApplicant,applicant).eq(Audit::getInfluence,influence));
Audit audit = new Audit(type,applicant,influence,target);
if(oldAudit != null) {
audit = new Audit(oldAudit.getA_id(),type,applicant,influence,target);
}
auditService.saveOrUpdate(audit);
return Response.success(ResultCode.SUCCESS);
} catch (IOException e) {
log.error(e.getMessage());
return Response.fail(ResultCode.SERVER_ERROR);
}
}
// 管理员api
@GetMapping("/get/all")
Response getAll(@RequestHeader("Authorization")String token,@RequestParam Integer size,@RequestParam Integer page) {
token = token.substring(7);
if(!jwtUtil.isAdmin(token)) return Response.success(ResultCode.ACCOUNT_PERMISSION_DENY);
return Response.success(ResultCode.SUCCESS,auditService.page(new Page<Audit>().setCurrent(page).setSize(size), Wrappers.<Audit>lambdaQuery().orderByAsc(Audit::getType)));
}
// TODO完善完基础类别的增删改查之后统一处理审核
@PostMapping("/update/status")
Response updateStatus(@RequestHeader("Authorization")String token,@RequestBody Audit audit) {
token = token.substring(7);
if(!jwtUtil.isAdmin(token)) return Response.success(ResultCode.ACCOUNT_PERMISSION_DENY);
auditService.passTypeOne(audit);
return null;
}
}

View File

@@ -6,11 +6,12 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import xin.merlin.myplayerbackend.entity.Account;
import xin.merlin.myplayerbackend.entity.http.Code;
import xin.merlin.myplayerbackend.service.CodeService;
import xin.merlin.myplayerbackend.service.impl.AccountServiceImpl;
import xin.merlin.myplayerbackend.service.LoginService;
import xin.merlin.myplayerbackend.utils.result.Response;
import xin.merlin.myplayerbackend.utils.result.ResultCode;
@@ -21,35 +22,41 @@ import xin.merlin.myplayerbackend.utils.result.ResultCode;
* */
@Slf4j
@RestController
@RequestMapping("/shadow")
@RequiredArgsConstructor
public class LoginController {
private final AccountServiceImpl accountService;
private final LoginService loginService;
private final CodeService codeService;
private final HttpServletRequest request;
//login
// 账户密码登录逻辑
@PostMapping("/login")
Response login(@RequestBody Account account){
return Response.success(ResultCode.SUCCESS);
try {
return loginService.login(account);
} catch (Exception e) {
log.error(e.getMessage());
return Response.fail(ResultCode.SERVER_ERROR);
}
}
//register
// 验证码登录逻辑
@PostMapping("/login/byCode")
Response login(@RequestBody Code code){
return null;
}
// 注册逻辑
@PostMapping("/register")
Response register(@RequestBody Code code){
if(code.getCode()==null|| code.getCode().isEmpty() || code.getC_id()==null || code.getC_id().isEmpty()) return Response.success(ResultCode.MAIL_VERIFY_NOT_EXIST);
try {
Response response = codeService.verify(code);
if(response.getCode().equals("200")){
Account account = new Account();
account.setAccount(code.getAccount());
account.setIp(request.getRemoteAddr());
accountService.save(account);
}
return response;
return loginService.register(code,request.getRemoteAddr());
} catch (Exception e) {
log.error(e.getMessage(), e);
return Response.fail(ResultCode.SERVER_ERROR);

View File

@@ -0,0 +1,45 @@
package xin.merlin.myplayerbackend.controller;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import xin.merlin.myplayerbackend.entity.UserInfo;
import xin.merlin.myplayerbackend.service.impl.UserServiceImpl;
import xin.merlin.myplayerbackend.utils.JwtUtil;
import xin.merlin.myplayerbackend.utils.result.Response;
import xin.merlin.myplayerbackend.utils.result.ResultCode;
import java.util.Objects;
@RestController
@RequestMapping("/user")
@RequiredArgsConstructor
public class UserController {
private final UserServiceImpl userService;
private final JwtUtil jwtUtil;
Boolean consistencyTest(String token, Integer id) {
token = token.substring(7);
Integer i = jwtUtil.getId(token);
return Objects.equals(i, id);
}
@GetMapping("/info")
Response getInfo(@RequestHeader("Authorization")String token){
token = token.substring(7);
Integer id = jwtUtil.getId(token);
return Response.success(ResultCode.SUCCESS,userService.getOne(Wrappers.<UserInfo>lambdaQuery().eq(UserInfo::getId,id)));
}
@PostMapping("/update")
Response updateInfo(@RequestHeader("Authorization")String token,@RequestBody UserInfo userinfo){
if(!consistencyTest(token,userinfo.getId())) return Response.success(ResultCode.USER_ILLEGAL_REQUEST);
userService.updateById(userinfo);
return Response.success(ResultCode.SUCCESS);
}
}

View File

@@ -4,9 +4,11 @@ package xin.merlin.myplayerbackend.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@TableName("account")
@NoArgsConstructor
public class Account {
@TableId("id")
private Integer id;
@@ -14,4 +16,13 @@ public class Account {
private String account;
private String password;
private String ip;
// 1 user; 0 admin
private Integer character;
public Account(String account, String password, String ip, Integer character) {
this.account = account;
this.password = password;
this.ip = ip;
this.character = character;
}
}

View File

@@ -3,16 +3,27 @@ package xin.merlin.myplayerbackend.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@TableName("audit")
@AllArgsConstructor
@NoArgsConstructor
public class Audit {
@TableId("a_id")
private Integer a_id;
// 约定type == 0 已通过审核, 1 用户头像待审核, 2 群聊头像待审核, 3 playroom头像待审核 4 修改邮箱请求, 5 已经通过验证的邮箱修改请求, 6 已过期的审核请求
private Integer type;
private Integer applicant;
private Integer influence;
private String changed;
public Audit(Integer type,Integer applicant,Integer influence,String changed) {
this.type = type;
this.applicant = applicant;
this.influence = influence;
this.changed = changed;
}
}

View File

@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("group")
public class Group {
@TableName("groupinfo")
public class GroupInfo {
@TableId("g_id")
private Integer g_id;

View File

@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("playroom")
public class Playroom {
@TableName("playroominfo")
public class PlayroomInfo {
@TableId("r_id")
private Integer r_id;

View File

@@ -5,8 +5,8 @@ import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@Data
@TableName("user")
public class User {
@TableName("userinfo")
public class UserInfo {
@TableId("u_id")
private String u_id;

View File

@@ -7,5 +7,5 @@ public class Code {
private String account;
private String c_id;
private String code;
private String password;
}

View File

@@ -2,8 +2,8 @@ package xin.merlin.myplayerbackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xin.merlin.myplayerbackend.entity.Group;
import xin.merlin.myplayerbackend.entity.GroupInfo;
@Mapper
public interface GroupMapper extends BaseMapper<Group> {
public interface GroupMapper extends BaseMapper<GroupInfo> {
}

View File

@@ -2,8 +2,8 @@ package xin.merlin.myplayerbackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xin.merlin.myplayerbackend.entity.Playroom;
import xin.merlin.myplayerbackend.entity.PlayroomInfo;
@Mapper
public interface PlayroomMapper extends BaseMapper<Playroom> {
public interface PlayroomMapper extends BaseMapper<PlayroomInfo> {
}

View File

@@ -0,0 +1,9 @@
package xin.merlin.myplayerbackend.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xin.merlin.myplayerbackend.entity.UserInfo;
@Mapper
public interface UserMapper extends BaseMapper<UserInfo> {
}

View File

@@ -6,7 +6,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;
import xin.merlin.myplayerbackend.entity.Account;
import xin.merlin.myplayerbackend.entity.http.Code;
import xin.merlin.myplayerbackend.utils.result.Response;
@@ -40,6 +39,16 @@ public class CodeService {
.expireAfterWrite(5, TimeUnit.MINUTES)
.build();
// // 验证因子队列
// private static final Cache<String, String> authList = Caffeine.newBuilder()
// .expireAfterWrite(5, TimeUnit.MINUTES)
// .build();
//
// // 验证因子队列对外可访问
// public Cache<String, String> getAuthList() {
// return authList;
// }
private final MailService mailService;
// 发送验证码
@@ -68,7 +77,6 @@ public class CodeService {
//验证验证码是否存在、可用、正确
@Transactional
public Response verify(Code code){
String c_id = code.getC_id();
String account = code.getAccount();
@@ -94,7 +102,10 @@ public class CodeService {
if (!tempCode.equals(code.getCode())) return Response.success(ResultCode.MAIL_VERIFY_CODE_ERROR);
codeFailCount.invalidate(c_id);
emailCooldown.invalidate(account);
// waitingList.invalidate(c_id);
//// 生成认证因子
// String auth = UUID.randomUUID().toString();
// authList.put(auth, account);
return Response.success(ResultCode.SUCCESS);
} catch (Exception e) {
throw new RuntimeException(e);

View File

@@ -1,26 +1,82 @@
package xin.merlin.myplayerbackend.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xin.merlin.myplayerbackend.entity.Account;
import xin.merlin.myplayerbackend.entity.UserInfo;
import xin.merlin.myplayerbackend.entity.http.Code;
import xin.merlin.myplayerbackend.mapper.AccountMapper;
import xin.merlin.myplayerbackend.mapper.UserMapper;
import xin.merlin.myplayerbackend.utils.JwtUtil;
import xin.merlin.myplayerbackend.utils.RandomCode;
import xin.merlin.myplayerbackend.utils.result.Response;
import xin.merlin.myplayerbackend.utils.result.ResultCode;
import java.util.Map;
@Slf4j
@Service
@RequiredArgsConstructor
public class LoginService{
private final AccountMapper accountMapper;
public Response login(Account account){
Account ta = accountMapper.selectOne(new QueryWrapper<Account>().eq("account",account.getAccount()));
if(ta == null) return Response.success(ResultCode.USER_NOT_FOUND);
private final UserMapper userMapper;
//TODO:111
return null;
private final CodeService codeService;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
// 账户密码登录服务逻辑
public Response login(Account account){
try {
Account ta = accountMapper.selectOne(Wrappers.<Account>lambdaQuery().eq(Account::getAccount,account.getAccount()));
if(ta == null) return Response.success(ResultCode.USER_NOT_FOUND);
if(!passwordEncoder.matches(account.getPassword(),ta.getPassword())){
return Response.success(ResultCode.ACCOUNT_PWD_ERROR);
}
String token = jwtUtil.generateToken(ta);
UserInfo userinfo = userMapper.selectOne(Wrappers.<UserInfo>lambdaQuery().eq(UserInfo::getId,ta.getId()));
return Response.success(ResultCode.SUCCESS, Map.of("token",token,"token_type","Bearer","userinfo",userinfo));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// 注册服务逻辑
@Transactional
public Response register(Code code, String ip){
try {
if(accountMapper.selectOne(Wrappers.<Account>lambdaQuery().eq(Account::getAccount,code.getAccount())) != null) return Response.success(ResultCode.ACCOUNT_EXIST);
Response response = codeService.verify(code);
if(response.getCode().equals("200")){
Account account = new Account(code.getAccount(),passwordEncoder.encode(code.getPassword()),ip,1);
accountMapper.insert(account);
account = accountMapper.selectOne(Wrappers.<Account>lambdaQuery().eq(Account::getAccount, account.getAccount()));
UserInfo u = new UserInfo();
u.setId(account.getId());
do{
u.setU_id(RandomCode.generateID());
}while (userMapper.selectById(u.getU_id())!=null);
userMapper.insert(u);
u = userMapper.selectById(u.getId());
codeService.getWaitingList().invalidate(code.getC_id());
return Response.success(ResultCode.SUCCESS,Map.of("token",jwtUtil.generateToken(account),"userinfo",u));
}
return response;
} catch (Exception e) {
throw new RuntimeException(e);
}
}

View File

@@ -33,4 +33,19 @@ public class MailService {
throw new RuntimeException(e);
}
}
public Boolean sendTextMail(String receiver, String text){
try {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(mail);
message.setTo(receiver);
message.setSubject("Welcome to use Merlin`s product");
message.setText("欢迎使用Merlin.xin产品 \n"+text);
mailSender.send(message);
return true;
} catch (MailException e) {
log.error("e: ", e);
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,86 @@
package xin.merlin.myplayerbackend.service;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.List;
@Service
@RequiredArgsConstructor
public class UploadService {
@Value("${resources.user.avatar}")
private String userAvatarDir;
@Value("${resources.group.avatar}")
private String groupAvatarDir;
@Value("${resources.playroom.avatar}")
private String playroomAvatarDir;
@Value("${resources.public}")
private String publicAvatarDir;
private static final List<String> AVATAR_ALLOWED_EXTENSIONS = Arrays.asList(".jpg", ".jpeg", ".png", ".gif");
/**
* 上传用户、群组、放映室头像
* @param file 目标文件
* @param type 上传类型
* @param influence 受影响的id
* @return host后的访问url
* @throws IOException 抛出io异常
*/
public String uploadAvatar(MultipartFile file, Integer type,Integer influence) throws IOException {
String DIR = switch (type) {
case 1 -> userAvatarDir;
case 2 -> groupAvatarDir;
case 3 -> playroomAvatarDir;
default -> publicAvatarDir;
};
String fileName = getFilename(file, influence, DIR);
Path targetPath = Paths.get(DIR, fileName);
Files.copy(file.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);
return "/resources/user/avatar/" + fileName; // 返回访问 URL
}
/**
*
* @param file 目标文件
* @param influence 受影响的id
* @param DIR 储存的位置
* @return fileName 文件在储存之后的名字
* @throws IOException 抛出io异常
*/
private static String getFilename(MultipartFile file, Integer influence, String DIR) throws IOException {
File dir = new File(DIR);
if (!dir.exists()) dir.mkdirs();
// 取文件扩展名并检查是否合法
String originalFileName = file.getOriginalFilename();
String fileExtension = "";
if (originalFileName != null && originalFileName.contains(".")) {
fileExtension = originalFileName.substring(originalFileName.lastIndexOf(".")).toLowerCase();
}
if (!AVATAR_ALLOWED_EXTENSIONS.contains(fileExtension)) {
throw new IOException("仅支持 JPG, PNG, GIF 格式");
}
// 以id进行命名方便直接替换
return influence.toString() + fileExtension;
}
}

View File

@@ -1,10 +1,166 @@
package xin.merlin.myplayerbackend.service.impl;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xin.merlin.myplayerbackend.entity.Account;
import xin.merlin.myplayerbackend.entity.Audit;
import xin.merlin.myplayerbackend.entity.http.Code;
import xin.merlin.myplayerbackend.mapper.AccountMapper;
import xin.merlin.myplayerbackend.mapper.AuditMapper;
import xin.merlin.myplayerbackend.mapper.UserMapper;
import xin.merlin.myplayerbackend.service.CodeService;
import xin.merlin.myplayerbackend.service.MailService;
import xin.merlin.myplayerbackend.utils.AESUtil;
import xin.merlin.myplayerbackend.utils.result.Response;
import xin.merlin.myplayerbackend.utils.result.ResultCode;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Service
@RequiredArgsConstructor
public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> {
private static final Cache<Integer, String> verifyURL = Caffeine.newBuilder()
.expireAfterWrite(24, TimeUnit.HOURS)
.build();
private final AccountMapper accountMapper;
private final AuditMapper auditMapper;
private final CodeService codeService;
private final MailService mailService;
private final AESUtil aesUtil;
private final PasswordEncoder passwordEncoder;
private Integer findByAccount(String account) {
Account accountEntity = accountMapper.selectOne(
Wrappers.<Account>lambdaQuery()
.select(Account::getId)
.eq(Account::getAccount, account)
);
return accountEntity == null ? null : accountEntity.getId();
}
/**
*
* @param code 验证码因子结构体
* @param email 新邮箱
* @return Response响应体
*/
@Transactional
public Response changeEmail(Code code, String email) {
try {
Response response = codeService.verify(code);
if (response.getCode().equals("200")){
Integer id = findByAccount(code.getAccount());
Audit audit = auditMapper.selectOne(Wrappers.<Audit>lambdaQuery().eq(Audit::getType,4).eq(Audit::getApplicant,id).eq(Audit::getInfluence,id));
if (audit != null){
audit.setType(6);
auditMapper.updateById(audit);
}
String url = aesUtil.encrypt(id+":"+email);
if (id != null) {
verifyURL.put(id, url);
}
else {
return Response.success(ResultCode.MAIL_INFO_LOST);
}
mailService.sendTextMail(email,"验证链接:\n"+"https://myplayer.merlin.xin/account/mail/verify/"+ url+"\n链接一天内有效请尽快验证");
auditMapper.insert(new Audit(4,id,id,email));
codeService.getWaitingList().invalidate(code.getC_id());
return Response.success(ResultCode.SUCCESS);
}
else{
return response;
}
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
/**
*
* @param encode 认证链接
* @return response响应体
*/
@Transactional
public Response verifyEmail(String encode) {
try {
Map<String, String> decode = aesUtil.decryptAndSplit(encode);
Integer id = Integer.parseInt(decode.get("id"));
String email = decode.get("email");
Audit audit = auditMapper.selectOne(Wrappers.<Audit>lambdaQuery().eq(Audit::getType,4).eq(Audit::getApplicant,id).eq(Audit::getInfluence,id));
if (audit == null) return Response.success(ResultCode.AUDIT_NO_RECORD);
if (!encode.equals(verifyURL.getIfPresent(id))){
audit.setType(6);
auditMapper.updateById(audit);
return Response.success(ResultCode.AUDIT_NO_RECORD);
}
if (!audit.getChanged().equals(email)) return Response.success(ResultCode.ACCOUNT_ILLEGAL_CHANGE);
Account account = accountMapper.selectById(id);
account.setAccount(email);
accountMapper.updateById(account);
audit.setType(5);
auditMapper.updateById(audit);
verifyURL.invalidate(id);
return Response.success(ResultCode.SUCCESS);
} catch (NumberFormatException e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
/**
*
* @param code 验证码认证因子
* @return response响应体
*/
@Transactional
public Response resetPassword(Code code){
try {
Response response = codeService.verify(code);
if (response.getCode().equals("200")){
Integer id = findByAccount(code.getAccount());
Account account = accountMapper.selectById(id);
account.setPassword(passwordEncoder.encode(code.getPassword()));
accountMapper.updateById(account);
codeService.getWaitingList().invalidate(code.getC_id());
return Response.success(ResultCode.SUCCESS);
}
return response;
} catch (Exception e) {
log.error(e.getMessage());
throw new RuntimeException(e);
}
}
// @Transactional
// public void init(Account account) {
// try {
// account = accountMapper.selectOne(Wrappers.<Account>lambdaQuery().eq(Account::getAccount, account.getAccount()));
// if(accountMapper.updateById(account)==0) throw new RuntimeException(account.getAccount()+"初始化未成功");
// User u = new User();
// u.setId(account.getId());
// do{
// u.setU_id(RandomCode.generateID());
// }while (userMapper.selectById(u.getU_id())!=null);
// userMapper.insert(u);
// } catch (RuntimeException e) {
// throw new RuntimeException(e);
// }
// }
}

View File

@@ -1,10 +1,21 @@
package xin.merlin.myplayerbackend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import xin.merlin.myplayerbackend.entity.Audit;
import xin.merlin.myplayerbackend.mapper.AuditMapper;
import xin.merlin.myplayerbackend.mapper.UserMapper;
@Service
@RequiredArgsConstructor
public class AuditServiceImpl extends ServiceImpl<AuditMapper, Audit> {
private final AuditMapper auditMapper;
private final UserMapper userMapper;
public Boolean passTypeOne(Audit audit) {
return false;
}
}

View File

@@ -2,9 +2,9 @@ package xin.merlin.myplayerbackend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import xin.merlin.myplayerbackend.entity.Group;
import xin.merlin.myplayerbackend.entity.GroupInfo;
import xin.merlin.myplayerbackend.mapper.GroupMapper;
@Service
public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> {
public class GroupServiceImpl extends ServiceImpl<GroupMapper, GroupInfo> {
}

View File

@@ -2,9 +2,9 @@ package xin.merlin.myplayerbackend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import xin.merlin.myplayerbackend.entity.Playroom;
import xin.merlin.myplayerbackend.entity.PlayroomInfo;
import xin.merlin.myplayerbackend.mapper.PlayroomMapper;
@Service
public class PlayroomServiceImpl extends ServiceImpl<PlayroomMapper, Playroom> {
public class PlayroomServiceImpl extends ServiceImpl<PlayroomMapper, PlayroomInfo> {
}

View File

@@ -0,0 +1,10 @@
package xin.merlin.myplayerbackend.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import xin.merlin.myplayerbackend.entity.UserInfo;
import xin.merlin.myplayerbackend.mapper.UserMapper;
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, UserInfo> {
}

View File

@@ -0,0 +1,67 @@
package xin.merlin.myplayerbackend.utils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
@Component
public class AESUtil {
// 16 位密钥(必须 16/24/32 位)
private final String key;
private static final String ALGORITHM = "AES";
public AESUtil(@Value("${aes.key}") String key) {
this.key = key;
}
/** 加密 */
public String encrypt(String plainText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, keySpec);
byte[] encrypted = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encrypted);
} catch (Exception e) {
throw new RuntimeException("AES 加密失败", e);
}
}
/** 解密 */
public String decrypt(String cipherText) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
SecretKeySpec keySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, keySpec);
byte[] decoded = Base64.getDecoder().decode(cipherText);
byte[] original = cipher.doFinal(decoded);
return new String(original, StandardCharsets.UTF_8);
} catch (Exception e) {
throw new RuntimeException("AES 解密失败", e);
}
}
public Map<String, String> decryptAndSplit(String cipherText) {
String plainText = this.decrypt(cipherText); // 先解密
String[] parts = plainText.split(":", 2); // 只分割一次,防止 email 里有 :
if (parts.length != 2) {
throw new IllegalArgumentException("解密数据格式不正确:" + plainText);
}
Map<String, String> result = new HashMap<>();
result.put("id", parts[0]);
result.put("email", parts[1]);
return result;
}
}

View File

@@ -3,9 +3,12 @@ package xin.merlin.myplayerbackend.utils;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import xin.merlin.myplayerbackend.config.security.JwtProperties;
import xin.merlin.myplayerbackend.entity.Account;
import javax.crypto.SecretKey;
@@ -13,6 +16,8 @@ import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.UUID;
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {
@@ -35,17 +40,28 @@ public class JwtUtil {
}
}
@PreDestroy
public void destroy() {
log.info("JWT 组件正在关闭...");
// 如有线程池、定时任务、IO 连接,在这里 shutdown/close
// 本例仅把引用置空,帮助 GC可选
this.jwtParser = null;
this.key = null;
log.info("JWT 组件已关闭");
}
/**
* 生成 JWT Token
*/
public String generateToken(String account, Integer id) {
public String generateToken(Account account) {
Date now = new Date();
Date expireDate = new Date(now.getTime() + jwtProperties.getExpire() * 1000L);
return Jwts.builder()
.subject(account)
.claim("id", id)
.claim("account", account)
.subject(account.getAccount())
.claim("id", account.getId())
.claim("account", account.getAccount())
.claim("character", account.getCharacter())
.id(UUID.randomUUID().toString())
.issuedAt(now)
.expiration(expireDate)
@@ -57,7 +73,7 @@ public class JwtUtil {
/**
* 解析 Token 获取 Claims
*/
public Claims getClaims(String token) {
private Claims getClaims(String token) {
try {
Jws<Claims> jws = jwtParser.parseSignedClaims(token);
System.out.println(jws.getPayload());
@@ -95,6 +111,16 @@ public class JwtUtil {
return claims.get("id", Integer.class);
}
// public Integer getCharacter(String token) {
// Claims claims = getClaims(token);
// return claims.get("character", Integer.class);
// }
public Boolean isAdmin(String token) {
Claims claims = getClaims(token);
return claims.get("character", Integer.class)==0;
}
// 自定义异常类
public static class TokenExpiredException extends RuntimeException {
public TokenExpiredException(String message, Throwable cause) {

View File

@@ -0,0 +1,19 @@
package xin.merlin.myplayerbackend.utils;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
public class SHA256Util {
public static String sha256(String s) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] bytes = md.digest(s.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : bytes) sb.append(String.format("%02x", b));
return sb.toString();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -15,10 +15,16 @@ public enum ResultCode {
NOT_FOUND("404", "资源不存在"),
SERVER_ERROR("500", "服务器内部错误"),
// 自定义业务错误码
//账户相关
ACCOUNT_EXIST("2001","账户已存在"),
ACCOUNT_EXIST("3001","账户已存在"),
ACCOUNT_NOT_INIT("3002","账户未初始化"),
ACCOUNT_PWD_ERROR("3003","账户密码错误"),
ACCOUNT_INFO_LOST("3004","账户信息丢失"),
ACCOUNT_ILLEGAL_CHANGE("3005","账户非法篡改"),
ACCOUNT_PERMISSION_DENY("3006","用户权限不足"),
//用户相关
USER_BANNED("4000","用户被封禁"),
@@ -28,7 +34,7 @@ public enum ResultCode {
USER_VERIFICATION_ERROR("4005","验证码不存在或错误"),
USER_SEND_TOO_FAST("4006","用户请求过快"),
USER_SEND_TOO_OFTEN("4007","请求次数过多,已被限制"),
ORDER_NOT_FOUND("4000", "订单不存在"),
USER_ILLEGAL_REQUEST("4008", "用户非法请求"),
//邮箱相关
MAIL_ACCOUNT_NOT_PROVIDED("4101","未提供验证码接受账户"),
@@ -36,7 +42,10 @@ public enum ResultCode {
MAIL_INFO_LOST("4103","验证信息丢失"),
MAIL_VERIFY_FAIL_TOO_MANY("4104","验证码错误过多,请重新申请验证码"),
MAIL_VERIFY_NOT_EXIST("4105","验证码元素丢失,请重新申请验证码"),
MAIL_VERIFY_CODE_ERROR("4106","验证码错误,请重新输入");
MAIL_VERIFY_CODE_ERROR("4106","验证码错误,请重新输入"),
//审核相关
AUDIT_NO_RECORD("4201","无审核记录条目");