增加登录

soul2/encrypt
soul2 1 year ago
parent b122d6b1c6
commit 5e5fb9a07d
  1. 15
      src/main/java/cn/soul2/jyjc/admin/annotation/SkinLogin.java
  2. 45
      src/main/java/cn/soul2/jyjc/admin/config/CorsConfig.java
  3. 92
      src/main/java/cn/soul2/jyjc/admin/config/UserLoginStatusBean.java
  4. 24
      src/main/java/cn/soul2/jyjc/admin/config/WebMvcConfig.java
  5. 32
      src/main/java/cn/soul2/jyjc/admin/controller/AnswerController.java
  6. 47
      src/main/java/cn/soul2/jyjc/admin/controller/UserController.java
  7. 45
      src/main/java/cn/soul2/jyjc/admin/dto/AnswerDetailDTO.java
  8. 37
      src/main/java/cn/soul2/jyjc/admin/dto/AnswerSubmitDTO.java
  9. 30
      src/main/java/cn/soul2/jyjc/admin/dto/UserLoginDTO.java
  10. 21
      src/main/java/cn/soul2/jyjc/admin/dto/UserLoginOutPageDTO.java
  11. 25
      src/main/java/cn/soul2/jyjc/admin/dto/UserLogoutDTO.java
  12. 37
      src/main/java/cn/soul2/jyjc/admin/dto/UserPageDTO.java
  13. 45
      src/main/java/cn/soul2/jyjc/admin/dto/UserSaveDTO.java
  14. 8
      src/main/java/cn/soul2/jyjc/admin/entity/AnswerDetailsDO.java
  15. 16
      src/main/java/cn/soul2/jyjc/admin/entity/SubjectDO.java
  16. 2
      src/main/java/cn/soul2/jyjc/admin/entity/SubjectItemsDO.java
  17. 93
      src/main/java/cn/soul2/jyjc/admin/entity/UserDO.java
  18. 87
      src/main/java/cn/soul2/jyjc/admin/entity/UserLoginOutDO.java
  19. 63
      src/main/java/cn/soul2/jyjc/admin/interceptor/TokenInterceptor.java
  20. 16
      src/main/java/cn/soul2/jyjc/admin/mapper/UserLoginOutMapper.java
  21. 16
      src/main/java/cn/soul2/jyjc/admin/mapper/UserMapper.java
  22. 62
      src/main/java/cn/soul2/jyjc/admin/repository/IUserLoginOutRepository.java
  23. 54
      src/main/java/cn/soul2/jyjc/admin/repository/IUserRepository.java
  24. 99
      src/main/java/cn/soul2/jyjc/admin/repository/impl/UserLoginOutRepositoryImpl.java
  25. 60
      src/main/java/cn/soul2/jyjc/admin/repository/impl/UserRepositoryImpl.java
  26. 20
      src/main/java/cn/soul2/jyjc/admin/service/IAnswerService.java
  27. 46
      src/main/java/cn/soul2/jyjc/admin/service/IUserService.java
  28. 58
      src/main/java/cn/soul2/jyjc/admin/service/impl/AnswerServiceImpl.java
  29. 87
      src/main/java/cn/soul2/jyjc/admin/service/impl/UserServiceImpl.java
  30. 52
      src/main/java/cn/soul2/jyjc/admin/utils/AesUtils.java
  31. 33
      src/main/java/cn/soul2/jyjc/admin/utils/Md5Utils.java
  32. 10
      src/main/java/cn/soul2/jyjc/admin/utils/MybatisFastGenerator.java
  33. 35
      src/main/java/cn/soul2/jyjc/admin/utils/SaltUtils.java
  34. 38
      src/main/java/cn/soul2/jyjc/admin/vo/UserVO.java
  35. 7
      src/main/resources/application-cors.yml
  36. 1
      src/main/resources/application.yml

@ -0,0 +1,15 @@
package cn.soul2.jyjc.admin.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author Soul2
* @date 2024-04-02
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SkinLogin {
}

