From 0ea6e1306405753662f7e64debba7ccab4e6f7cd Mon Sep 17 00:00:00 2001 From: merlin Date: Thu, 27 Nov 2025 17:42:31 +0800 Subject: [PATCH] feat: user related logic refactor --- mvnw.cmd | 2 +- .../config/security/SecurityConfig.java | 6 +- .../controller/AccountController.java | 57 +++++++ .../controller/AuditController.java | 80 +++++++++ .../controller/LoginController.java | 37 +++-- .../controller/UserController.java | 45 +++++ .../myplayerbackend/entity/Account.java | 11 ++ .../merlin/myplayerbackend/entity/Audit.java | 13 +- .../entity/{Group.java => GroupInfo.java} | 4 +- .../{Playroom.java => PlayroomInfo.java} | 4 +- .../entity/{User.java => UserInfo.java} | 4 +- .../myplayerbackend/entity/http/Code.java | 2 +- .../myplayerbackend/mapper/GroupMapper.java | 4 +- .../mapper/PlayroomMapper.java | 4 +- .../myplayerbackend/mapper/UserMapper.java | 9 + .../myplayerbackend/service/CodeService.java | 17 +- .../myplayerbackend/service/LoginService.java | 68 +++++++- .../myplayerbackend/service/MailService.java | 15 ++ .../service/UploadService.java | 86 ++++++++++ .../service/impl/AccountServiceImpl.java | 156 ++++++++++++++++++ .../service/impl/AuditServiceImpl.java | 11 ++ .../service/impl/GroupServiceImpl.java | 4 +- .../service/impl/PlayroomServiceImpl.java | 4 +- .../service/impl/UserServiceImpl.java | 10 ++ .../merlin/myplayerbackend/utils/AESUtil.java | 67 ++++++++ .../merlin/myplayerbackend/utils/JwtUtil.java | 36 +++- .../myplayerbackend/utils/SHA256Util.java | 19 +++ .../utils/result/ResultCode.java | 15 +- 28 files changed, 738 insertions(+), 52 deletions(-) create mode 100644 src/main/java/xin/merlin/myplayerbackend/controller/AccountController.java create mode 100644 src/main/java/xin/merlin/myplayerbackend/controller/AuditController.java create mode 100644 src/main/java/xin/merlin/myplayerbackend/controller/UserController.java rename src/main/java/xin/merlin/myplayerbackend/entity/{Group.java => GroupInfo.java} (86%) rename src/main/java/xin/merlin/myplayerbackend/entity/{Playroom.java => PlayroomInfo.java} (85%) rename src/main/java/xin/merlin/myplayerbackend/entity/{User.java => UserInfo.java} (87%) create mode 100644 src/main/java/xin/merlin/myplayerbackend/mapper/UserMapper.java create mode 100644 src/main/java/xin/merlin/myplayerbackend/service/UploadService.java create mode 100644 src/main/java/xin/merlin/myplayerbackend/service/impl/UserServiceImpl.java create mode 100644 src/main/java/xin/merlin/myplayerbackend/utils/AESUtil.java create mode 100644 src/main/java/xin/merlin/myplayerbackend/utils/SHA256Util.java diff --git a/mvnw.cmd b/mvnw.cmd index 92450f9..27f05e1 100644 --- a/mvnw.cmd +++ b/mvnw.cmd @@ -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 ---------------------------------------------------------------------------- diff --git a/src/main/java/xin/merlin/myplayerbackend/config/security/SecurityConfig.java b/src/main/java/xin/merlin/myplayerbackend/config/security/SecurityConfig.java index db01d1b..c17a949 100644 --- a/src/main/java/xin/merlin/myplayerbackend/config/security/SecurityConfig.java +++ b/src/main/java/xin/merlin/myplayerbackend/config/security/SecurityConfig.java @@ -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() ) diff --git a/src/main/java/xin/merlin/myplayerbackend/controller/AccountController.java b/src/main/java/xin/merlin/myplayerbackend/controller/AccountController.java new file mode 100644 index 0000000..ddc3bbc --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/controller/AccountController.java @@ -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); +// } +// } + + +} diff --git a/src/main/java/xin/merlin/myplayerbackend/controller/AuditController.java b/src/main/java/xin/merlin/myplayerbackend/controller/AuditController.java new file mode 100644 index 0000000..349219c --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/controller/AuditController.java @@ -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.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().setCurrent(page).setSize(size), Wrappers.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; + + } + + +} diff --git a/src/main/java/xin/merlin/myplayerbackend/controller/LoginController.java b/src/main/java/xin/merlin/myplayerbackend/controller/LoginController.java index fc7bf7b..f1aba97 100644 --- a/src/main/java/xin/merlin/myplayerbackend/controller/LoginController.java +++ b/src/main/java/xin/merlin/myplayerbackend/controller/LoginController.java @@ -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); diff --git a/src/main/java/xin/merlin/myplayerbackend/controller/UserController.java b/src/main/java/xin/merlin/myplayerbackend/controller/UserController.java new file mode 100644 index 0000000..5c6ec9c --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/controller/UserController.java @@ -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.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); + } + +} diff --git a/src/main/java/xin/merlin/myplayerbackend/entity/Account.java b/src/main/java/xin/merlin/myplayerbackend/entity/Account.java index 9904fef..ee3f4ab 100644 --- a/src/main/java/xin/merlin/myplayerbackend/entity/Account.java +++ b/src/main/java/xin/merlin/myplayerbackend/entity/Account.java @@ -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; + } } diff --git a/src/main/java/xin/merlin/myplayerbackend/entity/Audit.java b/src/main/java/xin/merlin/myplayerbackend/entity/Audit.java index 35756db..c9b1dab 100644 --- a/src/main/java/xin/merlin/myplayerbackend/entity/Audit.java +++ b/src/main/java/xin/merlin/myplayerbackend/entity/Audit.java @@ -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; + } } diff --git a/src/main/java/xin/merlin/myplayerbackend/entity/Group.java b/src/main/java/xin/merlin/myplayerbackend/entity/GroupInfo.java similarity index 86% rename from src/main/java/xin/merlin/myplayerbackend/entity/Group.java rename to src/main/java/xin/merlin/myplayerbackend/entity/GroupInfo.java index a1ac085..6b904a1 100644 --- a/src/main/java/xin/merlin/myplayerbackend/entity/Group.java +++ b/src/main/java/xin/merlin/myplayerbackend/entity/GroupInfo.java @@ -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; diff --git a/src/main/java/xin/merlin/myplayerbackend/entity/Playroom.java b/src/main/java/xin/merlin/myplayerbackend/entity/PlayroomInfo.java similarity index 85% rename from src/main/java/xin/merlin/myplayerbackend/entity/Playroom.java rename to src/main/java/xin/merlin/myplayerbackend/entity/PlayroomInfo.java index fc57e9f..d3c5a1f 100644 --- a/src/main/java/xin/merlin/myplayerbackend/entity/Playroom.java +++ b/src/main/java/xin/merlin/myplayerbackend/entity/PlayroomInfo.java @@ -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; diff --git a/src/main/java/xin/merlin/myplayerbackend/entity/User.java b/src/main/java/xin/merlin/myplayerbackend/entity/UserInfo.java similarity index 87% rename from src/main/java/xin/merlin/myplayerbackend/entity/User.java rename to src/main/java/xin/merlin/myplayerbackend/entity/UserInfo.java index d633146..e8e2699 100644 --- a/src/main/java/xin/merlin/myplayerbackend/entity/User.java +++ b/src/main/java/xin/merlin/myplayerbackend/entity/UserInfo.java @@ -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; diff --git a/src/main/java/xin/merlin/myplayerbackend/entity/http/Code.java b/src/main/java/xin/merlin/myplayerbackend/entity/http/Code.java index ad4739a..3da7411 100644 --- a/src/main/java/xin/merlin/myplayerbackend/entity/http/Code.java +++ b/src/main/java/xin/merlin/myplayerbackend/entity/http/Code.java @@ -7,5 +7,5 @@ public class Code { private String account; private String c_id; private String code; - + private String password; } diff --git a/src/main/java/xin/merlin/myplayerbackend/mapper/GroupMapper.java b/src/main/java/xin/merlin/myplayerbackend/mapper/GroupMapper.java index e02952e..c586ac7 100644 --- a/src/main/java/xin/merlin/myplayerbackend/mapper/GroupMapper.java +++ b/src/main/java/xin/merlin/myplayerbackend/mapper/GroupMapper.java @@ -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 { +public interface GroupMapper extends BaseMapper { } diff --git a/src/main/java/xin/merlin/myplayerbackend/mapper/PlayroomMapper.java b/src/main/java/xin/merlin/myplayerbackend/mapper/PlayroomMapper.java index b28db99..0e8b062 100644 --- a/src/main/java/xin/merlin/myplayerbackend/mapper/PlayroomMapper.java +++ b/src/main/java/xin/merlin/myplayerbackend/mapper/PlayroomMapper.java @@ -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 { +public interface PlayroomMapper extends BaseMapper { } diff --git a/src/main/java/xin/merlin/myplayerbackend/mapper/UserMapper.java b/src/main/java/xin/merlin/myplayerbackend/mapper/UserMapper.java new file mode 100644 index 0000000..45c1c91 --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/mapper/UserMapper.java @@ -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 { +} diff --git a/src/main/java/xin/merlin/myplayerbackend/service/CodeService.java b/src/main/java/xin/merlin/myplayerbackend/service/CodeService.java index 8290828..a4c0b28 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/CodeService.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/CodeService.java @@ -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 authList = Caffeine.newBuilder() +// .expireAfterWrite(5, TimeUnit.MINUTES) +// .build(); +// +// // 验证因子队列对外可访问 +// public Cache 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); diff --git a/src/main/java/xin/merlin/myplayerbackend/service/LoginService.java b/src/main/java/xin/merlin/myplayerbackend/service/LoginService.java index 413b952..e064df0 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/LoginService.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/LoginService.java @@ -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().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.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.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.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.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); + } } diff --git a/src/main/java/xin/merlin/myplayerbackend/service/MailService.java b/src/main/java/xin/merlin/myplayerbackend/service/MailService.java index 4f0401e..937fef0 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/MailService.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/MailService.java @@ -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); + } + } } diff --git a/src/main/java/xin/merlin/myplayerbackend/service/UploadService.java b/src/main/java/xin/merlin/myplayerbackend/service/UploadService.java new file mode 100644 index 0000000..84e95dd --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/service/UploadService.java @@ -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 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; + } + + +} diff --git a/src/main/java/xin/merlin/myplayerbackend/service/impl/AccountServiceImpl.java b/src/main/java/xin/merlin/myplayerbackend/service/impl/AccountServiceImpl.java index b390062..d8b4a96 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/impl/AccountServiceImpl.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/impl/AccountServiceImpl.java @@ -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 { + + private static final Cache 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.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.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 decode = aesUtil.decryptAndSplit(encode); + Integer id = Integer.parseInt(decode.get("id")); + String email = decode.get("email"); + Audit audit = auditMapper.selectOne(Wrappers.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.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); +// } +// } + } diff --git a/src/main/java/xin/merlin/myplayerbackend/service/impl/AuditServiceImpl.java b/src/main/java/xin/merlin/myplayerbackend/service/impl/AuditServiceImpl.java index 9aff1e3..5bdcae5 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/impl/AuditServiceImpl.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/impl/AuditServiceImpl.java @@ -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 { + + private final AuditMapper auditMapper; + + private final UserMapper userMapper; + + public Boolean passTypeOne(Audit audit) { + return false; + } } diff --git a/src/main/java/xin/merlin/myplayerbackend/service/impl/GroupServiceImpl.java b/src/main/java/xin/merlin/myplayerbackend/service/impl/GroupServiceImpl.java index 97caa6e..ea76e25 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/impl/GroupServiceImpl.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/impl/GroupServiceImpl.java @@ -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 { +public class GroupServiceImpl extends ServiceImpl { } diff --git a/src/main/java/xin/merlin/myplayerbackend/service/impl/PlayroomServiceImpl.java b/src/main/java/xin/merlin/myplayerbackend/service/impl/PlayroomServiceImpl.java index 1ec66c9..d1acab7 100644 --- a/src/main/java/xin/merlin/myplayerbackend/service/impl/PlayroomServiceImpl.java +++ b/src/main/java/xin/merlin/myplayerbackend/service/impl/PlayroomServiceImpl.java @@ -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 { +public class PlayroomServiceImpl extends ServiceImpl { } diff --git a/src/main/java/xin/merlin/myplayerbackend/service/impl/UserServiceImpl.java b/src/main/java/xin/merlin/myplayerbackend/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..f352620 --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/service/impl/UserServiceImpl.java @@ -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 { +} diff --git a/src/main/java/xin/merlin/myplayerbackend/utils/AESUtil.java b/src/main/java/xin/merlin/myplayerbackend/utils/AESUtil.java new file mode 100644 index 0000000..0f17095 --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/utils/AESUtil.java @@ -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 decryptAndSplit(String cipherText) { + String plainText = this.decrypt(cipherText); // 先解密 + String[] parts = plainText.split(":", 2); // 只分割一次,防止 email 里有 : + + if (parts.length != 2) { + throw new IllegalArgumentException("解密数据格式不正确:" + plainText); + } + + Map result = new HashMap<>(); + result.put("id", parts[0]); + result.put("email", parts[1]); + return result; + } +} + diff --git a/src/main/java/xin/merlin/myplayerbackend/utils/JwtUtil.java b/src/main/java/xin/merlin/myplayerbackend/utils/JwtUtil.java index 2d26235..bfea6d3 100644 --- a/src/main/java/xin/merlin/myplayerbackend/utils/JwtUtil.java +++ b/src/main/java/xin/merlin/myplayerbackend/utils/JwtUtil.java @@ -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 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) { diff --git a/src/main/java/xin/merlin/myplayerbackend/utils/SHA256Util.java b/src/main/java/xin/merlin/myplayerbackend/utils/SHA256Util.java new file mode 100644 index 0000000..55d7cb6 --- /dev/null +++ b/src/main/java/xin/merlin/myplayerbackend/utils/SHA256Util.java @@ -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); + } + } +} diff --git a/src/main/java/xin/merlin/myplayerbackend/utils/result/ResultCode.java b/src/main/java/xin/merlin/myplayerbackend/utils/result/ResultCode.java index c5b3df0..bf94157 100644 --- a/src/main/java/xin/merlin/myplayerbackend/utils/result/ResultCode.java +++ b/src/main/java/xin/merlin/myplayerbackend/utils/result/ResultCode.java @@ -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","无审核记录条目");