登录、注册接口,注册改为注册码机制

dev-active
lensfrex 2 years ago
parent 15baa50907
commit d154aab9ed
Signed by: lensfrex
GPG Key ID: 0F69A0A2FBEE98A0
  1. 143
      API-Document.md
  2. 45
      src/main/java/net/lensfrex/dscape/annotation/NotInvalidChar.java
  3. 2
      src/main/java/net/lensfrex/dscape/configure/MybatisPlusConfigure.java
  4. 57
      src/main/java/net/lensfrex/dscape/dao/entity/BlackList.java
  5. 57
      src/main/java/net/lensfrex/dscape/dao/entity/RegisterApplies.java
  6. 15
      src/main/java/net/lensfrex/dscape/dao/mapper/BlackListMapper.java
  7. 15
      src/main/java/net/lensfrex/dscape/dao/mapper/RegisterAppliesMapper.java
  8. 13
      src/main/java/net/lensfrex/dscape/dao/service/BlackListService.java
  9. 13
      src/main/java/net/lensfrex/dscape/dao/service/RegisterAppliesService.java
  10. 16
      src/main/java/net/lensfrex/dscape/dao/service/impl/BlackListServiceImpl.java
  11. 14
      src/main/java/net/lensfrex/dscape/dao/service/impl/RegisterAppliesServiceImpl.java
  12. 4
      src/main/java/net/lensfrex/dscape/dto/request/UserLoginRequestBody.java
  13. 9
      src/main/java/net/lensfrex/dscape/dto/request/user/RegisterRequestBody.java
  14. 6
      src/main/java/net/lensfrex/dscape/dto/response/general/Response.java
  15. 10
      src/main/java/net/lensfrex/dscape/dto/response/general/ResponseCode.java
  16. 20
      src/main/java/net/lensfrex/dscape/enums/user/BlackListTypeEnum.java
  17. 4
      src/main/java/net/lensfrex/dscape/enums/user/UserRoleEnum.java
  18. 20
      src/main/java/net/lensfrex/dscape/utils/NetworkUtil.java
  19. 57
      src/main/java/net/lensfrex/dscape/utils/validate/TextCheckUtil.java
  20. 19
      src/main/java/net/lensfrex/dscape/web/controllers/user/UserController.java
  21. 64
      src/main/java/net/lensfrex/dscape/web/controllers/user/admin/AdminController.java
  22. 103
      src/main/java/net/lensfrex/dscape/web/service/user/UserService.java
  23. 112
      src/main/java/net/lensfrex/dscape/web/service/user/admin/AdminService.java