@ -5,10 +5,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @author Soul2
* @date 2024-03-25
@ -16,47 +12,14 @@ import java.util.List;
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Value("${cors.allow-origins}")
private String[] allowOriginArray;
/**
* 将数组转换为List进行动态添加
*/
private final List<String> allowOriginList = new ArrayList<>();
// private static boolean isLocalV4Address(String ipAddress) {
// // todo 为进行手机测试而增加的读取内网IP,正式上线时须注释掉
// String[] parts = ipAddress.split("\\.");
// // 判断是否是有效的IPv4地址并且属于局域网
// return parts.length == 4 && parts[0].equals("192") && parts[1].equals("168");
// }
@Value("${cors.allow-origin}")
private String[] allowOrigin;
@Override
public void addCorsMappings(CorsRegistry registry) {
allowOriginList.addAll(Arrays.asList(allowOriginArray));
// // todo 为进行手机测试而增加的读取内网IP,正式上线时须注释掉
// try {
// Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
// while (networkInterfaces.hasMoreElements()) {
// NetworkInterface networkInterface = networkInterfaces.nextElement();
// Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
// while (inetAddresses.hasMoreElements()) {
// InetAddress inetAddress = inetAddresses.nextElement();
// if (isLocalV4Address(inetAddress.getHostAddress())) {
// System.out.println("Local IPv4 Address: " + inetAddress.getHostAddress());
// allowOriginList.add("http://" + inetAddress.getHostAddress() + ":7620");
// }
// }
// }
// } catch (Exception e) {
// e.printStackTrace();
// }
// 打印 allowOriginList 到控制台
// System.out.println("allowOriginList: " + allowOriginList);
registry.addMapping("/**")
.allowedOrigins(allowOriginList.toArray(new String[0]))
.allowedHeaders("*")
.allowedOrigins(allowOrigin)
.allowCredentials(true)
.allowedMethods("POST")
.maxAge(3600);

@ -0,0 +1,92 @@
package cn.soul2.jyjc.admin.config;
import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.concurrent.ConcurrentHashMap;
@Component
public class UserLoginStatusBean {
private ConcurrentHashMap<String, UserLoginOutDO> loginStatusMap = new ConcurrentHashMap<>();
/**
* 缓存登录信息
*
* @param token token
* @param loginOutStatus 登录信息
*/
public void login(String token, UserLoginOutDO loginOutStatus) {
loginStatusMap.put(token, loginOutStatus);
}
/**
* 从缓存读取登录信息
*
* @param token token
* @return {@link UserLoginOutDO}
*/
public UserLoginOutDO getStatus(String token) {
UserLoginOutDO loginStatus = loginStatusMap.getOrDefault(token, null);
if (loginStatus.getLapseTime() != null) {
LocalDateTime now = LocalDateTime.now();
if (now.isAfter(loginStatus.getLapseTime())) {
logout(token);
return null;
}
}
return loginStatus;
}
/**
* 为key设置登出
*
* @param token token
* @return {@link Boolean}
*/
public Boolean logout(String token) {
if (StringUtils.isNotBlank(token) && loginStatusMap.containsKey(token)) {
UserLoginOutDO removedUser = loginStatusMap.remove(token);
// 返回true表示成功登出,返回false表示未成功登出
return removedUser != null;
}
return Boolean.TRUE;
}
public Boolean logoutByUserId(String userId) {
final String[] targetKey = {null};
loginStatusMap.forEach((k, v) -> {
if (targetKey[0] == null && v.getUserId().equals(userId)) {
targetKey[0] = k;
}
});
return logout(targetKey[0]);
}
public String getTokenByUsername(String username) {
final String[] token = {null};
loginStatusMap.forEach((k, v) -> {
if (token[0] == null && v.getUserName().equals(username)) {
token[0] = k;
}
});
return token[0];
}
public Boolean containsToken(String token) {
if (StringUtils.isBlank(token)) {
return Boolean.FALSE;
}
UserLoginOutDO loginStatus = loginStatusMap.getOrDefault(token, null);
if (loginStatus.getLapseTime() != null) {
LocalDateTime now = LocalDateTime.now();
if (now.isAfter(loginStatus.getLapseTime())) {
logout(token);
}
}
return loginStatusMap.containsKey(token);
}
}

@ -0,0 +1,24 @@
package cn.soul2.jyjc.admin.config;
import cn.soul2.jyjc.admin.interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 注册请求拦截器
*
* @author Soul2
* @date 2024-04-02
*/
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor())
// 拦截所有路径
.addPathPatterns("/**");
}
}

@ -0,0 +1,32 @@
package cn.soul2.jyjc.admin.controller;
import cn.soul2.jyjc.admin.dto.AnswerSubmitDTO;
import cn.soul2.jyjc.admin.service.IAnswerService;
import cn.soul2.jyjc.admin.utils.base.BackUtils;
import cn.soul2.jyjc.admin.vo.base.Back;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* @author Soul2
* @date 2024-03-27 13:24
*/
@Slf4j
@RestController
@RequestMapping("/answer")
public class AnswerController {
@Autowired
private IAnswerService answerService;
@PostMapping("submit")
public Back<Boolean> submit(@RequestBody AnswerSubmitDTO dto) {
return BackUtils.success(answerService.handleSubmit(dto));
}
}

