完成子账户接口设计
This commit is contained in:
parent
48acb638e7
commit
fa630a1d89
@ -56,7 +56,8 @@ public class SecurityConfiguration {
|
|||||||
.requestMatchers("/api/auth/**", "/error").permitAll()
|
.requestMatchers("/api/auth/**", "/error").permitAll()
|
||||||
.requestMatchers("/monitor/**").permitAll()
|
.requestMatchers("/monitor/**").permitAll()
|
||||||
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()
|
||||||
.anyRequest().hasAnyRole(Const.ROLE_DEFAULT)
|
.requestMatchers("/api/user/sub/**").hasRole(Const.ROLE_ADMIN)
|
||||||
|
.anyRequest().hasAnyRole(Const.ROLE_ADMIN, Const.ROLE_NORMAL)
|
||||||
)
|
)
|
||||||
.formLogin(conf -> conf
|
.formLogin(conf -> conf
|
||||||
.loginProcessingUrl("/api/auth/login")
|
.loginProcessingUrl("/api/auth/login")
|
||||||
|
@ -2,12 +2,16 @@ package com.example.controller;
|
|||||||
|
|
||||||
import com.example.entity.RestBean;
|
import com.example.entity.RestBean;
|
||||||
import com.example.entity.vo.request.ChangePasswordVO;
|
import com.example.entity.vo.request.ChangePasswordVO;
|
||||||
|
import com.example.entity.vo.request.CreateSubAccountVO;
|
||||||
|
import com.example.entity.vo.response.SubAccountVO;
|
||||||
import com.example.service.AccountService;
|
import com.example.service.AccountService;
|
||||||
import com.example.utils.Const;
|
import com.example.utils.Const;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/user")
|
@RequestMapping("/api/user")
|
||||||
public class UserController {
|
public class UserController {
|
||||||
@ -22,4 +26,23 @@ public class UserController {
|
|||||||
RestBean.success() : RestBean.failure(401, "原密码输入错误!");
|
RestBean.success() : RestBean.failure(401, "原密码输入错误!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/sub/create")
|
||||||
|
public RestBean<Void> createSubAccount(@RequestBody @Valid CreateSubAccountVO vo) {
|
||||||
|
service.createSubAccount(vo);
|
||||||
|
return RestBean.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sub/delete")
|
||||||
|
public RestBean<Void> deleteSubAccount(int uid,
|
||||||
|
@RequestAttribute(Const.ATTR_USER_ID) int userId) {
|
||||||
|
if(uid == userId)
|
||||||
|
return RestBean.failure(401, "非法参数");
|
||||||
|
service.deleteSubAccount(uid);
|
||||||
|
return RestBean.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sub/list")
|
||||||
|
public RestBean<List<SubAccountVO>> subAccountList() {
|
||||||
|
return RestBean.success(service.listSubAccount());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,4 +23,5 @@ public class Account implements BaseData {
|
|||||||
String email;
|
String email;
|
||||||
String role;
|
String role;
|
||||||
Date registerTime;
|
Date registerTime;
|
||||||
|
String clients;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.example.entity.vo.request;
|
||||||
|
|
||||||
|
import jakarta.validation.constraints.Email;
|
||||||
|
import jakarta.validation.constraints.Size;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CreateSubAccountVO {
|
||||||
|
@Length(min = 1, max = 10)
|
||||||
|
String username;
|
||||||
|
@Email
|
||||||
|
String email;
|
||||||
|
@Length(min = 6, max = 20)
|
||||||
|
String password;
|
||||||
|
@Size(min = 1)
|
||||||
|
List<Integer> clients;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package com.example.entity.vo.response;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SubAccountVO {
|
||||||
|
int id;
|
||||||
|
String username;
|
||||||
|
String email;
|
||||||
|
JSONArray clientList;
|
||||||
|
}
|
@ -19,6 +19,7 @@ import org.springframework.stereotype.Component;
|
|||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于对请求头中Jwt令牌进行校验的工具,为当前请求添加用户验证信息
|
* 用于对请求头中Jwt令牌进行校验的工具,为当前请求添加用户验证信息
|
||||||
@ -60,6 +61,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
request.setAttribute(Const.ATTR_USER_ID, utils.toId(jwt));
|
request.setAttribute(Const.ATTR_USER_ID, utils.toId(jwt));
|
||||||
|
request.setAttribute(Const.ATTR_USER_ROLE, new ArrayList<>(user.getAuthorities()).get(0).getAuthority());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
filterChain.doFilter(request, response);
|
filterChain.doFilter(request, response);
|
||||||
|
@ -3,13 +3,20 @@ package com.example.service;
|
|||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
import com.example.entity.dto.Account;
|
import com.example.entity.dto.Account;
|
||||||
import com.example.entity.vo.request.ConfirmResetVO;
|
import com.example.entity.vo.request.ConfirmResetVO;
|
||||||
|
import com.example.entity.vo.request.CreateSubAccountVO;
|
||||||
import com.example.entity.vo.request.EmailResetVO;
|
import com.example.entity.vo.request.EmailResetVO;
|
||||||
|
import com.example.entity.vo.response.SubAccountVO;
|
||||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
public interface AccountService extends IService<Account>, UserDetailsService {
|
public interface AccountService extends IService<Account>, UserDetailsService {
|
||||||
Account findAccountByNameOrEmail(String text);
|
Account findAccountByNameOrEmail(String text);
|
||||||
String registerEmailVerifyCode(String type, String email, String address);
|
String registerEmailVerifyCode(String type, String email, String address);
|
||||||
String resetEmailAccountPassword(EmailResetVO info);
|
String resetEmailAccountPassword(EmailResetVO info);
|
||||||
String resetConfirm(ConfirmResetVO info);
|
String resetConfirm(ConfirmResetVO info);
|
||||||
boolean changePassword(int id, String oldPass, String newPass);
|
boolean changePassword(int id, String oldPass, String newPass);
|
||||||
|
void createSubAccount(CreateSubAccountVO vo);
|
||||||
|
void deleteSubAccount(int uid);
|
||||||
|
List<SubAccountVO> listSubAccount();
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
package com.example.service.impl;
|
package com.example.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.example.entity.dto.Account;
|
import com.example.entity.dto.Account;
|
||||||
import com.example.entity.vo.request.ConfirmResetVO;
|
import com.example.entity.vo.request.ConfirmResetVO;
|
||||||
|
import com.example.entity.vo.request.CreateSubAccountVO;
|
||||||
import com.example.entity.vo.request.EmailResetVO;
|
import com.example.entity.vo.request.EmailResetVO;
|
||||||
|
import com.example.entity.vo.response.SubAccountVO;
|
||||||
import com.example.mapper.AccountMapper;
|
import com.example.mapper.AccountMapper;
|
||||||
import com.example.service.AccountService;
|
import com.example.service.AccountService;
|
||||||
import com.example.utils.Const;
|
import com.example.utils.Const;
|
||||||
@ -19,6 +22,8 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
|||||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@ -127,6 +132,35 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void createSubAccount(CreateSubAccountVO vo) {
|
||||||
|
Account account = this.findAccountByNameOrEmail(vo.getEmail());
|
||||||
|
if(account != null)
|
||||||
|
throw new IllegalArgumentException("该电子邮件已被注册");
|
||||||
|
account = this.findAccountByNameOrEmail(vo.getUsername());
|
||||||
|
if(account != null)
|
||||||
|
throw new IllegalArgumentException("该用户名已被注册");
|
||||||
|
account = new Account(null, vo.getUsername(), passwordEncoder.encode(vo.getPassword()),
|
||||||
|
vo.getEmail(), Const.ROLE_NORMAL, new Date(), JSONArray.copyOf(vo.getClients()).toJSONString());
|
||||||
|
this.save(account);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteSubAccount(int uid) {
|
||||||
|
this.removeById(uid);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SubAccountVO> listSubAccount() {
|
||||||
|
return this.list(Wrappers.<Account>query().eq("role", Const.ROLE_NORMAL))
|
||||||
|
.stream().map(account -> {
|
||||||
|
SubAccountVO vo = account.asViewObject(SubAccountVO.class);
|
||||||
|
vo.setClientList(JSONArray.parse(account.getClients()));
|
||||||
|
return vo;
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 移除Redis中存储的邮件验证码
|
* 移除Redis中存储的邮件验证码
|
||||||
* @param email 电邮
|
* @param email 电邮
|
||||||
|
@ -7,6 +7,8 @@ public final class Const {
|
|||||||
//JWT令牌
|
//JWT令牌
|
||||||
public final static String JWT_BLACK_LIST = "jwt:blacklist:";
|
public final static String JWT_BLACK_LIST = "jwt:blacklist:";
|
||||||
public final static String JWT_FREQUENCY = "jwt:frequency:";
|
public final static String JWT_FREQUENCY = "jwt:frequency:";
|
||||||
|
//用户
|
||||||
|
public final static String USER_BLACK_LIST = "user:blacklist:";
|
||||||
//请求频率限制
|
//请求频率限制
|
||||||
public final static String FLOW_LIMIT_COUNTER = "flow:counter:";
|
public final static String FLOW_LIMIT_COUNTER = "flow:counter:";
|
||||||
public final static String FLOW_LIMIT_BLOCK = "flow:block:";
|
public final static String FLOW_LIMIT_BLOCK = "flow:block:";
|
||||||
@ -18,10 +20,11 @@ public final class Const {
|
|||||||
public final static int ORDER_CORS = -102;
|
public final static int ORDER_CORS = -102;
|
||||||
//请求自定义属性
|
//请求自定义属性
|
||||||
public final static String ATTR_USER_ID = "userId";
|
public final static String ATTR_USER_ID = "userId";
|
||||||
|
public final static String ATTR_USER_ROLE = "userRole";
|
||||||
public final static String ATTR_CLIENT = "client";
|
public final static String ATTR_CLIENT = "client";
|
||||||
//消息队列
|
//消息队列
|
||||||
public final static String MQ_MAIL = "mail";
|
public final static String MQ_MAIL = "mail";
|
||||||
//用户角色
|
//用户角色
|
||||||
public final static String ROLE_DEFAULT = "admin";
|
public final static String ROLE_ADMIN = "admin";
|
||||||
|
public final static String ROLE_NORMAL = "user";
|
||||||
}
|
}
|
||||||
|
@ -109,6 +109,7 @@ public class JwtUtils {
|
|||||||
try {
|
try {
|
||||||
DecodedJWT verify = jwtVerifier.verify(token);
|
DecodedJWT verify = jwtVerifier.verify(token);
|
||||||
if(this.isInvalidToken(verify.getId())) return null;
|
if(this.isInvalidToken(verify.getId())) return null;
|
||||||
|
if(this.isInvalidUser(verify.getClaim("id").asInt())) return null;
|
||||||
Map<String, Claim> claims = verify.getClaims();
|
Map<String, Claim> claims = verify.getClaims();
|
||||||
return new Date().after(claims.get("exp").asDate()) ? null : verify;
|
return new Date().after(claims.get("exp").asDate()) ? null : verify;
|
||||||
} catch (JWTVerificationException e) {
|
} catch (JWTVerificationException e) {
|
||||||
@ -177,6 +178,14 @@ public class JwtUtils {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void deleteUser(int uid) {
|
||||||
|
template.opsForValue().set(Const.USER_BLACK_LIST + uid, "", expire, TimeUnit.HOURS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isInvalidUser(int uid){
|
||||||
|
return Boolean.TRUE.equals(template.hasKey(Const.USER_BLACK_LIST + uid));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证Token是否被列入Redis黑名单
|
* 验证Token是否被列入Redis黑名单
|
||||||
* @param uuid 令牌ID
|
* @param uuid 令牌ID
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {reactive, ref} from "vue";
|
import {reactive, ref} from "vue";
|
||||||
import {Lock, Switch} from "@element-plus/icons-vue";
|
import {Lock, Plus, Switch} from "@element-plus/icons-vue";
|
||||||
import {logout, post} from "@/net";
|
import {logout, post} from "@/net";
|
||||||
import {ElMessage} from "element-plus";
|
import {ElMessage} from "element-plus";
|
||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
@ -56,6 +56,8 @@ function resetPassword() {
|
|||||||
<div style="display: flex;gap: 10px">
|
<div style="display: flex;gap: 10px">
|
||||||
<div style="flex: 50%">
|
<div style="flex: 50%">
|
||||||
<div class="info-card">
|
<div class="info-card">
|
||||||
|
<div class="title"><i class="fa-solid fa-lock"></i> 修改密码</div>
|
||||||
|
<el-divider style="margin: 10px 0"/>
|
||||||
<el-form @validate="onValidate" :model="form" :rules="rules"
|
<el-form @validate="onValidate" :model="form" :rules="rules"
|
||||||
ref="formRef" style="margin: 20px" label-width="100">
|
ref="formRef" style="margin: 20px" label-width="100">
|
||||||
<el-form-item label="当前密码" prop="password">
|
<el-form-item label="当前密码" prop="password">
|
||||||
@ -81,7 +83,11 @@ function resetPassword() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-card" style="flex: 50%">
|
<div class="info-card" style="flex: 50%">
|
||||||
|
<div class="title"><i class="fa-solid fa-users"></i> 子用户管理</div>
|
||||||
|
<el-divider style="margin: 10px 0"/>
|
||||||
|
<el-empty :image-size="100" description="还没有任何子用户哦">
|
||||||
|
<el-button :icon="Plus" type="primary" plain>添加子用户</el-button>
|
||||||
|
</el-empty>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -91,5 +97,12 @@ function resetPassword() {
|
|||||||
border-radius: 7px;
|
border-radius: 7px;
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
background-color: var(--el-bg-color);
|
background-color: var(--el-bg-color);
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: dodgerblue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user