@ -30,8 +30,7 @@
- [添加用户](#添加用户)
- [修改密码](#修改密码)
- [封禁、删除用户](#封禁删除用户)
- [管理员获取待审批注册用户](#管理员获取待审批注册用户)
- [审核用户注册](#审核用户注册)
- [管理员发放注册码](#管理员发放注册码)
---
@ -61,10 +60,10 @@
- 请求参数:
| 参数 | 值类型 | 可空 | 说明 |
| -------- | ------ | ----- | ------------------------------------------------ |
| 参数 | 值类型 | 可空 | 说明 |
| --------- | ------ | ----- | ------------------------------------------------ |
| user_name | string | false | 用户登录名,非空 |
| password | string | false | 用户登录密码,非空,其值为明文密码两次sha256加密 |
| password | string | false | 用户登录密码,非空,其值为明文密码两次sha256加密 |
例如:
``` json
@ -102,35 +101,33 @@
| code | 错误 |
| ----- | ---------------------- |
| 40301 | 用户名或密码错误 |
| 40302 | 用户已被封禁 |
| 40305 | 上级管理员未认证该用户 |
| 60101 | 用户名或密码错误 |
| 60102 | 用户已被封禁 |
| 60103 | 上级管理员未认证该用户 |
---
### 用户注册(公共)
> 用户注册可由管理员手动创建,也可以由用户自行注册,但是需要提供管理员名称,并且由管理员确认信息后才能使用
>
> 在系统中首次注册的用户即为管理员,管理员注册后普通用户必须要提供上级管理员才能注册
> 在系统中首次注册的用户即为管理员,管理员注册后普通用户必须要提供上级管理员提供的注册码才能注册
- URL路径:`/user/register`
- 方法:`POST`
- 请求参数:
| 参数 | 值类型 | 可空 | 说明 |
| -------- | ------ | ----- | ------------------------------------------------ |
| user_name | string | false | 用户登录名,非空 |
| password | string | false | 用户登录密码,非空,其值为明文密码两次sha256加密 |
| superior | string | true | 上级管理员用户名 |
| 参数 | 值类型 | 可空 | 说明 |
| ----------- | ------ | ----- | ------------------------------------------------ |
| user_name | string | false | 用户登录名,非空 |
| password | string | false | 用户登录密码,非空,其值为明文密码两次sha256加密 |
| invite_code | string | true | 上级管理员发放的注册码 |
例如:
``` json
{
"user_name": "admin",
"user_name": "lensfrex",
"password": "e723fb2ff93afb010960ac20c05439f1cdd1ecbb533947e7de9f43656a612052",
"superior": "lensfrex"
"invite_code": "asdf"
}
```
@ -156,7 +153,8 @@
| code | 错误 |
| ----- | ---------- |
| 40300 | 权限不足 |
| 20001 | 用户已存在 |
| 60201 | 用户已存在 |
| 60202 | 注册码不正确 |
> 管理员添加用户,详见[添加用户](#添加用户)
@ -382,12 +380,12 @@
- 请求参数:
| 参数 | 值类型 | 可空 | 说明 |
| -------- | -------------- | -------------------- | ------------------------------------------------ |
| 参数 | 值类型 | 可空 | 说明 |
| --------- | -------------- | -------------------- | ------------------------------------------------ |
| user_name | string | false | 用户登录名,非空 |
| password | string | false | 用户登录密码,非空,其值为明文密码两次sha256加密 |
| role | int | true | 用户角色 |
| token | string(header) | 管理员的access_token |
| password | string | false | 用户登录密码,非空,其值为明文密码两次sha256加密 |
| role | int | true | 用户角色 |
| token | string(header) | 管理员的access_token |
例如:
``` json
@ -474,7 +472,7 @@
### 封禁、删除用户
- URL路径:`/user/admin/modifyStatus/{uid}`
- URL路径:`/user/admin/modifyStatus/{uid}?status={status}`
- 方法:`POST`
- 请求参数:
@ -482,17 +480,9 @@
| 参数 | 值类型 | 可空 | 说明 |
| ------ | -------------- | ----------------- | ------------------------------------------------------------------------ |
| uid | string(url) | false | 欲修改状态的用户uid |
| status | int | false | 修改状态,0:正常;1:彻底封禁(无法登录);2:注销(销号);3:计算封禁 |
| status | int(url) | false | 修改状态,0:正常;1:彻底封禁(无法登录);2:注销(销号);3:计算封禁 |
| token | string(header) | 当前对应用户的uid |
例如:
``` json
{
"uid": "9a6d777c-997d-a7e5-35f4-8471b7582ac0",
"new_password": 1
}
```
- 返回数据
> data部分无返回数据
@ -514,24 +504,17 @@
---
### 管理员获取待审批注册用户
### 管理员发放注册码
- URL路径:`/user/admin/applications/list?offset={offset}&limit={limit}&page={page}`
- URL路径:`/user/admin/inviteCode?expired={expired}&count={count}`
- 方法:`GET`
- 请求参数:
| 参数 | 值类型 | 可空 | 说明 |
| ------ | -------------- | ----- | ------------------- |
| offset | int(url) | false | 分页数据偏移量 |
| limit | int(url) | false | 每页数据数量 |
| page | int(url) | false | 分页页码 |
| token | string(header) | false | 请求token,必须提供 |
例如,需要第三页数据,每页10条数据:
```
https://dscape.lensfrex.net/api/v1/user/admin/apply?offset=20&limit=10&page=3
```
| expired | long(url) | false | 过期时间,留空或设为0表示不过期,以时间戳为格式 |
| count | int(url) | false | 获取数量,留空只获取一条,每位管理员最大可申请1024条(有效的) |
- 返回数据
@ -539,84 +522,26 @@
| 参数 | 值类型 | 可空 | 说明 |
| ---- | ---------------- | ------------------ | ---- |
| id | int | 审批记录id |
| uid | string | 待审批用户uid |
| name | string | 待审批用户名 |
| ip | string | 用户发送审批时的ip |
| time | datetime(string) | 申请时间 |
例如:
``` json
{
"code": 20000,
"msg": "success",
"data" : [
{
"id": 64888,
"uid": "9a6d777c-997d-a7e5-35f4-8471b7582ac0",
"name": "lensfrex2",
"ip": "127.0.0.1",
"time": "2022-07-10 08:00:00"
},
{
"id": 64889,
"uid": "9a6d777c-997d-a7e5-35f4-8471b7582ac1",
"name": "lensfrex2",
"ip": "127.0.0.1",
"time": "2022-07-10 08:00:00"
},
"asdf",
"zxcv",
"qwer"
// ...
]
}
```
- 错误信息
| code | 错误 |
| ----- | -------- |
| 40300 | 权限不足 |
---
### 审核用户注册
- URL路径:`/user/admin/applications/deal/{uid}`
- 方法:`POST`
- 请求参数:
| 参数 | 值类型 | 可空 | 说明 |
| ----- | -------------- | ----------------- | --------------------------------- |
| uid | string(url) | false | 处理的用户uid |
| idea | boolean | false | 处理意见,true:通过;false:拒绝 |
| token | string(header) | 当前对应用户的uid |
例如:
``` json
{
"uid": "9a6d777c-997d-a7e5-35f4-8471b7582ac0",
"idea": true
}
```
- 返回数据
> data部分无返回数据
例如:
``` json
{
"code": 20000,
"msg": "success",
"data" : null
}
```
- 错误信息
| code | 错误 |
| ----- | -------- |
| 40300 | 权限不足 |
| 60401 | 申请数量超过上限 |
---
---

@ -0,0 +1,45 @@
///*
// * Class created by lensfrex.
// */
//
//package net.lensfrex.dscape.annotation;
//
//import net.lensfrex.dscape.utils.validate.TextCheckUtil;
//
//import java.lang.annotation.Documented;
//import java.lang.annotation.Repeatable;
//import java.lang.annotation.Retention;
//import java.lang.annotation.Target;
//
//import static java.lang.annotation.ElementType.*;
//import static java.lang.annotation.RetentionPolicy.RUNTIME;
//
//@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
//@Retention(RUNTIME)
//@Repeatable(NotInvalidChar.List.class)
//@Documented
//@Constraint(validatedBy = TextCheckUtil.class)//标明由哪个类执行校验逻辑
//public @interface NotInvalidChar {
// String message() default "value not in enum values.";
//
// Class<?>[] groups() default {};
//
//// Class<? extends Payload>[] payload() default {};
//
// /**
// * @return date must in this value array
// */
// String[] value();
//
// /**
// * Defines several {@link NotInvalidChar} annotations on the same element.
// *
// * @see NotInvalidChar
// */
// @Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
// @Retention(RUNTIME)
// @Documented
// @interface List {
// NotInvalidChar[] value();
// }
//}

@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.annotation.Resource;
@ -15,6 +16,7 @@ import javax.annotation.Resource;
*/
@Slf4j
@Configuration
@EnableTransactionManagement
public class MybatisPlusConfigure {
@Resource
DataSourceProperties dataSourceProperties;

@ -0,0 +1,57 @@
package net.lensfrex.dscape.dao.entity;
import lombok.Data;
import java.util.Date;
import java.util.List;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
/**
* @description black_list
* @author lensfrex
* @date 2022-08-19
*/
@Data
public class BlackList implements Serializable {
private static final long serialVersionUID = 1L;
@TableId(type = IdType.AUTO)
/**
* id
*/
private Integer id;
/**
* 黑名单数据类型0ip1uid
*/
private int type;
/**
* 黑名单条目名称可空
*/
private String name;
/**
* 黑名单数据
*/
private String data;
/**
* create_time
*/
private Date createTime;
/**
* edit_time
*/
private Date editTime;
/**
* is_deleted
*/
private int isDeleted;
public BlackList() {}
}

@ -0,0 +1,57 @@
package net.lensfrex.dscape.dao.entity;
import lombok.Data;
import java.time.LocalDateTime;
import java.io.Serializable;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
/**
* @description register_applies
* @author lensfrex
* @date 2022-08-18
*/
@Data
public class RegisterApplies implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 管理员uid
*/
private String admin;
/**
* 申请认证的用户
*/
private String applyUser;
/**
* 申请状态
*/
private int status;
/**
* create_time
*/
private LocalDateTime createTime;
/**
* edit_time
*/
private LocalDateTime editTime;
/**
* is_deleted
*/
private int isDeleted;
public RegisterApplies() {}
}

@ -0,0 +1,15 @@
package net.lensfrex.dscape.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import net.lensfrex.dscape.dao.entity.BlackList;
import java.util.List;
/**
* @description black_listMapper
* @author lensfrex
* @date 2022-08-19
*/
@Mapper
public interface BlackListMapper extends BaseMapper<BlackList> {
}

@ -0,0 +1,15 @@
package net.lensfrex.dscape.dao.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import net.lensfrex.dscape.dao.entity.RegisterApplies;
import java.util.List;
/**
* @description register_appliesMapper
* @author lensfrex
* @date 2022-08-18
*/
@Mapper
public interface RegisterAppliesMapper extends BaseMapper<RegisterApplies> {
}

@ -0,0 +1,13 @@
package net.lensfrex.dscape.dao.service;
import net.lensfrex.dscape.dao.entity.BlackList;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @description black_list服务层
* @author lensfrex
* @date 2022-08-19
*/
@Service
public interface BlackListService extends IService<BlackList> {
}

@ -0,0 +1,13 @@
package net.lensfrex.dscape.dao.service;
import net.lensfrex.dscape.dao.entity.RegisterApplies;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* @description register_applies服务层
* @author lensfrex
* @date 2022-08-18
*/
@Service
public interface RegisterAppliesService extends IService<RegisterApplies> {
}

@ -0,0 +1,16 @@
/*
* Class created by lensfrex.
*/
package net.lensfrex.dscape.dao.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.lensfrex.dscape.dao.entity.BlackList;
import net.lensfrex.dscape.dao.mapper.BlackListMapper;
import net.lensfrex.dscape.dao.service.BlackListService;
import org.springframework.stereotype.Service;
@Service
public class BlackListServiceImpl extends ServiceImpl<BlackListMapper, BlackList> implements BlackListService {
}

@ -0,0 +1,14 @@
/*
* Class created by lensfrex.
*/
package net.lensfrex.dscape.dao.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.lensfrex.dscape.dao.entity.RegisterApplies;
import net.lensfrex.dscape.dao.mapper.RegisterAppliesMapper;
import net.lensfrex.dscape.dao.service.RegisterAppliesService;
public class RegisterAppliesServiceImpl extends ServiceImpl<RegisterAppliesMapper, RegisterApplies> implements RegisterAppliesService {
}

@ -8,8 +8,8 @@ import lombok.Data;
@Data
public class UserLoginRequestBody {
private String password;
@JsonProperty("user_name")
private String userName;
private String password;
}

@ -1,10 +1,15 @@
package net.lensfrex.dscape.dto.request.user;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class RegisterRequestBody {
private String password;
private String superior;
@JsonProperty("user_name")
private String userName;
private String password;
@JsonProperty("invite_code")
private String inviteCode;
}

@ -36,8 +36,12 @@ public class Response<T> {
return success(null);
}
public static<T> Response<T> error(int code, String message) {
return new Response<>(code, message, null);
}
public static<T> Response<T> error(ResponseCode code, String message) {
return new Response<>(code.getCode(), message, null);
return error(code.getCode(), message);
}
public static<T> Response<T> error(ResponseCode code) {

@ -14,10 +14,14 @@ public enum ResponseCode {
SERVER_INTERNAL_ERROR(50000, "服务器内部错误"),
API_NOT_IMPLEMENT(0, "接口未实现"),
USERNAME_OR_PASSWORD_WRONG(40301, "用户名或密码不正确"),
USER_WAS_BANNED(40302, "用户已被封禁"),
USER_WAS_NOT_IDENTIFIED(40301, "上级管理员未认证该用户"),
USERNAME_OR_PASSWORD_WRONG(60101, "用户名或密码不正确"),
USER_WAS_BANNED(60102, "用户已被封禁"),
USER_WAS_NOT_IDENTIFIED(60103, "上级管理员未认证该用户"),
USER_NAME_EXISTS(60204, "用户名已经被使用"),
INVITE_CODE_WRONG(60202, "注册码不正确"),
INVITE_CODE_REACHED_LIMIT(60401, "注册码已达上限")
;
private final int code;

@ -0,0 +1,20 @@
package net.lensfrex.dscape.enums.user;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.Getter;
@Getter
public enum BlackListTypeEnum {
IP_ADDRESS(0, "IP地址"),
UID(1, "用户UID"),;
@EnumValue
private final int code;
private final String name;
BlackListTypeEnum(int code, String name) {
this.code = code;
this.name = name;
}
}

@ -5,8 +5,8 @@ import lombok.Getter;
@Getter
public enum UserRoleEnum {
ADMIN(1, "admin"),
NORMAL_USER(0, "normal");
NORMAL_USER(0, "normal"),
ADMIN(1, "admin");
@EnumValue
private final int code;

@ -0,0 +1,20 @@
package net.lensfrex.dscape.utils;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
@Scope("singleton")
public class NetworkUtil {
public static String getRealIP(HttpServletRequest request) {
String forward = request.getHeader("X-Forwarded-For");
if (forward != null) {
return forward.split(",")[0];
} else {
return request.getRemoteAddr();
}
}
}

@ -0,0 +1,57 @@
/*
* Class created by lensfrex.
*/
/*
* Class created by lensfrex.
*/
package net.lensfrex.dscape.utils.validate;
public class TextCheckUtil {
private static final String ABC_AND_CHINESE_REGEX = "[^a-zA-Z0-9\\_\\u4e00-\\u9fa5]";
private static final String ABC_REGEX = "[^a-zA-Z0-9\\_]";
// [^a-zA-Z0-9\_\[\];',./{}:"<>?/*\-\\!@#$%^&()\+\u4e00-\u9fa5]
private static final String ALL_CHARACTER_REGEX = "[^a-zA-Z0-9\\_\\[\\];',./{}:\"<>?/*\\-\\\\!@#$%^&()+=~`\\u4e00-\\u9fa5]";
/**
* 检测非字母和中文符号下划线"_"除外
* @param source 待检测字符串
* @return 是否含有非字母字符
*/
public static boolean checkNoneAbcAndChinese(String source) {
return !source.matches(ABC_AND_CHINESE_REGEX);
}
/**
* 批量检测非字母和中文符号下划线"_"除外
* @param source 待检测字符串
* @return 是否含有非字母字符
*/
public static boolean checkNoneAbcAndChinese(String... source) {
for (String text : source) {
if (text.matches(ABC_AND_CHINESE_REGEX)) {
return true;
}
}
return false;
}
public static boolean checkNoneCharacter(String source) {
return !source.matches(ALL_CHARACTER_REGEX);
}
public static boolean checkNoneCharacter(String... source) {
for (String text : source) {
if (text.matches(ALL_CHARACTER_REGEX)) {
return true;
}
}
return false;
}
}

@ -14,9 +14,15 @@ import net.lensfrex.dscape.dto.response.general.Response;
import net.lensfrex.dscape.dto.response.general.ResponseCode;
import net.lensfrex.dscape.dto.response.user.RegisterResponseBody;
import net.lensfrex.dscape.exception.GlobalException;
import net.lensfrex.dscape.utils.NetworkUtil;
import net.lensfrex.dscape.utils.validate.TextCheckUtil;
import net.lensfrex.dscape.web.service.user.UserService;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import java.net.http.HttpClient;
@JsonSerialize(using = ToStringSerializer.class)
@RestController
@RequestMapping("/user")
@ -41,17 +47,22 @@ public class UserController {
}
@GetMapping(value = "/checkLogin", produces = "application/json")
public Response<Boolean> testLogin() {
return Response.success(StpUtil.isLogin());
public Response<SaTokenInfo> testLogin() {
return Response.success(userService.checkLogin());
}
@PostMapping(value = "/register", produces = "application/json")
public Response<RegisterResponseBody> register(@RequestBody RegisterRequestBody request) {
public Response<RegisterResponseBody> register(@RequestBody RegisterRequestBody request, HttpServletRequest httpServletRequest) {
if (request.getUserName() == null || request.getPassword() == null) {
throw new GlobalException(ResponseCode.PARAM_WRONG);
}
return Response.success(userService.register(request));
// 这里的密码也用checkNoneAbcAndChinese是因为正常从前端发来的话是sha256,实际上是可以任意符号的
if (TextCheckUtil.checkNoneAbcAndChinese(request.getUserName(), request.getPassword())) {
throw new GlobalException(ResponseCode.INVALID_REQUEST);
}
return Response.success(userService.register(request, NetworkUtil.getRealIP(httpServletRequest)));
}
@PostMapping(value = "/modifyPassword/{uid}", produces = "application/json")

@ -4,35 +4,71 @@
package net.lensfrex.dscape.web.controllers.user.admin;
import cn.dev33.satoken.stp.StpUtil;
import lombok.extern.slf4j.Slf4j;
import net.lensfrex.dscape.dto.response.general.Response;
import net.lensfrex.dscape.dto.response.general.ResponseCode;
import net.lensfrex.dscape.enums.user.UserRoleEnum;
import net.lensfrex.dscape.exception.GlobalException;
import net.lensfrex.dscape.web.service.user.admin.AdminService;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/user/admin")
public class AdminController {
private final AdminService adminService;
public AdminController(AdminService adminService) {
this.adminService = adminService;
}
@PostMapping(value = "/add", produces = "application/json")
public Response<Object> addUser(@RequestBody String body,
@RequestHeader String token) {
return Response.error(ResponseCode.API_NOT_IMPLEMENT);
}
@PostMapping(value = "/modifyStatus/{uid}", produces = "application/json")
public Response modifyStatus(@RequestBody String body, @PathVariable String uid,
@RequestHeader String token) {
return Response.error(ResponseCode.API_NOT_IMPLEMENT);
}
@PostMapping(value = "/modifyStatus", produces = "application/json")
public Response<Object> modifyStatus(@RequestParam String username, @RequestParam int status) {
if (!StpUtil.hasRole(String.valueOf(UserRoleEnum.ADMIN.getCode()))) {
throw new GlobalException(ResponseCode.PERMISSION_DENIED);
}
@GetMapping(value = "/application/list", produces = "application/json")
public Response listApplication(
@RequestParam String offset, @RequestParam String limit, @RequestParam String page,
@RequestHeader String token) {
return Response.error(ResponseCode.API_NOT_IMPLEMENT);
switch (status) {
case 0:
case 1:
adminService.modifyUserStatus(username, status);
break;
case 2:
adminService.deleteUser(username);
break;
case 3:
adminService.addUserToBlackList(username);
break;
default:
throw new GlobalException(ResponseCode.PARAM_WRONG);
}
return Response.success();
}
@PostMapping(value = "/application/deal/{uid}", produces = "application/json")
public Response dealApplication(@RequestBody String body, @PathVariable String uid,
@RequestHeader String token) {
return Response.error(ResponseCode.API_NOT_IMPLEMENT);
@GetMapping(value = "/inviteCode", produces = "application/json")
public Response<List<String>> generateInviteCodes(@RequestParam(required = false, defaultValue = "-1") long expired,
@RequestParam(required = false, defaultValue = "1") int count) {
if (!StpUtil.hasRole(String.valueOf(UserRoleEnum.ADMIN.getCode()))) {
throw new GlobalException(ResponseCode.PERMISSION_DENIED);
}
if (count > 1024) {
throw new GlobalException(ResponseCode.INVALID_REQUEST);
}
List<String> result = adminService.generateInviteCode(expired, count);
return Response.success(result);
}
}

@ -9,11 +9,14 @@ import cn.dev33.satoken.stp.StpUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import lombok.extern.slf4j.Slf4j;
import net.lensfrex.dscape.dao.entity.UserBasic;
import net.lensfrex.dscape.dao.entity.UserRole;
import net.lensfrex.dscape.dao.service.UserBasicService;
import net.lensfrex.dscape.dao.service.UserRoleService;
import net.lensfrex.dscape.dto.request.UserLoginRequestBody;
import net.lensfrex.dscape.dto.request.user.RegisterRequestBody;
import net.lensfrex.dscape.dto.response.general.ResponseCode;
import net.lensfrex.dscape.dto.response.user.RegisterResponseBody;
import net.lensfrex.dscape.enums.user.UserRoleEnum;
import net.lensfrex.dscape.enums.user.UserStatusEnum;
import net.lensfrex.dscape.exception.GlobalException;
import net.lensfrex.dscape.utils.ObjectJsonSerializer;
@ -21,6 +24,9 @@ import org.mindrot.jbcrypt.BCrypt;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Slf4j
@Service
@ -28,20 +34,30 @@ public class UserService {
private static final String USER_INFO_CACHE_KEY = "dscape:user:basic";
private static final String REGISTER_INVITE_CODE_KEY = "dscape:user:inviteCodes";
private final ObjectJsonSerializer objectJsonSerializer;
private final RedisTemplate<String, String> redis;
private final UserBasicService userBasicService;
private final UserRoleService userRoleService;
@Autowired
public UserService(ObjectJsonSerializer objectJsonSerializer,
RedisTemplate<String, String> redis,
UserBasicService userBasicService) {
UserBasicService userBasicService,
UserRoleService userRoleService) {
this.objectJsonSerializer = objectJsonSerializer;
this.redis = redis;
this.userBasicService = userBasicService;
this.userRoleService = userRoleService;
}
public SaTokenInfo checkLogin() {
return StpUtil.getTokenInfo();
}
public SaTokenInfo login(UserLoginRequestBody requestBody) {
@ -49,32 +65,37 @@ public class UserService {
String password = requestBody.getPassword();
UserBasic userBasic;
if (Boolean.TRUE.equals(redis.hasKey(USER_INFO_CACHE_KEY))) {
String data = (String) redis.opsForHash().get(USER_INFO_CACHE_KEY, username);
userBasic = objectJsonSerializer.deserialize(data, UserBasic.class);
// 找缓存
String cacheData = (String) redis.opsForHash().get(USER_INFO_CACHE_KEY, username);
if (cacheData != null) {
userBasic = objectJsonSerializer.deserialize(cacheData, UserBasic.class);
} else {
// 进数据库里边查
QueryWrapper<UserBasic> wrapper = new QueryWrapper<>();
wrapper.lambda()
// .select(UserBasic::getUid, UserBasic::getPassword, UserBasic::getUserName, UserBasic::getStatus)
.eq(UserBasic::getUserName, username)
.eq(UserBasic::getIsDeleted, 0);
userBasic = userBasicService.getOne(wrapper);
try {
redis.opsForHash().put(USER_INFO_CACHE_KEY, username, objectJsonSerializer.serialize(userBasic));
if (userBasic != null) {
redis.opsForHash().put(USER_INFO_CACHE_KEY, username, objectJsonSerializer.serialize(userBasic));
}
} catch (Exception e) {
log.info("将用户数据存入redis缓存时发生错误", e);
}
}
// 验证密码
if (userBasic != null && BCrypt.checkpw(password, userBasic.getPassword())) {
// 检查用户状态
if (userBasic.getStatus() == UserStatusEnum.BANNED.getCode()) {
throw new GlobalException(ResponseCode.USER_WAS_BANNED);
} else if (userBasic.getStatus() == UserStatusEnum.NOT_IDENTIFIED.getCode()) {
throw new GlobalException(ResponseCode.USER_WAS_NOT_IDENTIFIED);
}
StpUtil.login(requestBody.getUserName());
StpUtil.login(userBasic.getUid());
} else {
throw new GlobalException(ResponseCode.USERNAME_OR_PASSWORD_WRONG);
}
@ -82,7 +103,71 @@ public class UserService {
return StpUtil.getTokenInfo();
}
public RegisterResponseBody register(RegisterRequestBody requestBody) {
return new RegisterResponseBody();
private static final String ADMIN_INVITE_CODE_COUNTER_KEY = "dscape:admin:inviteCodeCounter:%s";
@Transactional
public RegisterResponseBody register(RegisterRequestBody requestBody, String registerIp) {
QueryWrapper<UserBasic> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(UserBasic::getUserName, requestBody.getUserName())
.eq(UserBasic::getIsDeleted, 0);
// 检查用户名是否被占用
if (userBasicService.count(wrapper) != 0) {
throw new GlobalException(ResponseCode.USER_NAME_EXISTS);
}
RegisterResponseBody registerResponseBody = new RegisterResponseBody();
// 注册码验证
String inviteCode = requestBody.getInviteCode();
if (inviteCode == null) {
// 验证是否注册为初始管理员
if (userBasicService.count() != 0) {
throw new GlobalException(ResponseCode.PERMISSION_DENIED);
}
String uid = addUserToDatabase(requestBody.getUserName(), requestBody.getPassword(), "null", UserRoleEnum.ADMIN, registerIp);
registerResponseBody.setUid(uid);
} else {
String superior = (String) redis.opsForHash().get(REGISTER_INVITE_CODE_KEY, inviteCode);
if (superior == null) {
throw new GlobalException(ResponseCode.INVITE_CODE_WRONG);
}
String uid = addUserToDatabase(requestBody.getUserName(), requestBody.getPassword(), superior, UserRoleEnum.NORMAL_USER, registerIp);
registerResponseBody.setUid(uid);
redis.opsForHash().delete(REGISTER_INVITE_CODE_KEY, inviteCode);
redis.opsForValue().decrement(String.format(ADMIN_INVITE_CODE_COUNTER_KEY, superior));
}
return registerResponseBody;
}
/**
* 添加一个用户到数据库
* @param userName 用户名
* @param password 密码未用bcrypt加密的
* @param superior 上级管理员本身是管理员时可为空
* @param userRoleEnum 用户角色
* @return 用户uid
*/
private String addUserToDatabase(String userName, String password, String superior, UserRoleEnum userRoleEnum, String userIp) {
UserBasic userBasic = new UserBasic();
String uid = UUID.randomUUID().toString();
userBasic.setUid(uid);
userBasic.setPassword(BCrypt.hashpw(password, BCrypt.gensalt()));
userBasic.setUserName(userName);
userBasic.setStatus(UserStatusEnum.NORMAL.getCode());
userBasic.setSuperior(superior);
userBasic.setCreateIp(userIp);
userBasicService.save(userBasic);
UserRole userRole = new UserRole();
userRole.setRole(userRoleEnum.getCode());
userRole.setUid(uid);
userRoleService.save(userRole);
return uid;
}
}

@ -0,0 +1,112 @@
/*
* Class created by lensfrex.
*/
package net.lensfrex.dscape.web.service.user.admin;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaFoxUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import net.lensfrex.dscape.dao.entity.BlackList;
import net.lensfrex.dscape.dao.entity.UserBasic;
import net.lensfrex.dscape.dao.service.BlackListService;
import net.lensfrex.dscape.dao.service.UserBasicService;
import net.lensfrex.dscape.dto.response.general.ResponseCode;
import net.lensfrex.dscape.enums.user.BlackListTypeEnum;
import net.lensfrex.dscape.exception.GlobalException;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@Service
public class AdminService {
private final RedisTemplate<String, String> redis;
private final UserBasicService userBasicService;
private final BlackListService blackListService;
public AdminService(RedisTemplate<String, String> redis,
UserBasicService userBasicService,
BlackListService blackListService) {
this.redis = redis;
this.userBasicService = userBasicService;
this.blackListService = blackListService;
}
private static final int INVITE_CODE_LENGTH = 6;
private static final String REGISTER_INVITE_CODE_KEY = "dscape:user:inviteCodes";
private static final String ADMIN_INVITE_CODE_COUNTER_KEY = "dscape:admin:inviteCodeCounter:%s";
public List<String> generateInviteCode(long expired, int count) {
String adminUid = StpUtil.getLoginIdAsString();
String adminInviteCodeCounterKey = String.format(ADMIN_INVITE_CODE_COUNTER_KEY, adminUid);
String countResult = redis.opsForValue().get(adminInviteCodeCounterKey);
if (countResult != null && Integer.parseInt(countResult) > 1024) {
throw new GlobalException(ResponseCode.INVITE_CODE_REACHED_LIMIT);
}
List<String> inviteCodes = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
inviteCodes.add(SaFoxUtil.getRandomString(INVITE_CODE_LENGTH));
}
HashMap<String, String> codeAdmin = new HashMap<>(count);
inviteCodes.forEach(inviteCode -> codeAdmin.put(inviteCode, adminUid));
redis.opsForHash().putAll(REGISTER_INVITE_CODE_KEY, codeAdmin);
redis.opsForValue().increment(adminInviteCodeCounterKey, count);
return inviteCodes;
}
private static final String USER_INFO_CACHE_KEY = "dscape:user:basic";
public void modifyUserStatus(String username, int status) {
UpdateWrapper<UserBasic> wrapper = new UpdateWrapper<>();
wrapper.lambda()
.eq(UserBasic::getUserName, username)
.eq(UserBasic::getSuperior, StpUtil.getLoginIdAsString())
.set(UserBasic::getStatus, status);
boolean success = userBasicService.update(wrapper);
if (success) {
this.clearUserBasicCache(username);
}
}
public void addUserToBlackList(String username) {
BlackList blackList = new BlackList();
blackList.setData(username);
blackList.setType(BlackListTypeEnum.UID.getCode());
boolean success = blackListService.save(blackList);
}
public void deleteUser(String username) {
UpdateWrapper<UserBasic> wrapper = new UpdateWrapper<>();
wrapper.lambda()
.eq(UserBasic::getUserName, username)
.set(UserBasic::getIsDeleted, 1);
boolean success = userBasicService.update(wrapper);
if (success) {
this.clearUserBasicCache(username);
}
}
private void clearUserBasicCache(String username) {
redis.opsForHash().delete(USER_INFO_CACHE_KEY, username);
}
}
Loading…
Cancel
Save