@ -0,0 +1,47 @@
package cn.soul2.jyjc.admin.controller;
import cn.soul2.jyjc.admin.annotation.SkinLogin;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.service.IUserService;
import cn.soul2.jyjc.admin.utils.base.BackUtils;
import cn.soul2.jyjc.admin.vo.UserVO;
import cn.soul2.jyjc.admin.vo.base.Back;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
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;
/**
* @author Soul2
* @date 2024-03-31 15:48
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
@PostMapping("login")
@SkinLogin
public Back<UserVO> login(@RequestBody UserLoginDTO dto) {
return BackUtils.success(userService.login(dto));
}
@PostMapping("register")
@SkinLogin
public Back<UserVO> register(@RequestBody UserLoginDTO dto) {
return BackUtils.success(userService.register(dto));
}
@PostMapping("logout")
public Back<Boolean> logout(@RequestBody UserLogoutDTO dto) {
return BackUtils.success(userService.logout(dto));
}
}

@ -0,0 +1,45 @@
package cn.soul2.jyjc.admin.dto;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Soul2
* @date 2024-03-27 13:31
*/
@Data
@Accessors(chain = true)
public class AnswerDetailDTO {
/**
* 回答内容(文字/选项id,多选时以,分隔)
*/
private String content;
/**
* 得分
*/
private Integer points;
/**
* 题目id
*/
private String subjectId;
/**
* 题目类型:0-单选;1-多选;2-文字
*/
private Integer subjectType;
/**
* 题目分值
*/
private Integer subjectPoints;
/**
* 权重
*/
private Double weights;
}

@ -0,0 +1,37 @@
package cn.soul2.jyjc.admin.dto;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* @author Soul2
* @date 2024-03-27 13:26
*/
@Data
@Accessors(chain = true)
public class AnswerSubmitDTO {
/**
* 二维码id
*/
private String qrId;
/**
* 问卷Id
*/
private String qnId;
/**
* openid, 即答卷人
*/
private String openid;
/**
* 答题内容
*/
private List<AnswerDetailDTO> details;
}

@ -0,0 +1,30 @@
package cn.soul2.jyjc.admin.dto;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Soul2
* @date 2024-03-29 21:55
*/
@Data
@Accessors(chain = true)
public class UserLoginDTO {
/**
* userName
*/
private String username;
/**
* password
*/
private String password;
/**
* 密码错误次数(暂未使用)
*/
private Integer pswErrorCount;
}

@ -0,0 +1,21 @@
package cn.soul2.jyjc.admin.dto;
import cn.soul2.jyjc.admin.dto.base.PageParams;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Soul2
* @date 2024-03-29 21:50
*/
@Data
@Accessors(chain = true)
public class UserLoginOutPageDTO extends PageParams {
/**
* userId
*/
private String userId;
}

@ -0,0 +1,25 @@
package cn.soul2.jyjc.admin.dto;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Soul2
* @date 2024-03-29 21:56
*/
@Data
@Accessors(chain = true)
public class UserLogoutDTO {
/**
* userName
*/
private String username;
/**
* token
*/
private String token;
}

@ -0,0 +1,37 @@
package cn.soul2.jyjc.admin.dto;
import cn.soul2.jyjc.admin.dto.base.PageParams;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
/**
* @author Soul2
* @date 2024-03-29 20:50
*/
@Data
@Accessors(chain = true)
public class UserPageDTO extends PageParams {
/**
* id
*/
private String id;
/**
* 用户名
*/
private String username;
/**
* 状态;0(默认)正常4封号9异常
*/
private Integer status;
/**
* 注册时间
*/
private LocalDateTime startTime;
private LocalDateTime endTime;
}

@ -0,0 +1,45 @@
package cn.soul2.jyjc.admin.dto;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Soul2
* @date 2024-03-29 20:50
*/
@Data
@Accessors(chain = true)
public class UserSaveDTO {
/**
* id
*/
private String id;
/**
* 用户名
*/
private String username;
/**
* 密码
*/
private String password;
/**
* 状态;0(默认)正常4封号9异常
*/
private Integer status;
/**
* 权限等级,默认200
*/
private Integer authorityLevel;
/**
* 密码修改次数
*/
private Integer pswChange;
}

