diff --git a/src/main/java/xyz/playedu/api/constant/SystemConstant.java b/src/main/java/xyz/playedu/api/constant/SystemConstant.java index a4d8425..62456a9 100644 --- a/src/main/java/xyz/playedu/api/constant/SystemConstant.java +++ b/src/main/java/xyz/playedu/api/constant/SystemConstant.java @@ -8,7 +8,8 @@ public class SystemConstant { public final static String REDIS_PREFIX = "playedu:"; - public final static String JWT_PRV_ADMIN_USER = "dc14511e97e7eb725fb2976bc939b375"; + public final static String JWT_PRV_ADMIN_USER = "dc14511e97e7eb725fb2976bc939b375";//AdminUser的md5加密 + public final static String JWT_PRV_USER = "8f9bfe9d1345237cb3b2b205864da075";//User的md5加密 public final static String INTERNAL_IP = "127.0.0.1"; diff --git a/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java b/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java index 1711f3a..2f6222c 100644 --- a/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java +++ b/src/main/java/xyz/playedu/api/controller/frontend/LoginController.java @@ -1,11 +1,63 @@ package xyz.playedu.api.controller.frontend; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.validation.annotation.Validated; +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 xyz.playedu.api.constant.SystemConstant; +import xyz.playedu.api.domain.User; +import xyz.playedu.api.event.UserLoginEvent; +import xyz.playedu.api.request.frontend.LoginPasswordRequest; +import xyz.playedu.api.service.JWTService; +import xyz.playedu.api.service.UserService; +import xyz.playedu.api.types.JsonResponse; +import xyz.playedu.api.types.JwtToken; +import xyz.playedu.api.util.HelperUtil; +import xyz.playedu.api.util.IpUtil; +import xyz.playedu.api.util.RequestUtil; + +import java.util.Date; +import java.util.HashMap; /** * @Author 杭州白书科技有限公司 * @create 2023/3/2 21:51 */ @RestController +@RequestMapping("/api/v1/auth/login") public class LoginController { + + @Autowired + private UserService userService; + + @Autowired + private JWTService jwtService; + + @Autowired + private ApplicationContext ctx; + + @PostMapping("/password") + public JsonResponse password(@RequestBody @Validated LoginPasswordRequest req) { + User user = userService.find(req.getEmail()); + if (user == null) { + return JsonResponse.error("邮箱未注册"); + } + if (!HelperUtil.MD5(req.getPassword() + user.getSalt()).equals(user.getPassword())) { + return JsonResponse.error("密码错误"); + } + + JwtToken token = jwtService.generate(user.getId(), RequestUtil.url(), SystemConstant.JWT_PRV_USER); + + HashMap data = new HashMap<>(); + data.put("token", token.getToken()); + data.put("expired", token.getExpire()); + + ctx.publishEvent(new UserLoginEvent(this, user.getId(), new Date(), token.getToken(), IpUtil.getIpAddress(), RequestUtil.ua())); + + return JsonResponse.data(data); + } + } diff --git a/src/main/java/xyz/playedu/api/domain/UserLoginRecord.java b/src/main/java/xyz/playedu/api/domain/UserLoginRecord.java new file mode 100644 index 0000000..844afc1 --- /dev/null +++ b/src/main/java/xyz/playedu/api/domain/UserLoginRecord.java @@ -0,0 +1,141 @@ +package xyz.playedu.api.domain; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import java.io.Serializable; +import java.util.Date; +import lombok.Data; + +/** + * + * @TableName user_login_records + */ +@TableName(value ="user_login_records") +@Data +public class UserLoginRecord implements Serializable { + /** + * + */ + @TableId(type = IdType.AUTO) + private Integer id; + + /** + * + */ + private Integer userId; + + /** + * JTI + */ + private String jti; + + /** + * 登录ip + */ + private String ip; + + /** + * Ip解析区域 + */ + private String ipArea; + + /** + * 浏览器 + */ + private String browser; + + /** + * 浏览器版本 + */ + private String browserVersion; + + /** + * 操作系统 + */ + private String os; + + /** + * 过期时间 + */ + private Long expired; + + /** + * 是否注销 + */ + private Integer isLogout; + + /** + * 创建时间 + */ + private Date createdAt; + + @TableField(exist = false) + private static final long serialVersionUID = 1L; + + @Override + public boolean equals(Object that) { + if (this == that) { + return true; + } + if (that == null) { + return false; + } + if (getClass() != that.getClass()) { + return false; + } + UserLoginRecord other = (UserLoginRecord) that; + return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId())) + && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId())) + && (this.getJti() == null ? other.getJti() == null : this.getJti().equals(other.getJti())) + && (this.getIp() == null ? other.getIp() == null : this.getIp().equals(other.getIp())) + && (this.getIpArea() == null ? other.getIpArea() == null : this.getIpArea().equals(other.getIpArea())) + && (this.getBrowser() == null ? other.getBrowser() == null : this.getBrowser().equals(other.getBrowser())) + && (this.getBrowserVersion() == null ? other.getBrowserVersion() == null : this.getBrowserVersion().equals(other.getBrowserVersion())) + && (this.getOs() == null ? other.getOs() == null : this.getOs().equals(other.getOs())) + && (this.getExpired() == null ? other.getExpired() == null : this.getExpired().equals(other.getExpired())) + && (this.getIsLogout() == null ? other.getIsLogout() == null : this.getIsLogout().equals(other.getIsLogout())) + && (this.getCreatedAt() == null ? other.getCreatedAt() == null : this.getCreatedAt().equals(other.getCreatedAt())); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getId() == null) ? 0 : getId().hashCode()); + result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode()); + result = prime * result + ((getJti() == null) ? 0 : getJti().hashCode()); + result = prime * result + ((getIp() == null) ? 0 : getIp().hashCode()); + result = prime * result + ((getIpArea() == null) ? 0 : getIpArea().hashCode()); + result = prime * result + ((getBrowser() == null) ? 0 : getBrowser().hashCode()); + result = prime * result + ((getBrowserVersion() == null) ? 0 : getBrowserVersion().hashCode()); + result = prime * result + ((getOs() == null) ? 0 : getOs().hashCode()); + result = prime * result + ((getExpired() == null) ? 0 : getExpired().hashCode()); + result = prime * result + ((getIsLogout() == null) ? 0 : getIsLogout().hashCode()); + result = prime * result + ((getCreatedAt() == null) ? 0 : getCreatedAt().hashCode()); + return result; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()); + sb.append(" ["); + sb.append("Hash = ").append(hashCode()); + sb.append(", id=").append(id); + sb.append(", userId=").append(userId); + sb.append(", jti=").append(jti); + sb.append(", ip=").append(ip); + sb.append(", ipArea=").append(ipArea); + sb.append(", browser=").append(browser); + sb.append(", browserVersion=").append(browserVersion); + sb.append(", os=").append(os); + sb.append(", expired=").append(expired); + sb.append(", isLogout=").append(isLogout); + sb.append(", createdAt=").append(createdAt); + sb.append(", serialVersionUID=").append(serialVersionUID); + sb.append("]"); + return sb.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/xyz/playedu/api/event/UserLoginEvent.java b/src/main/java/xyz/playedu/api/event/UserLoginEvent.java new file mode 100644 index 0000000..4a11162 --- /dev/null +++ b/src/main/java/xyz/playedu/api/event/UserLoginEvent.java @@ -0,0 +1,37 @@ +package xyz.playedu.api.event; + +import cn.hutool.http.useragent.UserAgent; +import lombok.Getter; +import lombok.Setter; +import org.springframework.context.ApplicationEvent; + +import java.util.Date; + +/** + * @Author 杭州白书科技有限公司 + * @create 2023/3/10 13:22 + */ +@Setter +@Getter +public class UserLoginEvent extends ApplicationEvent { + + private Integer userId; + + private Date loginAt; + + private String token; + + private String ip; + + private UserAgent userAgent; + + public UserLoginEvent(Object source, Integer userId, Date loginAt, String token, String ip, UserAgent userAgent) { + super(source); + this.userId = userId; + this.loginAt = loginAt; + this.token = token; + this.ip = ip; + this.userAgent = userAgent; + } +} + diff --git a/src/main/java/xyz/playedu/api/listener/UserLoginListener.java b/src/main/java/xyz/playedu/api/listener/UserLoginListener.java new file mode 100644 index 0000000..4345fc5 --- /dev/null +++ b/src/main/java/xyz/playedu/api/listener/UserLoginListener.java @@ -0,0 +1,47 @@ +package xyz.playedu.api.listener; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import xyz.playedu.api.constant.SystemConstant; +import xyz.playedu.api.event.UserLoginEvent; +import xyz.playedu.api.exception.JwtLogoutException; +import xyz.playedu.api.service.JWTService; +import xyz.playedu.api.service.UserLoginRecordService; +import xyz.playedu.api.types.JWTPayload; +import xyz.playedu.api.util.IpUtil; + +/** + * @Author 杭州白书科技有限公司 + * @create 2023/3/10 13:45 + */ +@Component +@Slf4j +public class UserLoginListener { + + @Autowired + private UserLoginRecordService loginRecordService; + + @Autowired + private JWTService jwtService; + + @EventListener + @Async + public void updateLoginInfo(UserLoginEvent event) throws JwtLogoutException { + String ipArea = IpUtil.getRealAddressByIP(event.getIp()); + JWTPayload payload = jwtService.parse(event.getToken(), SystemConstant.JWT_PRV_USER); + loginRecordService.store( + event.getUserId(), + payload.getJti(), + payload.getExp(), + event.getIp(), + ipArea, + event.getUserAgent().getBrowser().toString(), + event.getUserAgent().getVersion(), + event.getUserAgent().getOs().toString() + ); + } + +} diff --git a/src/main/java/xyz/playedu/api/mapper/UserLoginRecordMapper.java b/src/main/java/xyz/playedu/api/mapper/UserLoginRecordMapper.java new file mode 100644 index 0000000..0c4d7dc --- /dev/null +++ b/src/main/java/xyz/playedu/api/mapper/UserLoginRecordMapper.java @@ -0,0 +1,20 @@ +package xyz.playedu.api.mapper; + +import org.apache.ibatis.annotations.Mapper; +import xyz.playedu.api.domain.UserLoginRecord; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** +* @author tengteng +* @description 针对表【user_login_records】的数据库操作Mapper +* @createDate 2023-03-10 14:06:55 +* @Entity xyz.playedu.api.domain.UserLoginRecord +*/ +@Mapper +public interface UserLoginRecordMapper extends BaseMapper { + +} + + + + diff --git a/src/main/java/xyz/playedu/api/request/backend/AppConfigRequest.java b/src/main/java/xyz/playedu/api/request/backend/AppConfigRequest.java index f58baf6..e557381 100644 --- a/src/main/java/xyz/playedu/api/request/backend/AppConfigRequest.java +++ b/src/main/java/xyz/playedu/api/request/backend/AppConfigRequest.java @@ -1,5 +1,6 @@ package xyz.playedu.api.request.backend; +import jakarta.validation.constraints.NotNull; import lombok.Data; import java.util.HashMap; @@ -11,6 +12,7 @@ import java.util.HashMap; @Data public class AppConfigRequest { + @NotNull(message = "配置为空") private HashMap data; } diff --git a/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java b/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java new file mode 100644 index 0000000..1abc3aa --- /dev/null +++ b/src/main/java/xyz/playedu/api/request/frontend/LoginPasswordRequest.java @@ -0,0 +1,28 @@ +package xyz.playedu.api.request.frontend; + +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * @Author 杭州白书科技有限公司 + * @create 2023/3/10 13:13 + */ +@Data +public class LoginPasswordRequest { + + @NotBlank(message = "请输入邮箱") + private String email; + + @NotBlank(message = "请输入密码") + private String password; + + @NotBlank(message = "请输入验证码") + @JsonProperty("captcha_key") + private String captchaKey; + + @NotBlank(message = "请输入验证码") + @JsonProperty("captcha_val") + private String captchaVal; + +} diff --git a/src/main/java/xyz/playedu/api/service/UserLoginRecordService.java b/src/main/java/xyz/playedu/api/service/UserLoginRecordService.java new file mode 100644 index 0000000..0db83a4 --- /dev/null +++ b/src/main/java/xyz/playedu/api/service/UserLoginRecordService.java @@ -0,0 +1,15 @@ +package xyz.playedu.api.service; + +import xyz.playedu.api.domain.UserLoginRecord; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + * @author tengteng + * @description 针对表【user_login_records】的数据库操作Service + * @createDate 2023-03-10 13:40:33 + */ +public interface UserLoginRecordService extends IService { + UserLoginRecord store(Integer userId, String jti, Long expired, String ip, String ipArea, String browser, String browserVersion, String os); + + void saveIpArea(Integer id, String area); +} diff --git a/src/main/java/xyz/playedu/api/service/UserService.java b/src/main/java/xyz/playedu/api/service/UserService.java index de4b3f2..cf92a63 100644 --- a/src/main/java/xyz/playedu/api/service/UserService.java +++ b/src/main/java/xyz/playedu/api/service/UserService.java @@ -24,6 +24,10 @@ public interface UserService extends IService { User findOrFail(Integer id) throws NotFoundException; + User find(Integer id); + + User find(String email); + User createWithDepIds(String email, String name, String avatar, String password, String idCard, Integer[] depIds); User updateWithDepIds(User user, String email, String nickname, String name, String avatar, String password, String idCard, Integer[] depIds); diff --git a/src/main/java/xyz/playedu/api/service/impl/UserLoginRecordServiceImpl.java b/src/main/java/xyz/playedu/api/service/impl/UserLoginRecordServiceImpl.java new file mode 100644 index 0000000..7ca3489 --- /dev/null +++ b/src/main/java/xyz/playedu/api/service/impl/UserLoginRecordServiceImpl.java @@ -0,0 +1,43 @@ +package xyz.playedu.api.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import xyz.playedu.api.domain.UserLoginRecord; +import xyz.playedu.api.service.UserLoginRecordService; +import xyz.playedu.api.mapper.UserLoginRecordMapper; +import org.springframework.stereotype.Service; + +/** + * @author tengteng + * @description 针对表【user_login_records】的数据库操作Service实现 + * @createDate 2023-03-10 13:40:33 + */ +@Service +public class UserLoginRecordServiceImpl extends ServiceImpl + implements UserLoginRecordService { + @Override + public UserLoginRecord store(Integer userId, String jti, Long expired, String ip, String ipArea, String browser, String browserVersion, String os) { + UserLoginRecord record = new UserLoginRecord(); + record.setUserId(userId); + record.setJti(jti); + record.setExpired(expired); + record.setIp(ip); + record.setIpArea(ipArea); + record.setBrowser(browser); + record.setBrowserVersion(browserVersion); + record.setOs(os); + save(record); + return record; + } + + @Override + public void saveIpArea(Integer id, String area) { + UserLoginRecord record = new UserLoginRecord(); + record.setId(id); + record.setIpArea(area); + updateById(record); + } +} + + + + diff --git a/src/main/java/xyz/playedu/api/service/impl/UserServiceImpl.java b/src/main/java/xyz/playedu/api/service/impl/UserServiceImpl.java index 00e7467..fb99c7f 100644 --- a/src/main/java/xyz/playedu/api/service/impl/UserServiceImpl.java +++ b/src/main/java/xyz/playedu/api/service/impl/UserServiceImpl.java @@ -193,6 +193,16 @@ public class UserServiceImpl extends ServiceImpl implements Us public List getDepIdsByUserId(Integer userId) { return userDepartmentService.list(userDepartmentService.query().getWrapper().eq("user_id", userId)).stream().map(UserDepartment::getDepId).toList(); } + + @Override + public User find(Integer id) { + return getOne(query().getWrapper().eq("id", id)); + } + + @Override + public User find(String email) { + return getOne(query().getWrapper().eq("email", email)); + } } diff --git a/src/main/java/xyz/playedu/api/util/IpUtil.java b/src/main/java/xyz/playedu/api/util/IpUtil.java index 27d648e..5b63641 100644 --- a/src/main/java/xyz/playedu/api/util/IpUtil.java +++ b/src/main/java/xyz/playedu/api/util/IpUtil.java @@ -3,8 +3,7 @@ package xyz.playedu.api.util; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; import jakarta.servlet.http.HttpServletRequest; @@ -12,10 +11,9 @@ import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; +@Slf4j public class IpUtil { - private static final Logger log = LoggerFactory.getLogger(IpUtil.class); - /** * 获取客户端IP * @@ -218,20 +216,6 @@ public class IpUtil { return "127.0.0.1"; } - /** - * 获取主机名 - * - * @return 本地主机名 - * @author fzr - */ - public static String getHostName() { - try { - return InetAddress.getLocalHost().getHostName(); - } catch (UnknownHostException ignored) { - } - return "未知"; - } - /** * 从多级反向代理中获得第一个非unknown IP地址 * diff --git a/src/main/java/xyz/playedu/api/util/RequestUtil.java b/src/main/java/xyz/playedu/api/util/RequestUtil.java index af76292..5dbcab2 100644 --- a/src/main/java/xyz/playedu/api/util/RequestUtil.java +++ b/src/main/java/xyz/playedu/api/util/RequestUtil.java @@ -1,5 +1,7 @@ package xyz.playedu.api.util; +import cn.hutool.http.useragent.UserAgent; +import cn.hutool.http.useragent.UserAgentUtil; import jakarta.servlet.http.HttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; @@ -9,18 +11,17 @@ import java.util.List; public class RequestUtil { - /** - * 获取请求对象 - * - * @return HttpServletRequest - * @author fzr - */ public static HttpServletRequest handler() { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (servletRequestAttributes != null) { - return servletRequestAttributes.getRequest(); + return servletRequestAttributes == null ? null : servletRequestAttributes.getRequest(); + } + + public static UserAgent ua() { + HttpServletRequest request = RequestUtil.handler(); + if (request == null) { + return null; } - return null; + return UserAgentUtil.parse(request.getHeader("User-Agent")); } public static String token() { @@ -35,65 +36,24 @@ public class RequestUtil { return token.split(" ")[1]; } - /** - * 获取不带参请求URl - * 示例: https://127.0.0.1:8082/api/system/menu/menus - * - * @return String - * @author fzr - */ public static String url() { HttpServletRequest request = RequestUtil.handler(); - if (request != null) { - return request.getRequestURL().toString(); - } - return ""; + return request == null ? "" : request.getRequestURL().toString(); } - /** - * 获取带端口的请求地址 - * 示例: https://127.0.0.1:8082 - * - * @return String - * @author fzr - */ public static String uri() { - String domain = RequestUtil.domain(); - if (!Arrays.asList(443, 80, 0).contains(RequestUtil.port())) { - domain += ":" + RequestUtil.port(); - } - - return domain; + Integer portNumber = port(); + return RequestUtil.domain() + (Arrays.asList(443, 80, 0).contains(portNumber) ? "" : ":" + portNumber); } - /** - * 获取请求路由 - * 示例: /api/system/menu/menus - * - * @return String - * @author fzr - */ - public static String route() { + public static String pathname() { HttpServletRequest request = RequestUtil.handler(); - if (request != null) { - return request.getRequestURI(); - } - return ""; + return request == null ? "" : request.getRequestURI(); } - /** - * 获取请求端口 - * 示例: 443/80 - * - * @return Integer - * @author fzr - */ public static Integer port() { HttpServletRequest request = RequestUtil.handler(); - if (request != null) { - return request.getServerPort(); - } - return 0; + return request == null ? 0 : request.getServerPort(); } public static String domain() { @@ -106,30 +66,4 @@ public class RequestUtil { } return null; } - - public static Boolean isGet() { - return isMethod("GET"); - } - - public static Boolean isPost() { - return isMethod("POST"); - } - - public static Boolean isPUT() { - return isMethod("PUT"); - } - - public static Boolean isDelete() { - return isMethod("DELETE"); - } - - public static boolean isMethod(String method) { - ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); - if (servletRequestAttributes != null) { - HttpServletRequest request = servletRequestAttributes.getRequest(); - return request.getMethod().equals(method); - } - return false; - } - } diff --git a/src/main/resources/mapper/UserLoginRecordMapper.xml b/src/main/resources/mapper/UserLoginRecordMapper.xml new file mode 100644 index 0000000..a0d9371 --- /dev/null +++ b/src/main/resources/mapper/UserLoginRecordMapper.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + id,user_id,jti, + ip,ip_area,browser, + browser_version,os,expired, + is_logout,created_at + +