@ -15,7 +15,7 @@ import java.time.LocalDateTime;
* </p>
*
* @author Soul2
* @since 2024-03-12 10:10:24
* @since 2024-03-27 15:38:51
*/
@Getter
@Setter
@ -32,10 +32,10 @@ public class AnswerDetailsDO extends Model<AnswerDetailsDO> {
private String id;
/**
* 状态0禁用1启用
* 题目类型:0-单选;1-多选;2-文字
*/
@TableField("status")
private Short status;
@TableField("subject_type")
private Integer subjectType;
/**
* 更新时间

@ -15,7 +15,7 @@ import java.time.LocalDateTime;
* </p>
*
* @author Soul2
* @since 2024-03-12 10:10:24
* @since 2024-03-27 15:38:52
*/
@Getter
@Setter
@ -80,8 +80,20 @@ public class SubjectDO extends Model<SubjectDO> {
@TableField("type")
private Short type;
/**
* 权重
*/
@TableField("weights")
private Double weights;
/**
* 分值
*/
@TableField("points")
private Integer points;
@Override
public Serializable pkVal() {
return null;
return this.id;
}
}

@ -72,7 +72,7 @@ public class SubjectItemsDO extends Model<SubjectItemsDO> {
* 权重
*/
@TableField("weights")
private Object weights;
private Double weights;
/**
* 分值

@ -0,0 +1,93 @@
package cn.soul2.jyjc.admin.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 用户表
* </p>
*
* @author Soul2
* @since 2024-03-30 14:34:13
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("tb_user")
public class UserDO extends Model<UserDO> {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
/**
* 用户名
*/
@TableField("username")
private String username;
/**
* 密码
*/
@TableField("password")
private String password;
/**
* 状态;0(默认)正常4封号9异常
*/
@TableField("status")
private Integer status;
/**
* 权限等级,默认200
*/
@TableField("authority_level")
private Integer authorityLevel;
/**
* 密码修改次数
*/
@TableField("psw_change")
private Integer pswChange;
/**
* 更新时间
*/
@TableField(value = "updated_time", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 删除标记
*/
@TableField("removed")
@TableLogic
private Integer removed;
/**
* ,用于密码加密,未启用
*/
@TableField("salt")
private String salt;
@Override
public Serializable pkVal() {
return this.id;
}
}

@ -0,0 +1,87 @@
package cn.soul2.jyjc.admin.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
import java.io.Serializable;
import java.time.LocalDateTime;
/**
* <p>
* 用户登入登出表
* </p>
*
* @author Soul2
* @since 2024-03-30 13:57:00
*/
@Getter
@Setter
@Accessors(chain = true)
@TableName("tb_user_login_out")
public class UserLoginOutDO extends Model<UserLoginOutDO> {
private static final long serialVersionUID = 1L;
/**
* id
*/
@TableId(value = "id", type = IdType.ASSIGN_UUID)
private String id;
/**
* 用户id
*/
@TableField("user_id")
private String userId;
/**
* 用户名称
*/
@TableField("user_name")
private String userName;
/**
* 登录时间,登录成功才写入
*/
@TableField("login_time")
private LocalDateTime loginTime;
/**
* 登录失效时间
*/
@TableField("lapse_time")
private LocalDateTime lapseTime;
/**
* 用户操作登出时间
*/
@TableField("logout_time")
private LocalDateTime logoutTime;
/**
* 密码错误次数
*/
@TableField("psw_error_count")
private Integer pswErrorCount;
/**
* 创建时间
*/
@TableField(value = "created_time", fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 1=删除;0=未删除
*/
@TableField("removed")
@TableLogic
private Integer removed;
@Override
public Serializable pkVal() {
return this.id;
}
}

@ -0,0 +1,63 @@
package cn.soul2.jyjc.admin.interceptor;
import cn.soul2.jyjc.admin.annotation.SkinLogin;
import cn.soul2.jyjc.admin.config.UserLoginStatusBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author Soul2
* @date 2024-04-02 15:31
*/
public class TokenInterceptor implements HandlerInterceptor {
@Autowired
private UserLoginStatusBean userLoginStatusBean;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 允许OPTIONS请求通过
if (isCorsRequest(request)) {
return true;
}
// 跳过登录
// 如果处理器是一个方法处理器
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// 检查方法上是否存在SkinLogin注解
if (handlerMethod.getMethod().isAnnotationPresent(SkinLogin.class)) {
// 如果存在,绕过拦截器
return true;
}
}
// 验证token
// 从请求头中获取 token
String token = request.getHeader("jyjc-Token");
// 检查 token 是否存在并且有效,这里可以根据实际情况自行实现验证逻辑
if (token != null && isValidToken(token)) {
// Token 有效,允许请求通过
return true;
} else {
// Token 无效,拒绝请求,可以返回特定的响应状态码,例如 401 Unauthorized
response.setStatus(40401);
return false;
}
}
private boolean isValidToken(String token) {
// 实现 token 验证逻辑,例如验证 token 的签名或者在数据库中验证 token 的有效性
return userLoginStatusBean.containsToken(token);
// 返回 true 表示 token 有效,返回 false 表示 token 无效
}
private boolean isCorsRequest(HttpServletRequest request) {
// 检查请求头中是否包含 Origin 字段,如果存在,则认为是跨域请求
return request.getHeader("Origin") != null;
}
}

@ -0,0 +1,16 @@
package cn.soul2.jyjc.admin.mapper;
import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 用户登入登出表 Mapper 接口
* </p>
*
* @author Soul2
* @since 2024-03-29 20:46:42
*/
public interface UserLoginOutMapper extends BaseMapper<UserLoginOutDO> {
}

@ -0,0 +1,16 @@
package cn.soul2.jyjc.admin.mapper;
import cn.soul2.jyjc.admin.entity.UserDO;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 用户表 Mapper 接口
* </p>
*
* @author Soul2
* @since 2024-03-29 20:46:42
*/
public interface UserMapper extends BaseMapper<UserDO> {
}

@ -0,0 +1,62 @@
package cn.soul2.jyjc.admin.repository;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLoginOutPageDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.entity.UserDO;
import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 用户登入登出表 服务类
* </p>
*
* @author Soul2
* @since 2024-03-29 20:46:42
*/
public interface IUserLoginOutRepository extends IService<UserLoginOutDO> {
/**
* 分页查询
*
* @param dto 查询条件
* @return {@link IPage}<{@link UserLoginOutDO}>
*/
IPage<UserLoginOutDO> page(UserLoginOutPageDTO dto);
/**
* 用户登录
*
* @param dto 用户信息
* @param user 用户
* @return {@link String}
*/
String login(UserLoginDTO dto, UserDO user);
/**
* 用户登出
*
* @param dto 用户信息
* @return {@link String}
*/
String logout(UserLogoutDTO dto);
/**
* 用户登出
*
* @param userId 用户id
* @return {@link String}
*/
Boolean logoutByUserId(String userId);
/**
* 删除目标用户的所有登录记录
*
* @param userId 用户id
* @return {@link Boolean}
*/
Boolean removeByUserId(String userId);
}

@ -0,0 +1,54 @@
package cn.soul2.jyjc.admin.repository;
import cn.soul2.jyjc.admin.dto.UserPageDTO;
import cn.soul2.jyjc.admin.dto.UserSaveDTO;
import cn.soul2.jyjc.admin.dto.base.UpdateStatusDTO;
import cn.soul2.jyjc.admin.entity.UserDO;
import cn.soul2.jyjc.admin.vo.base.VPage;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 用户表 服务类
* </p>
*
* @author Soul2
* @since 2024-03-29 20:46:42
*/
public interface IUserRepository extends IService<UserDO> {
/**
* 分页查询
*
* @param dto 查询条件
* @return {@link VPage}<{@link UserDO}>
*/
IPage<UserDO> page(UserPageDTO dto);
/**
* 保存或更新
*
* @param dto /无id区分更新/新增
* @return {@link Boolean}
*/
Boolean saveOrUpdate(UserSaveDTO dto);
/**
* 更新状态
*
* @param dto 新状态
* @return {@link Boolean}
*/
Boolean updateStatus(UpdateStatusDTO dto);
/**
* 读取指定名字的用户
*
* @param username 用户名
* @return {@link UserDO}
*/
UserDO getOneByUsername(String username);
}

@ -0,0 +1,99 @@
package cn.soul2.jyjc.admin.repository.impl;
import cn.soul2.jyjc.admin.config.UserLoginStatusBean;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLoginOutPageDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.entity.UserDO;
import cn.soul2.jyjc.admin.entity.UserLoginOutDO;
import cn.soul2.jyjc.admin.mapper.UserLoginOutMapper;
import cn.soul2.jyjc.admin.repository.IUserLoginOutRepository;
import cn.soul2.jyjc.admin.utils.base.PageUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
/**
* <p>
* 用户登入登出表 服务实现类
* </p>
*
* @author Soul2
* @since 2024-03-29 20:46:42
*/
@Service
public class UserLoginOutRepositoryImpl extends ServiceImpl<UserLoginOutMapper, UserLoginOutDO> implements IUserLoginOutRepository {
@Autowired
private UserLoginStatusBean userLoginStatusBean;
@Override
public IPage<UserLoginOutDO> page(UserLoginOutPageDTO dto) {
LambdaQueryWrapper<UserLoginOutDO> query = Wrappers.lambdaQuery();
query.eq(StringUtils.isNotBlank(dto.getUserId()), UserLoginOutDO::getUserId, dto.getUserId())
.orderByDesc(UserLoginOutDO::getCreatedTime);
return super.page(PageUtils.build(dto), query);
}
@Override
public String login(UserLoginDTO dto, UserDO user) {
UserLoginOutDO loginOutDO;
boolean saved;
LocalDateTime now = LocalDateTime.now();
// 判断是否登陆过,在缓存无法读取到目标用户名时即未登录,若登录过则令之前的登录记录失效
String token = userLoginStatusBean.getTokenByUsername(dto.getUsername());
if (token != null) {
userLoginStatusBean.logout(token);
UserLoginOutDO oldLoginOut = super.getById(token);
oldLoginOut.setLapseTime(now).setLogoutTime(now);
super.updateById(oldLoginOut);
}
loginOutDO = new UserLoginOutDO();
loginOutDO.setUserId(user.getId())
.setUserName(user.getUsername())
.setLoginTime(now)
.setLapseTime(now.plusHours(2))
;
saved = super.save(loginOutDO);
if (saved) {
userLoginStatusBean.login(loginOutDO.getId(), loginOutDO);
return loginOutDO.getId();
}
return null;
}
@Override
public String logout(UserLogoutDTO dto) {
// 清除缓存内的登录信息
Boolean logout = userLoginStatusBean.logout(dto.getToken());
// 保存退出登录的时间
LocalDateTime now = LocalDateTime.now();
UserLoginOutDO oldLoginOut = super.getById(dto.getToken());
oldLoginOut.setLogoutTime(now);
super.updateById(oldLoginOut);
return logout ? dto.getUsername() : null;
}
@Override
public Boolean logoutByUserId(String userId) {
return userLoginStatusBean.logoutByUserId(userId);
}
@Override
public Boolean removeByUserId(String userId) {
if (org.apache.commons.lang3.StringUtils.isBlank(userId)) {
return Boolean.TRUE;
}
LambdaUpdateWrapper<UserLoginOutDO> update = Wrappers.lambdaUpdate();
update.eq(UserLoginOutDO::getUserId, userId);
return super.remove(update);
}
}

@ -0,0 +1,60 @@
package cn.soul2.jyjc.admin.repository.impl;
import cn.soul2.jyjc.admin.dto.UserPageDTO;
import cn.soul2.jyjc.admin.dto.UserSaveDTO;
import cn.soul2.jyjc.admin.dto.base.UpdateStatusDTO;
import cn.soul2.jyjc.admin.entity.UserDO;
import cn.soul2.jyjc.admin.mapper.UserMapper;
import cn.soul2.jyjc.admin.repository.IUserRepository;
import cn.soul2.jyjc.admin.utils.base.PageUtils;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
/**
* <p>
* 用户表 服务实现类
* </p>
*
* @author Soul2
* @since 2024-03-29 20:46:42
*/
@Service
public class UserRepositoryImpl extends ServiceImpl<UserMapper, UserDO> implements IUserRepository {
@Override
public IPage<UserDO> page(UserPageDTO dto) {
LambdaQueryWrapper<UserDO> query = Wrappers.lambdaQuery();
query.eq(dto.getStatus() != null, UserDO::getStatus, dto.getStatus())
.like(StringUtils.isNotBlank(dto.getUsername()), UserDO::getUsername, dto.getUsername())
.orderByDesc(UserDO::getUpdatedTime);
return super.page(PageUtils.build(dto), query);
}
@Override
public Boolean saveOrUpdate(UserSaveDTO dto) {
UserDO user = new UserDO();
BeanUtils.copyProperties(dto, user);
return super.saveOrUpdate(user);
}
@Override
public Boolean updateStatus(UpdateStatusDTO dto) {
LambdaUpdateWrapper<UserDO> update = Wrappers.lambdaUpdate();
update.set(UserDO::getStatus, dto.getStatus())
.eq(UserDO::getId, dto.getId());
return super.update(update);
}
@Override
public UserDO getOneByUsername(String username) {
LambdaQueryWrapper<UserDO> query = Wrappers.lambdaQuery();
query.eq(UserDO::getUsername, username);
return super.getOne(query, false);
}
}

@ -0,0 +1,20 @@
package cn.soul2.jyjc.admin.service;
import cn.soul2.jyjc.admin.dto.AnswerSubmitDTO;
/**
* @author Soul2
* @date 2024-03-27 14:43
*/
public interface IAnswerService {
/**
* 提交答卷
*
* @param dto 答卷内容
* @return {@link Boolean}
*/
Boolean handleSubmit(AnswerSubmitDTO dto);
}

@ -0,0 +1,46 @@
package cn.soul2.jyjc.admin.service;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.vo.UserVO;
/**
* @author Soul2
* @date 2024-03-29 20:47
*/
public interface IUserService {
/**
* 注册一个用户
*
* @param dto 用户名和密码
* @return {@link UserVO}
*/
UserVO register(UserLoginDTO dto);
/**
* 用户登录
*
* @param dto 用户名和密码
* @return {@link UserVO}
*/
UserVO login(UserLoginDTO dto);
/**
* 用户退出登录
*
* @param dto 登录key
* @return {@link Boolean}
*/
Boolean logout(UserLogoutDTO dto);
/**
* 删除用户并退出登录,同时将登录记录一并删除
*
* @param userId 用户id
* @return {@link Boolean}
*/
Boolean remove(String userId);
}

@ -0,0 +1,58 @@
package cn.soul2.jyjc.admin.service.impl;
import cn.soul2.jyjc.admin.dto.AnswerSubmitDTO;
import cn.soul2.jyjc.admin.entity.AnswerDetailsDO;
import cn.soul2.jyjc.admin.entity.AnswerSheetDO;
import cn.soul2.jyjc.admin.repository.IAnswerDetailsRepository;
import cn.soul2.jyjc.admin.repository.IAnswerSheetRepository;
import cn.soul2.jyjc.admin.service.IAnswerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Soul2
* @date 2024-03-27 14:43
*/
@Service
public class AnswerServiceImpl implements IAnswerService {
@Autowired
private IAnswerSheetRepository sheetRepository;
@Autowired
private IAnswerDetailsRepository detailsRepository;
@Override
@Transactional
public Boolean handleSubmit(AnswerSubmitDTO dto) {
AnswerSheetDO sheet = new AnswerSheetDO();
sheet.setRespondent(dto.getOpenid())
.setQnId(dto.getQnId())
.setQrId(dto.getQrId());
boolean sheetSaved = sheetRepository.save(sheet);
if (sheetSaved) {
List<AnswerDetailsDO> details = dto.getDetails().stream().map(d -> {
AnswerDetailsDO detail = new AnswerDetailsDO();
detail.setAnswerContent(d.getContent())
.setAnswerSheetId(sheet.getId())
.setSubjectType(d.getSubjectType())
.setSubjectId(d.getSubjectId());
return detail;
}).collect(Collectors.toList());
boolean detailsSaved = detailsRepository.saveBatch(details);
if (!detailsSaved) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return Boolean.FALSE;
}
} else {
return Boolean.FALSE;
}
return Boolean.TRUE;
}
}

@ -0,0 +1,87 @@
package cn.soul2.jyjc.admin.service.impl;
import cn.soul2.jyjc.admin.dto.UserLoginDTO;
import cn.soul2.jyjc.admin.dto.UserLogoutDTO;
import cn.soul2.jyjc.admin.entity.UserDO;
import cn.soul2.jyjc.admin.repository.IUserLoginOutRepository;
import cn.soul2.jyjc.admin.repository.IUserRepository;
import cn.soul2.jyjc.admin.service.IUserService;
import cn.soul2.jyjc.admin.utils.Md5Utils;
import cn.soul2.jyjc.admin.vo.UserVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* @author Soul2
* @date 2024-03-29 20:47
*/
@Service
public class UserServiceImpl implements IUserService {
@Autowired
private IUserRepository userRepository;
@Autowired
private IUserLoginOutRepository loginOutRepository;
@Override
public UserVO register(UserLoginDTO dto) {
UserVO vo = new UserVO();
if (userRepository.getOneByUsername(dto.getUsername()) == null) {
UserDO user = new UserDO();
user.setUsername(dto.getUsername())
.setPassword(Md5Utils.md5(dto.getPassword()));
boolean saved = userRepository.save(user);
if (!saved) {
vo.setLoginFail("注册失败!");
} else {
String loginKey = loginOutRepository.login(dto, user);
if (loginKey != null) {
BeanUtils.copyProperties(user, vo);
vo.setToken(loginKey);
} else {
vo.setLoginFail("注册成功但登录失败!");
}
}
} else {
vo.setLoginFail("用户名已存在!");
}
return vo;
}
@Override
public UserVO login(UserLoginDTO dto) {
UserVO vo = new UserVO();
UserDO user = userRepository.getOneByUsername(dto.getUsername());
if (user != null) {
if (Md5Utils.verify(user.getPassword(), dto.getPassword())) {
String loginKey = loginOutRepository.login(dto, user);
if (loginKey != null) {
BeanUtils.copyProperties(user, vo);
vo.setToken(loginKey);
} else {
vo.setLoginFail("登录失败!");
}
} else {
vo.setLoginFail("密码错误!");
}
} else {
vo.setLoginFail("该用户尚未注册!");
}
return vo;
}
@Override
public Boolean logout(UserLogoutDTO dto) {
return loginOutRepository.logout(dto) != null;
}
@Override
public Boolean remove(String userId) {
loginOutRepository.logoutByUserId(userId);
loginOutRepository.removeByUserId(userId);
return userRepository.removeById(userId);
}
}

@ -0,0 +1,52 @@
package cn.soul2.jyjc.admin.utils;
import org.springframework.beans.factory.annotation.Value;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
/**
* @author Soul2
* @date 2024-03-30 14:02
*/
public class AesUtils {
@Value("${encrypt.keys.aes}")
private String aesKey;
private static final String AES_ALGORITHM = "AES";
private SecretKeySpec generateKey(String key) {
return new SecretKeySpec(key.getBytes(), AES_ALGORITHM);
}
public String encrypt(String source) throws Exception {
return encrypt(source, aesKey);
}
public String decrypt(String encrypted) throws Exception {
return decrypt(encrypted, aesKey);
}
private String encrypt(String source, String key) throws Exception {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, generateKey(key));
byte[] encryptedBytes = cipher.doFinal(source.getBytes());
return Base64.getEncoder().encodeToString(encryptedBytes);
}
private String decrypt(String encrypted, String key) throws Exception {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, generateKey(key));
byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encrypted));
return new String(decryptedBytes);
}
public static void main(String[] args) {
}
}

@ -0,0 +1,33 @@
package cn.soul2.jyjc.admin.utils;
import org.springframework.util.DigestUtils;
import java.nio.charset.StandardCharsets;
import java.util.Objects;
/**
* @author fuzongyao
* @date 2021/10/14 11:19
* @since 1.0
*/
public class Md5Utils {
/**
* 加盐
*/
private static final String SALT = "4v6dKAOn+tEiVH58/XKeUw==";
public static String md5(String body) {
body += SALT;
return DigestUtils.md5DigestAsHex(body.getBytes(StandardCharsets.UTF_8)).toUpperCase();
}
public static boolean verify(String md5, String body) {
return Objects.equals(md5, md5(body));
}
public static void main(String[] args) {
System.out.println(md5("123456"));
}
}

@ -19,16 +19,16 @@ public class MybatisFastGenerator {
private static final DatasourceConfig DATASOURCE_CONFIG = new DatasourceConfig(
"Soul2",
"jdbc:mysql://localhost:3306/pioneer?characterEncoding=utf8&serverTimezone=GMT%2B8",
"root",
"123456"
"jdbc:mysql://soul2.cn:3306/thli_upline?characterEncoding=utf8&serverTimezone=GMT%2B8",
"thli",
"thli@20180913"
);
/**
* 设置需要生成的表名
*/
private static final String[] TABLE_NAMES = {
"tb_user",
"tb_user_login_out",
// "tb_user_login_out",
};
/**
* 是否为表更新
@ -37,7 +37,7 @@ public class MybatisFastGenerator {
/**
* 模块名称
*/
private static final String MODULE_NAME = "demo";
private static final String MODULE_NAME = "jyjc.admin";
/**
* 设置过滤表前缀

@ -0,0 +1,35 @@
package cn.soul2.jyjc.admin.utils;
import java.security.SecureRandom;
import java.util.Base64;
/**
* 盐值生成
*
* @author Soul2
* @date 2024-03-30 14:30
*/
public class SaltUtils {
/**
* 生成指定长度的盐值
*
* @param length 盐值长度
* @return {@link String}
*/
public static String generateSalt(int length) {
SecureRandom random = new SecureRandom();
byte[] salt = new byte[length];
random.nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}
public static void main(String[] args) {
// 设置盐值长度
int saltLength = 16;
String salt = generateSalt(saltLength);
System.out.println("Generated Salt: " + salt);
}
}

@ -0,0 +1,38 @@
package cn.soul2.jyjc.admin.vo;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* @author Soul2
* @date 2024-03-30 14:40
*/
@Data
@Accessors(chain = true)
public class UserVO {
/**
* username
*/
private String username;
/**
* token
* 其实就是'UserLoginOut'表的id
*/
private String token;
/**
* 权限等级
*/
private String authorityLevel;
/**
* 状态;0(默认)正常4封号9异常
*/
private Integer status;
private String loginFail;
}

@ -1,7 +1,7 @@
# 允许跨域的地址
cors:
allow-origins: http://localhost
allow-origin: http://localhost
---
spring:
@ -9,13 +9,12 @@ spring:
activate:
on-profile: dev
cors:
allow-origins: http://localhost:6100, http://localhost:7620, http://192.168.10.104:7620
allow-origin: http://localhost:6100, http://localhost:7620, http://192.168.10.104:7620
---
spring:
config:
activate:
on-profile: prod
cors:
allow-origins: http://test.soul2.cn
allow-origin: http://test.soul2.cn

@ -4,6 +4,7 @@ server:
#上下文
servlet.context-path: /thli/jyjc/api
encrypt.keys.aes: 98478f8a45887eda446501bcb44010520c3f53c7b45ff36215e86ce4f049b0f2
spring:
application.name: jyjc-admin

Loading…
Cancel
Save