feat: 个人信息页面.

This commit is contained in:
lijiahang
2023-10-31 19:07:48 +08:00
parent 6c9aabd4fd
commit 3fef9b8ae2
42 changed files with 647 additions and 148 deletions

View File

@@ -25,6 +25,8 @@ public interface Const extends com.orion.lang.constant.Const, FieldConst {
Integer DEFAULT_SORT = 10; Integer DEFAULT_SORT = 10;
int LOGIN_HISTORY_COUNT = 30;
Long NONE_ID = -1L; Long NONE_ID = -1L;
Integer DEFAULT_VERSION = 1; Integer DEFAULT_VERSION = 1;

View File

@@ -46,7 +46,7 @@ public enum ErrorCode implements CodeInfo {
// -------------------- 自定义 - 业务 -------------------- // -------------------- 自定义 - 业务 --------------------
OTHER_DEVICE_LOGIN(700, "该账号于 {} 已在其他设备登 {}({})"), OTHER_DEVICE_LOGIN(700, "该账号于 {} 已在其他设备登 {}({})"),
USER_DISABLED(701, "当前用户已禁用"), USER_DISABLED(701, "当前用户已禁用"),

View File

@@ -49,7 +49,7 @@ public interface ErrorMessage {
String USERNAME_PASSWORD_ERROR = "用户名或密码错误"; String USERNAME_PASSWORD_ERROR = "用户名或密码错误";
String MAX_LOGIN_FAILED = "失败次数已上限"; String MAX_LOGIN_FAILED = "失败次数已上限";
String HISTORY_ABSENT = "历史值不存在"; String HISTORY_ABSENT = "历史值不存在";

View File

@@ -52,7 +52,7 @@ public class PrettyLogPrinterInterceptor extends AbstractLogPrinterInterceptor {
if (!Strings.isEmpty(summary)) { if (!Strings.isEmpty(summary)) {
requestLog.append("\tsummary: ").append(summary).append('\n'); requestLog.append("\tsummary: ").append(summary).append('\n');
} }
// 登用户 // 登用户
Long loginUserId = securityHolder.getLoginUserId(); Long loginUserId = securityHolder.getLoginUserId();
if (loginUserId != null) { if (loginUserId != null) {
requestLog.append("\tuser: ").append(loginUserId).append('\n'); requestLog.append("\tuser: ").append(loginUserId).append('\n');

View File

@@ -56,7 +56,7 @@ public class RowLogPrinterInterceptor extends AbstractLogPrinterInterceptor impl
if (!Strings.isEmpty(summary)) { if (!Strings.isEmpty(summary)) {
fields.put(SUMMARY, summary); fields.put(SUMMARY, summary);
} }
// 登用户 // 登用户
fields.put(USER, securityHolder.getLoginUserId()); fields.put(USER, securityHolder.getLoginUserId());
// http // http
if (request != null) { if (request != null) {

View File

@@ -106,6 +106,14 @@ public class DataQuery<T> {
return then; return then;
} }
public DataQuery<T> limit(int limit) {
return this.last(Const.LIMIT + Const.SPACE + limit);
}
public DataQuery<T> limit(int offset, int limit) {
return this.last(Const.LIMIT + Const.SPACE + offset + Const.COMMA + limit);
}
public DataQuery<T> only() { public DataQuery<T> only() {
return this.last(Const.LIMIT_1); return this.last(Const.LIMIT_1);
} }

View File

@@ -69,7 +69,7 @@ public class SecurityUtils {
} }
/** /**
* 获取当前用户id * 获取当前 userId
* *
* @return id * @return id
*/ */
@@ -78,6 +78,16 @@ public class SecurityUtils {
return loginUser != null ? loginUser.getId() : null; return loginUser != null ? loginUser.getId() : null;
} }
/**
* 获取当前 username
*
* @return username
*/
public static String getLoginUsername() {
LoginUser loginUser = getLoginUser();
return loginUser != null ? loginUser.getUsername() : null;
}
/** /**
* 设置当前用户 * 设置当前用户
* *

View File

@@ -161,7 +161,7 @@ orion:
# 下面引用了 需要注意 # 下面引用了 需要注意
field: field:
ignore: ignore:
- password,newPassword,useNewPassword,publicKey,privateKey - password,beforePassword,newPassword,useNewPassword,publicKey,privateKey
- metrics - metrics
desensitize: desensitize:
storage: storage:

View File

@@ -1,4 +1,4 @@
### 登 - admin 用户 ### 登 - admin 用户
POST {{baseUrl}}/infra/auth/login POST {{baseUrl}}/infra/auth/login
Content-Type: application/json Content-Type: application/json
@@ -8,7 +8,7 @@ Content-Type: application/json
} }
### 登 ### 登
POST {{baseUrl}}/infra/auth/login POST {{baseUrl}}/infra/auth/login
Content-Type: application/json Content-Type: application/json

View File

@@ -10,7 +10,6 @@ import com.orion.ops.module.infra.entity.request.user.UserLoginRequest;
import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest; import com.orion.ops.module.infra.entity.request.user.UserUpdatePasswordRequest;
import com.orion.ops.module.infra.entity.vo.UserLoginVO; import com.orion.ops.module.infra.entity.vo.UserLoginVO;
import com.orion.ops.module.infra.service.AuthenticationService; import com.orion.ops.module.infra.service.AuthenticationService;
import com.orion.ops.module.infra.service.SystemUserService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@@ -40,12 +39,9 @@ public class AuthenticationController {
@Resource @Resource
private AuthenticationService authenticationService; private AuthenticationService authenticationService;
@Resource
private SystemUserService systemUserService;
@OperatorLog(AuthenticationOperatorType.LOGIN) @OperatorLog(AuthenticationOperatorType.LOGIN)
@PermitAll @PermitAll
@Operation(summary = "") @Operation(summary = "")
@PostMapping("/login") @PostMapping("/login")
public UserLoginVO login(@Validated @RequestBody UserLoginRequest request, public UserLoginVO login(@Validated @RequestBody UserLoginRequest request,
HttpServletRequest servletRequest) { HttpServletRequest servletRequest) {

View File

@@ -4,8 +4,10 @@ import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.common.validator.group.Page; import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog; import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode; import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper; import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest; import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO; import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import com.orion.ops.module.infra.service.OperatorLogService; import com.orion.ops.module.infra.service.OperatorLogService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
@@ -13,12 +15,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
/** /**
* 操作日志 api * 操作日志 api
@@ -47,5 +47,22 @@ public class OperatorLogController {
return operatorLogService.getOperatorLogPage(request); return operatorLogService.getOperatorLogPage(request);
} }
// fixme 权限配置
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/login-history")
@Operation(summary = "查询用户登录日志")
public List<LoginHistoryVO> getLoginHistory(@RequestParam("username") String username) {
return operatorLogService.getLoginHistory(username);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/current-login-history")
@Operation(summary = "查询当前用户登录日志")
public List<LoginHistoryVO> getCurrentLoginHistory() {
String username = SecurityUtils.getLoginUsername();
return operatorLogService.getLoginHistory(username);
}
} }

View File

@@ -7,6 +7,7 @@ import com.orion.ops.framework.biz.operator.log.core.annotation.OperatorLog;
import com.orion.ops.framework.common.validator.group.Page; import com.orion.ops.framework.common.validator.group.Page;
import com.orion.ops.framework.log.core.annotation.IgnoreLog; import com.orion.ops.framework.log.core.annotation.IgnoreLog;
import com.orion.ops.framework.log.core.enums.IgnoreLogMode; import com.orion.ops.framework.log.core.enums.IgnoreLogMode;
import com.orion.ops.framework.security.core.utils.SecurityUtils;
import com.orion.ops.framework.web.core.annotation.RestWrapper; import com.orion.ops.framework.web.core.annotation.RestWrapper;
import com.orion.ops.module.infra.define.operator.SystemUserOperatorType; import com.orion.ops.module.infra.define.operator.SystemUserOperatorType;
import com.orion.ops.module.infra.entity.request.user.*; import com.orion.ops.module.infra.entity.request.user.*;
@@ -95,6 +96,20 @@ public class SystemUserController {
return HttpWrapper.ok(); return HttpWrapper.ok();
} }
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get-current")
@Operation(summary = "查询当前用户信息")
public SystemUserVO getCurrentUserInfo() {
return systemUserService.getSystemUserById(SecurityUtils.getLoginUserId());
}
@PutMapping("/update-current")
@Operation(summary = "更新当前用户信息")
public Integer updateCurrentUser(@Validated @RequestBody SystemUserUpdateRequest request) {
request.setId(SecurityUtils.getLoginUserId());
return systemUserService.updateSystemUserById(request);
}
@IgnoreLog(IgnoreLogMode.RET) @IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get") @GetMapping("/get")
@Operation(summary = "通过 id 查询用户") @Operation(summary = "通过 id 查询用户")

View File

@@ -3,6 +3,7 @@ package com.orion.ops.module.infra.convert;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel; import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.module.infra.entity.domain.OperatorLogDO; import com.orion.ops.module.infra.entity.domain.OperatorLogDO;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest; import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO; import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@@ -25,4 +26,6 @@ public interface OperatorLogConvert {
OperatorLogVO to(OperatorLogDO domain); OperatorLogVO to(OperatorLogDO domain);
LoginHistoryVO toLoginHistory(OperatorLogDO domain);
} }

View File

@@ -24,13 +24,13 @@ public interface UserCacheKeyDefine {
CacheKeyDefine LOGIN_FAILED_COUNT = new CacheKeyBuilder() CacheKeyDefine LOGIN_FAILED_COUNT = new CacheKeyBuilder()
.key("user:failed:{}") .key("user:failed:{}")
.desc("用户登失败次数 ${username}") .desc("用户登失败次数 ${username}")
.timeout(3, TimeUnit.DAYS) .timeout(3, TimeUnit.DAYS)
.build(); .build();
CacheKeyDefine LOGIN_TOKEN = new CacheKeyBuilder() CacheKeyDefine LOGIN_TOKEN = new CacheKeyBuilder()
.key("user:token:{}:{}") .key("user:token:{}:{}")
.desc("用户登 token ${id} ${time}") .desc("用户登 token ${id} ${time}")
.type(LoginTokenDTO.class) .type(LoginTokenDTO.class)
.timeout(24, TimeUnit.HOURS) .timeout(24, TimeUnit.HOURS)
.build(); .build();

View File

@@ -25,7 +25,7 @@ public class AuthenticationOperatorType extends InitializingOperatorTypes {
@Override @Override
public OperatorType[] types() { public OperatorType[] types() {
return new OperatorType[]{ return new OperatorType[]{
new OperatorType(L, LOGIN, "系统"), new OperatorType(L, LOGIN, "系统"),
new OperatorType(L, LOGOUT, "登出系统"), new OperatorType(L, LOGOUT, "登出系统"),
new OperatorType(L, UPDATE_PASSWORD, "修改密码"), new OperatorType(L, UPDATE_PASSWORD, "修改密码"),
}; };

View File

@@ -6,7 +6,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* 登 token 缓存 * 登 token 缓存
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
@@ -28,7 +28,7 @@ public class LoginTokenDTO {
* *
* @see com.orion.ops.module.infra.enums.LoginTokenStatusEnum * @see com.orion.ops.module.infra.enums.LoginTokenStatusEnum
*/ */
private Integer tokenStatus; private Integer status;
/** /**
* 已续签次数 * 已续签次数
@@ -36,18 +36,43 @@ public class LoginTokenDTO {
private Integer refreshCount; private Integer refreshCount;
/** /**
* 登陆时间/其他设备登陆时间 * 原始登录身份
*/ */
private Long loginTime; private Identity origin;
/** /**
* 登陆 ip/其他设备登陆 ip * 覆盖登录身份
*/ */
private String ip; private Identity override;
/** /**
* 登陆地址/其他设备登陆地址 * 身份信息
*/ */
private String location; @Data
@NoArgsConstructor
@AllArgsConstructor
public static class Identity {
/**
* 原始登录时间
*/
private Long loginTime;
/**
* 当前设备登录地址
*/
private String address;
/**
* 当前设备登录地址
*/
private String location;
/**
* 当前设备 userAgent
*/
private String userAgent;
}
} }

View File

@@ -26,6 +26,9 @@ public class OperatorLogQueryRequest extends PageRequest {
@Schema(description = "用户id") @Schema(description = "用户id")
private Long userId; private Long userId;
@Schema(description = "用户名")
private String username;
@Size(max = 32) @Size(max = 32)
@Schema(description = "模块") @Schema(description = "模块")
private String module; private String module;

View File

@@ -6,14 +6,14 @@ import lombok.Data;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
/** /**
* 登请求 * 登请求
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
* @since 2023/7/13 22:16 * @since 2023/7/13 22:16
*/ */
@Data @Data
@Schema(name = "UserLoginRequest", description = "请求") @Schema(name = "UserLoginRequest", description = "请求")
public class UserLoginRequest { public class UserLoginRequest {
@NotEmpty @NotEmpty

View File

@@ -0,0 +1,49 @@
package com.orion.ops.module.infra.entity.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.Date;
/**
* 登录日志 视图响应对象
*
* @author Jiahang Li
* @version 1.0.0
* @since 2023-10-10 17:08
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(name = "LoginHistoryVO", description = "登录日志 视图响应对象")
public class LoginHistoryVO implements Serializable {
private static final long serialVersionUID = 1L;
@Schema(description = "id")
private Long id;
@Schema(description = "请求ip")
private String address;
@Schema(description = "请求地址")
private String location;
@Schema(description = "userAgent")
private String userAgent;
@Schema(description = "操作结果 0失败 1成功")
private Integer result;
@Schema(description = "错误信息")
private String errorMessage;
@Schema(description = "创建时间")
private Date createTime;
}

View File

@@ -7,7 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
/** /**
* 用户登响应 * 用户登响应
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
@@ -20,7 +20,7 @@ import lombok.NoArgsConstructor;
@Schema(name = "SystemUserVO", description = "用户 视图响应对象") @Schema(name = "SystemUserVO", description = "用户 视图响应对象")
public class UserLoginVO { public class UserLoginVO {
@Schema(description = " token") @Schema(description = " token")
private String token; private String token;
} }

View File

@@ -3,7 +3,7 @@ package com.orion.ops.module.infra.enums;
import lombok.Getter; import lombok.Getter;
/** /**
* 登 token 状态 * 登 token 状态
* *
* @author Jiahang Li * @author Jiahang Li
* @version 1.0.0 * @version 1.0.0
@@ -18,9 +18,9 @@ public enum LoginTokenStatusEnum {
OK(0), OK(0),
/** /**
* 已在其他设备登 * 已在其他设备登
*/ */
OTHER_DEVICE(1, "已在其他设备登"), OTHER_DEVICE(1, "已在其他设备登"),
; ;

View File

@@ -57,7 +57,7 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
} }
// 检查 token 状态 // 检查 token 状态
this.checkTokenStatus(tokenInfo); this.checkTokenStatus(tokenInfo);
// 获取登信息 // 获取登信息
LoginUser user = authenticationService.getLoginUser(tokenInfo.getId()); LoginUser user = authenticationService.getLoginUser(tokenInfo.getId());
// 检查用户状态 // 检查用户状态
UserStatusEnum.checkUserStatus(user.getStatus()); UserStatusEnum.checkUserStatus(user.getStatus());
@@ -70,17 +70,18 @@ public class SecurityFrameworkServiceImpl implements SecurityFrameworkService {
* @param loginToken loginToken * @param loginToken loginToken
*/ */
private void checkTokenStatus(LoginTokenDTO loginToken) { private void checkTokenStatus(LoginTokenDTO loginToken) {
Integer tokenStatus = loginToken.getTokenStatus(); Integer tokenStatus = loginToken.getStatus();
// 正常状态 // 正常状态
if (LoginTokenStatusEnum.OK.getStatus().equals(tokenStatus)) { if (LoginTokenStatusEnum.OK.getStatus().equals(tokenStatus)) {
return; return;
} }
// 其他设备登 // 其他设备登
if (LoginTokenStatusEnum.OTHER_DEVICE.getStatus().equals(tokenStatus)) { if (LoginTokenStatusEnum.OTHER_DEVICE.getStatus().equals(tokenStatus)) {
LoginTokenDTO.Identity override = loginToken.getOverride();
throw ErrorCode.OTHER_DEVICE_LOGIN.exception( throw ErrorCode.OTHER_DEVICE_LOGIN.exception(
Dates.format(new Date(loginToken.getLoginTime()), Dates.MD_HM), Dates.format(new Date(override.getLoginTime()), Dates.MD_HM),
loginToken.getIp(), override.getAddress(),
loginToken.getLocation()); override.getLocation());
} }
} }

View File

@@ -18,7 +18,7 @@ import javax.servlet.http.HttpServletRequest;
public interface AuthenticationService { public interface AuthenticationService {
// TODO 配置化 // TODO 配置化
// 允许多端登 // 允许多端登
boolean allowMultiDevice = true; boolean allowMultiDevice = true;
// 允许凭证续签 // 允许凭证续签
boolean allowRefresh = true; boolean allowRefresh = true;
@@ -28,7 +28,7 @@ public interface AuthenticationService {
int maxFailedLoginCount = 5; int maxFailedLoginCount = 5;
/** /**
* 登 * 登
* *
* @param request request * @param request request
* @param servletRequest servletRequest * @param servletRequest servletRequest
@@ -51,7 +51,7 @@ public interface AuthenticationService {
void updatePassword(UserUpdatePasswordRequest request); void updatePassword(UserUpdatePasswordRequest request);
/** /**
* 获取登用户信息 * 获取登用户信息
* *
* @param userId userId * @param userId userId
* @return loginUser * @return loginUser

View File

@@ -3,8 +3,11 @@ package com.orion.ops.module.infra.service;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel; import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest; import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO; import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import java.util.List;
/** /**
* 操作日志 服务类 * 操作日志 服务类
* *
@@ -29,4 +32,12 @@ public interface OperatorLogService {
*/ */
DataGrid<OperatorLogVO> getOperatorLogPage(OperatorLogQueryRequest request); DataGrid<OperatorLogVO> getOperatorLogPage(OperatorLogQueryRequest request);
/**
* 查询用户登录日志
*
* @param username username
* @return rows
*/
List<LoginHistoryVO> getLoginHistory(String username);
} }

View File

@@ -71,9 +71,9 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Override @Override
public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) { public UserLoginVO login(UserLoginRequest request, HttpServletRequest servletRequest) {
// 登前检查 // 登前检查
this.preCheckLogin(request); this.preCheckLogin(request);
// 获取登用户 // 获取登用户
LambdaQueryWrapper<SystemUserDO> wrapper = systemUserDAO.wrapper() LambdaQueryWrapper<SystemUserDO> wrapper = systemUserDAO.wrapper()
.eq(SystemUserDO::getUsername, request.getUsername()); .eq(SystemUserDO::getUsername, request.getUsername());
SystemUserDO user = systemUserDAO.of(wrapper).getOne(); SystemUserDO user = systemUserDAO.of(wrapper).getOne();
@@ -90,17 +90,18 @@ public class AuthenticationServiceImpl implements AuthenticationService {
this.deleteUserCache(user); this.deleteUserCache(user);
// 重设用户缓存 // 重设用户缓存
this.setUserCache(user); this.setUserCache(user);
// 获取登陆 ip // 获取登录信息
String remoteAddr = Servlets.getRemoteAddr(servletRequest); String remoteAddr = Servlets.getRemoteAddr(servletRequest);
String location = IpUtils.getLocation(remoteAddr); String location = IpUtils.getLocation(remoteAddr);
String userAgent = Servlets.getUserAgent(servletRequest);
long current = System.currentTimeMillis(); long current = System.currentTimeMillis();
// 不允许多端登 // 不允许多端登
if (!allowMultiDevice) { if (!allowMultiDevice) {
// 无效化其他缓存 // 无效化其他缓存
this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location); this.invalidOtherDeviceToken(user.getId(), current, remoteAddr, location, userAgent);
} }
// 生成 loginToken // 生成 loginToken
String token = this.generatorLoginToken(user, current, remoteAddr, location); String token = this.generatorLoginToken(user, current, remoteAddr, location, userAgent);
return UserLoginVO.builder() return UserLoginVO.builder()
.token(token) .token(token)
.build(); .build();
@@ -108,7 +109,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Override @Override
public void logout(HttpServletRequest request) { public void logout(HttpServletRequest request) {
// 获取登 token // 获取登 token
String loginToken = SecurityUtils.obtainAuthorization(request); String loginToken = SecurityUtils.obtainAuthorization(request);
if (loginToken == null) { if (loginToken == null) {
return; return;
@@ -156,12 +157,12 @@ public class AuthenticationServiceImpl implements AuthenticationService {
@Override @Override
public LoginTokenDTO getLoginTokenInfo(String loginToken, boolean checkRefresh) { public LoginTokenDTO getLoginTokenInfo(String loginToken, boolean checkRefresh) {
// 获取登 key pair // 获取登 key pair
Pair<Long, Long> pair = this.getLoginTokenPair(loginToken); Pair<Long, Long> pair = this.getLoginTokenPair(loginToken);
if (pair == null) { if (pair == null) {
return null; return null;
} }
// 获取登 key value // 获取登 key value
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(pair.getKey(), pair.getValue()); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(pair.getKey(), pair.getValue());
String loginCache = redisTemplate.opsForValue().get(loginKey); String loginCache = redisTemplate.opsForValue().get(loginKey);
if (loginCache != null) { if (loginCache != null) {
@@ -181,7 +182,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
LoginTokenDTO refresh = JSON.parseObject(refreshCache, LoginTokenDTO.class); LoginTokenDTO refresh = JSON.parseObject(refreshCache, LoginTokenDTO.class);
int refreshCount = refresh.getRefreshCount() + 1; int refreshCount = refresh.getRefreshCount() + 1;
refresh.setRefreshCount(refreshCount); refresh.setRefreshCount(refreshCount);
// 设置登缓存 // 设置登缓存
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, refresh); RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, refresh);
if (refreshCount < maxRefreshCount) { if (refreshCount < maxRefreshCount) {
// 小于续签最大次数 则再次设置 refreshToken // 小于续签最大次数 则再次设置 refreshToken
@@ -213,7 +214,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
} }
/** /**
* 登预检查 * 登预检查
* *
* @param request request * @param request request
*/ */
@@ -222,7 +223,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
if (request.getPassword().length() != Const.MD5_LEN) { if (request.getPassword().length() != Const.MD5_LEN) {
throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR); throw Exceptions.argument(ErrorMessage.USERNAME_PASSWORD_ERROR);
} }
// 检查登失败次数 // 检查登失败次数
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
String failedCount = redisTemplate.opsForValue().get(failedCountKey); String failedCount = redisTemplate.opsForValue().get(failedCountKey);
if (failedCount != null && Integer.parseInt(failedCount) >= maxFailedLoginCount) { if (failedCount != null && Integer.parseInt(failedCount) >= maxFailedLoginCount) {
@@ -243,7 +244,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
if (user != null && user.getPassword().equals(Signatures.md5(request.getPassword()))) { if (user != null && user.getPassword().equals(Signatures.md5(request.getPassword()))) {
return true; return true;
} }
// 刷新登失败缓存 // 刷新登失败缓存
String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername()); String failedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(request.getUsername());
Long failedLoginCount = redisTemplate.opsForValue().increment(failedCountKey); Long failedLoginCount = redisTemplate.opsForValue().increment(failedCountKey);
RedisUtils.setExpire(failedCountKey, UserCacheKeyDefine.LOGIN_FAILED_COUNT); RedisUtils.setExpire(failedCountKey, UserCacheKeyDefine.LOGIN_FAILED_COUNT);
@@ -293,7 +294,7 @@ public class AuthenticationServiceImpl implements AuthenticationService {
private void deleteUserCache(SystemUserDO user) { private void deleteUserCache(SystemUserDO user) {
// 用户信息缓存 // 用户信息缓存
String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId()); String userInfoKey = UserCacheKeyDefine.USER_INFO.format(user.getId());
// 登失败次数缓存 // 登失败次数缓存
String loginFailedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername()); String loginFailedCountKey = UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(user.getUsername());
// 删除缓存 // 删除缓存
redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey)); redisTemplate.delete(Lists.of(userInfoKey, loginFailedCountKey));
@@ -324,34 +325,34 @@ public class AuthenticationServiceImpl implements AuthenticationService {
} }
/** /**
* 无效化其他登信息 * 无效化其他登信息
* *
* @param id id * @param id id
* @param loginTime loginTime * @param loginTime loginTime
* @param remoteAddr remoteAddr * @param remoteAddr remoteAddr
* @param location location * @param location location
* @param userAgent userAgent
*/ */
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
private void invalidOtherDeviceToken(Long id, long loginTime, String remoteAddr, String location) { private void invalidOtherDeviceToken(Long id, long loginTime,
String remoteAddr, String location, String userAgent) {
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*");
// 获取登信息 // 获取登信息
Set<String> loginKeyList = RedisUtils.scanKeys(loginKey); Set<String> loginKeyList = RedisUtils.scanKeys(loginKey);
if (!loginKeyList.isEmpty()) { if (!loginKeyList.isEmpty()) {
// 获取有效登信息 // 获取有效登信息
List<LoginTokenDTO> loginTokenInfoList = redisTemplate.opsForValue() List<LoginTokenDTO> loginTokenInfoList = redisTemplate.opsForValue()
.multiGet(loginKeyList) .multiGet(loginKeyList)
.stream() .stream()
.filter(Objects::nonNull) .filter(Objects::nonNull)
.map(s -> JSON.parseObject(s, LoginTokenDTO.class)) .map(s -> JSON.parseObject(s, LoginTokenDTO.class))
.filter(s -> LoginTokenStatusEnum.OK.getStatus().equals(s.getTokenStatus())) .filter(s -> LoginTokenStatusEnum.OK.getStatus().equals(s.getStatus()))
.collect(Collectors.toList()); .collect(Collectors.toList());
// 修改登信息 // 修改登信息
for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) { for (LoginTokenDTO loginTokenInfo : loginTokenInfoList) {
String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getLoginTime()); String deviceLoginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTokenInfo.getOrigin().getLoginTime());
loginTokenInfo.setTokenStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus()); loginTokenInfo.setStatus(LoginTokenStatusEnum.OTHER_DEVICE.getStatus());
loginTokenInfo.setLoginTime(loginTime); loginTokenInfo.setOverride(new LoginTokenDTO.Identity(loginTime, remoteAddr, location, userAgent));
loginTokenInfo.setIp(remoteAddr);
loginTokenInfo.setLocation(location);
RedisStrings.setJson(deviceLoginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginTokenInfo); RedisStrings.setJson(deviceLoginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginTokenInfo);
} }
} }
@@ -372,20 +373,19 @@ public class AuthenticationServiceImpl implements AuthenticationService {
* @param loginTime loginTime * @param loginTime loginTime
* @param remoteAddr remoteAddr * @param remoteAddr remoteAddr
* @param location location * @param location location
* @param userAgent userAgent
* @return loginToken * @return loginToken
*/ */
private String generatorLoginToken(SystemUserDO user, long loginTime, private String generatorLoginToken(SystemUserDO user, long loginTime,
String remoteAddr, String location) { String remoteAddr, String location, String userAgent) {
Long id = user.getId(); Long id = user.getId();
// 生成 loginToken // 生成 loginToken
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTime); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, loginTime);
LoginTokenDTO loginValue = LoginTokenDTO.builder() LoginTokenDTO loginValue = LoginTokenDTO.builder()
.id(id) .id(id)
.tokenStatus(LoginTokenStatusEnum.OK.getStatus()) .status(LoginTokenStatusEnum.OK.getStatus())
.refreshCount(0) .refreshCount(0)
.ip(remoteAddr) .origin(new LoginTokenDTO.Identity(loginTime, remoteAddr, location, userAgent))
.loginTime(loginTime)
.location(location)
.build(); .build();
RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue); RedisStrings.setJson(loginKey, UserCacheKeyDefine.LOGIN_TOKEN, loginValue);
// 生成 refreshToken // 生成 refreshToken

View File

@@ -3,16 +3,20 @@ package com.orion.ops.module.infra.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.orion.lang.define.wrapper.DataGrid; import com.orion.lang.define.wrapper.DataGrid;
import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel; import com.orion.ops.framework.biz.operator.log.core.model.OperatorLogModel;
import com.orion.ops.framework.common.constant.Const;
import com.orion.ops.module.infra.convert.OperatorLogConvert; import com.orion.ops.module.infra.convert.OperatorLogConvert;
import com.orion.ops.module.infra.dao.OperatorLogDAO; import com.orion.ops.module.infra.dao.OperatorLogDAO;
import com.orion.ops.module.infra.define.operator.AuthenticationOperatorType;
import com.orion.ops.module.infra.entity.domain.OperatorLogDO; import com.orion.ops.module.infra.entity.domain.OperatorLogDO;
import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest; import com.orion.ops.module.infra.entity.request.operator.OperatorLogQueryRequest;
import com.orion.ops.module.infra.entity.vo.LoginHistoryVO;
import com.orion.ops.module.infra.entity.vo.OperatorLogVO; import com.orion.ops.module.infra.entity.vo.OperatorLogVO;
import com.orion.ops.module.infra.service.OperatorLogService; import com.orion.ops.module.infra.service.OperatorLogService;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List;
/** /**
* 操作日志 服务实现类 * 操作日志 服务实现类
@@ -46,6 +50,19 @@ public class OperatorLogServiceImpl implements OperatorLogService {
.dataGrid(OperatorLogConvert.MAPPER::to); .dataGrid(OperatorLogConvert.MAPPER::to);
} }
@Override
public List<LoginHistoryVO> getLoginHistory(String username) {
// 条件
OperatorLogQueryRequest request = new OperatorLogQueryRequest();
request.setUsername(username);
request.setType(AuthenticationOperatorType.LOGIN);
LambdaQueryWrapper<OperatorLogDO> wrapper = this.buildQueryWrapper(request);
// 查询
return operatorLogDAO.of(wrapper)
.limit(Const.LOGIN_HISTORY_COUNT)
.list(OperatorLogConvert.MAPPER::toLoginHistory);
}
/** /**
* 构建查询 wrapper * 构建查询 wrapper
* *
@@ -55,12 +72,14 @@ public class OperatorLogServiceImpl implements OperatorLogService {
private LambdaQueryWrapper<OperatorLogDO> buildQueryWrapper(OperatorLogQueryRequest request) { private LambdaQueryWrapper<OperatorLogDO> buildQueryWrapper(OperatorLogQueryRequest request) {
return operatorLogDAO.wrapper() return operatorLogDAO.wrapper()
.eq(OperatorLogDO::getUserId, request.getUserId()) .eq(OperatorLogDO::getUserId, request.getUserId())
.eq(OperatorLogDO::getUsername, request.getUsername())
.eq(OperatorLogDO::getRiskLevel, request.getRiskLevel()) .eq(OperatorLogDO::getRiskLevel, request.getRiskLevel())
.eq(OperatorLogDO::getModule, request.getModule()) .eq(OperatorLogDO::getModule, request.getModule())
.eq(OperatorLogDO::getType, request.getType()) .eq(OperatorLogDO::getType, request.getType())
.eq(OperatorLogDO::getResult, request.getResult()) .eq(OperatorLogDO::getResult, request.getResult())
.ge(OperatorLogDO::getStartTime, request.getStartTimeStart()) .ge(OperatorLogDO::getStartTime, request.getStartTimeStart())
.le(OperatorLogDO::getStartTime, request.getStartTimeEnd()); .le(OperatorLogDO::getStartTime, request.getStartTimeEnd())
.orderByDesc(OperatorLogDO::getId);
} }
} }

View File

@@ -124,7 +124,7 @@ public class SystemUserServiceImpl implements SystemUserService {
// 更新用户 // 更新用户
int effect = systemUserDAO.updateById(updateRecord); int effect = systemUserDAO.updateById(updateRecord);
log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord)); log.info("SystemUserService-updateUserStatus effect: {}, updateRecord: {}", effect, JSON.toJSONString(updateRecord));
// 如果之前是锁定则删除登失败次数缓存 // 如果之前是锁定则删除登失败次数缓存
if (UserStatusEnum.LOCKED.getStatus().equals(record.getStatus())) { if (UserStatusEnum.LOCKED.getStatus().equals(record.getStatus())) {
redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername())); redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
} }
@@ -224,9 +224,9 @@ public class SystemUserServiceImpl implements SystemUserService {
update.setPassword(Signatures.md5(request.getPassword())); update.setPassword(Signatures.md5(request.getPassword()));
int effect = systemUserDAO.updateById(update); int effect = systemUserDAO.updateById(update);
log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect); log.info("SystemUserService-resetPassword record: {}, effect: {}", JSON.toJSONString(update), effect);
// 删除登失败次数缓存 // 删除登失败次数缓存
redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername())); redisTemplate.delete(UserCacheKeyDefine.LOGIN_FAILED_COUNT.format(record.getUsername()));
// 删除登缓存 // 删除登缓存
String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*"); String loginKey = UserCacheKeyDefine.LOGIN_TOKEN.format(id, "*");
Set<String> loginKeyList = RedisUtils.scanKeys(loginKey); Set<String> loginKeyList = RedisUtils.scanKeys(loginKey);
if (!loginKeyList.isEmpty()) { if (!loginKeyList.isEmpty()) {

View File

@@ -1,7 +1,7 @@
import axios from 'axios'; import axios from 'axios';
/** /**
* 登请求 * 登请求
*/ */
export interface LoginRequest { export interface LoginRequest {
username?: string; username?: string;
@@ -9,7 +9,7 @@ export interface LoginRequest {
} }
/** /**
* 登响应 * 登响应
*/ */
export interface LoginResponse { export interface LoginResponse {
token: string; token: string;
@@ -24,7 +24,7 @@ export interface UserUpdatePasswordRequest {
} }
/** /**
* 登 * 登
*/ */
export function login(data: LoginRequest) { export function login(data: LoginRequest) {
return axios.post<LoginResponse>('/infra/auth/login', data); return axios.post<LoginResponse>('/infra/auth/login', data);

View File

@@ -0,0 +1,75 @@
import type { DataGrid, Pagination } from '@/types/global';
import axios from 'axios';
/**
* 操作日志查询参数
*/
export interface OperatorLogQueryRequest extends Pagination {
userId?: number;
username?: string;
module?: string;
type?: string;
riskLevel?: string;
result?: number;
startTimeStart?: string;
startTimeEnd?: string;
}
/**
* 操作日志查询响应
*/
export interface OperatorLogQueryResponse {
id: number;
userId: number;
username: string;
traceId: string;
address: string;
location: string;
userAgent: string;
riskLevel: string;
module: string;
type: string;
logInfo: string;
extra: string;
result: number;
errorMessage: string;
returnValue: string;
duration: number;
startTime: number;
endTime: number;
createTime: number;
}
/**
* 登录日志查询响应
*/
export interface LoginHistoryQueryResponse {
id: number;
address: string;
location: string;
userAgent: string;
result: number;
errorMessage: string;
createTime: number;
}
/**
* 分页操作日志
*/
export function getOperatorLogPage(request: OperatorLogQueryRequest) {
return axios.post<DataGrid<OperatorLogQueryResponse>>('/infra/operator-log/query', request);
}
/**
* 查询登录日志
*/
export function getLoginHistory(username: string) {
return axios.get<LoginHistoryQueryResponse[]>('/infra/operator-log/login-history', { params: { username } });
}
/**
* 查询当前用户登录日志
*/
export function getCurrentLoginHistory() {
return axios.get<LoginHistoryQueryResponse[]>('/infra/operator-log/current-login-history');
}

View File

@@ -12,8 +12,6 @@ export interface UserCreateRequest {
avatar?: string; avatar?: string;
mobile?: string; mobile?: string;
email?: string; email?: string;
status?: number;
lastLoginTime?: string;
} }
/** /**
@@ -93,6 +91,20 @@ export function resetUserPassword(request: UserUpdateRequest) {
return axios.put('/infra/system-user/reset-password', request); return axios.put('/infra/system-user/reset-password', request);
} }
/**
* 查询当前用户
*/
export function getCurrentUser() {
return axios.get<UserQueryResponse>('/infra/system-user/get-current');
}
/**
* 更新当前用户
*/
export function updateCurrentUser(request: UserUpdateRequest) {
return axios.put('/infra/system-user/update-current', request);
}
/** /**
* 通过 id 查询用户 * 通过 id 查询用户
*/ */

View File

@@ -154,7 +154,7 @@
<template #content> <template #content>
<!-- 个人中心 --> <!-- 个人中心 -->
<a-doption> <a-doption>
<a-space @click="$router.push({ name: 'userMine' })"> <a-space @click="$router.push({ name: 'userInfo' })">
<icon-user /> <icon-user />
<span>个人中心</span> <span>个人中心</span>
</a-space> </a-space>

View File

@@ -17,9 +17,9 @@ const USER: AppRouteRecordRaw = {
component: () => import('@/views/user/user/index.vue'), component: () => import('@/views/user/user/index.vue'),
}, },
{ {
name: 'userMine', name: 'userInfo',
path: '/user/mine', path: '/user/info',
component: () => import('@/views/user/mine/index.vue'), component: () => import('@/views/user/info/index.vue'),
}, },
], ],
}; };

View File

@@ -56,9 +56,9 @@ export default defineStore('user', {
username: loginForm.username, username: loginForm.username,
password: md5(loginForm.password as string), password: md5(loginForm.password as string),
}; };
// 执行登 // 执行登
const res = await userLogin(loginRequest); const res = await userLogin(loginRequest);
// 设置登 token // 设置登 token
setToken(res.data.token); setToken(res.data.token);
} catch (err) { } catch (err) {
clearToken(); clearToken();

View File

@@ -81,7 +81,7 @@
if (!errors) { if (!errors) {
setLoading(true); setLoading(true);
try { try {
// 执行登 // 执行登
await userStore.login(values); await userStore.login(values);
// 跳转路由 // 跳转路由
const { redirect, ...othersQuery } = router.currentRoute.value.query; const { redirect, ...othersQuery } = router.currentRoute.value.query;

View File

@@ -76,6 +76,10 @@
@page-change="(page) => fetchTableData(page, pagination.pageSize)" @page-change="(page) => fetchTableData(page, pagination.pageSize)"
@page-size-change="(size) => fetchTableData(1, size)" @page-size-change="(size) => fetchTableData(1, size)"
:bordered="false"> :bordered="false">
<!-- 配置项 -->
<template #keyName="{record}">
{{ record.keyName }}<span style="margin: 0 4px;">-</span>{{ record.keyDescription }}
</template>
<!-- 值 --> <!-- 值 -->
<template #value="{ record }"> <template #value="{ record }">
<span class="copy-left" title="复制" @click="copy(record.value)"> <span class="copy-left" title="复制" @click="copy(record.value)">

View File

@@ -15,10 +15,7 @@ const columns = [
slotName: 'keyName', slotName: 'keyName',
align: 'left', align: 'left',
ellipsis: true, ellipsis: true,
tooltip: true, tooltip: true
render: ({ record }) => {
return `${record.keyName} - ${record.keyDescription}`;
},
}, { }, {
title: '配置描述', title: '配置描述',
dataIndex: 'label', dataIndex: 'label',

View File

@@ -0,0 +1,131 @@
<template>
<a-spin :loading="loading" class="main-container">
<span class="extra-message">只展示最近登录的 30 条历史记录</span>
<a-timeline>
<a-timeline-item v-for="item in list"
:key="item.id">
<!-- 图标 -->
<template #dot>
<div class="icon-container">
<icon-desktop />
</div>
</template>
<!-- 日志行 -->
<div class="log-line">
<!-- 地址行 -->
<span class="address-line">
<span class="mr8">{{ item.address }}</span>
<span>{{ item.location }}</span>
</span>
<!-- 错误信息行 -->
<span class="error-line" v-if="item.result === ResultStatus.FAILED">
登录失败: {{ item.errorMessage }}
</span>
<!-- 时间行 -->
<span class="time-line">
{{ dateFormat(new Date(item.createTime)) }}
</span>
<!-- ua -->
<span class="ua-line">
{{ item.userAgent }}
</span>
</div>
</a-timeline-item>
</a-timeline>
</a-spin>
</template>
<script lang="ts">
export default {
name: 'login-history'
};
</script>
<script lang="ts" setup>
import type { LoginHistoryQueryResponse } from '@/api/user/operator-log';
import useLoading from '@/hooks/loading';
import { ref, onMounted } from 'vue';
import { useUserStore } from '@/store';
import { ResultStatus } from '../types/const';
import { getCurrentLoginHistory } from '@/api/user/operator-log';
import { dateFormat } from '@/utils';
const list = ref<LoginHistoryQueryResponse[]>([]);
const userStore = useUserStore();
const { loading, setLoading } = useLoading();
// 查询操作日志
onMounted(async () => {
try {
setLoading(true);
const { data } = await getCurrentLoginHistory();
list.value = data;
} catch (e) {
} finally {
setLoading(false);
}
});
</script>
<style lang="less" scoped>
.main-container {
width: 100%;
min-height: 200px;
padding-left: 48px;
}
.extra-message {
margin-bottom: 38px;
margin-left: -20px;
display: block;
color: var(--color-text-3);
user-select: none;
}
.icon-container {
border-radius: 50%;
width: 56px;
height: 56px;
background: var(--color-fill-4);
font-size: 28px;
color: #FFFFFF;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.arco-timeline-item-content-wrapper) {
position: relative;
margin-left: 44px;
margin-top: -22px;
}
:deep(.arco-timeline-item) {
padding-bottom: 36px;
}
.log-line {
display: flex;
flex-direction: column;
.address-line {
color: var(--color-text-1);
font-size: 16px;
font-weight: 600;
}
.time-line, .ua-line, .error-line {
color: var(--color-text-3);
font-size: 14px;
margin-top: 2px;
}
.error-line {
color: rgb(var(--danger-6));
font-weight: 600;
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<a-spin :loading="loading" style="width: 400px;">
<!-- 头像 -->
<div class="avatar-container">
<div class="avatar-wrapper">
<a-avatar :size="88"
:style="{ backgroundColor: '#3370ff' }">
{{ nickname }}
</a-avatar>
</div>
</div>
<a-form :model="formModel"
ref="formRef"
label-align="right"
size="medium"
:style="{ width: '100%' }"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 18 }"
:rules="formRules">
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" disabled />
</a-form-item>
<!-- 花名 -->
<a-form-item field="nickname" label="花名">
<a-input v-model="formModel.nickname" placeholder="请输入花名" />
</a-form-item>
<!-- 手机号 -->
<a-form-item field="mobile" label="手机号">
<a-input v-model="formModel.mobile" placeholder="请输入手机号" />
</a-form-item>
<!-- 邮箱 -->
<a-form-item field="email" label="邮箱">
<a-input v-model="formModel.email" placeholder="请输入邮箱" />
</a-form-item>
</a-form>
<!-- 操作 -->
<div class="handler-container">
<a-button type="primary" @click="save">保存</a-button>
</div>
</a-spin>
</template>
<script lang="ts">
export default {
name: 'user-info'
};
</script>
<script lang="ts" setup>
import type { UserUpdateRequest } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { computed, ref, onMounted } from 'vue';
import formRules from '../../user/types/form.rules';
import { useUserStore } from '@/store';
import { getCurrentUser, updateCurrentUser } from '@/api/user/user';
import { pick } from 'lodash';
const userStore = useUserStore();
const { loading, setLoading } = useLoading();
const formRef = ref();
const formModel = ref<UserUpdateRequest>({});
// 用户名
const nickname = computed(() => userStore.nickname?.substring(0, 1));
// 保存
const save = async () => {
setLoading(true);
try {
await updateCurrentUser(formModel.value);
userStore.nickname = formModel.value.nickname;
} catch (e) {
} finally {
setLoading(false);
}
};
// 加载用户信息
onMounted(async () => {
setLoading(true);
try {
const { data } = await getCurrentUser();
formModel.value = pick(data, 'id', 'username', 'nickname', 'mobile', 'email');
} catch (e) {
} finally {
setLoading(false);
}
});
</script>
<style lang="less" scoped>
.avatar-container {
display: flex;
justify-content: flex-end;
padding: 4px 0;
.avatar-wrapper {
display: flex;
justify-content: center;
margin-bottom: 16px;
width: calc(100% / 24 * 18);
}
}
.handler-container {
display: flex;
margin-left: calc(100% / 24 * 6);
}
</style>

View File

@@ -0,0 +1,55 @@
<template>
<div class="tabs-container">
<a-tabs type="rounded"
size="medium"
position="left"
:lazy-load="true"
:destroy-on-hide="true">
<!-- 个人信息 -->
<a-tab-pane key="1" title="个人信息">
<user-info />
</a-tab-pane>
<!-- 登录日志 -->
<a-tab-pane key="2" title="登录日志">
<login-history />
</a-tab-pane>
<!-- 登录设备 -->
<a-tab-pane key="3" title="登录设备">
<login-history />
</a-tab-pane>
<!-- 操作日志 -->
<a-tab-pane key="4" title="操作日志">
</a-tab-pane>
</a-tabs>
</div>
</template>
<script lang="ts">
export default {
name: 'userInfo'
};
</script>
<script lang="ts" setup>
import UserInfo from './components/user-info.vue';
import LoginHistory from './components/login-history.vue';
</script>
<style lang="less" scoped>
.tabs-container {
background: #FFFFFF;
margin: 16px 16px 0 16px;
padding: 16px;
display: flex;
flex-direction: column;
border-radius: 4px;
}
:deep(.arco-tabs-pane) {
border-left: 1px var(--color-neutral-3) solid;
}
:deep(.arco-tabs-tab-title) {
user-select: none;
}
</style>

View File

@@ -0,0 +1,7 @@
// 结果状态
export const ResultStatus = {
// 失败
FAILED: 0,
// 成功
SUCCESS: 1,
};

View File

@@ -1,53 +0,0 @@
<template>
<a-row class="layout-container">
<a-col :span="8">
<a-card class="general-card">
<template #title>
用户信息
</template>
<a-spin :loading="loading" style="width: 100%">
<a-form :model="formModel"
ref="formRef"
label-align="right"
:label-col-props="{ span: 6 }"
:wrapper-col-props="{ span: 16 }"
:rules="formRules">
<!-- 用户名 -->
<a-form-item field="username" label="用户名">
<a-input v-model="formModel.username" disabled />
</a-form-item>
<!-- 花名 -->
<a-form-item field="nickname" label="花名">
<a-input v-model="formModel.nickname" placeholder="请输入花名" />
</a-form-item>
<!-- 手机号 -->
<a-form-item field="mobile" label="手机号">
<a-input v-model="formModel.mobile" placeholder="请输入手机号" />
</a-form-item>
<!-- 邮箱 -->
<a-form-item field="email" label="邮箱">
<a-input v-model="formModel.email" placeholder="请输入邮箱" />
</a-form-item>
</a-form>
</a-spin>
</a-card>
</a-col>
</a-row>
</template>
<script lang="ts" setup>
import type { UserUpdateRequest } from '@/api/user/user';
import useLoading from '@/hooks/loading';
import { ref } from 'vue';
import formRules from '../user/types/form.rules';
const { loading, setLoading } = useLoading();
const formRef = ref();
const formModel = ref<UserUpdateRequest>({});
</script>
<style lang="less" scoped>
</style>

View File

@@ -114,7 +114,7 @@ INSERT INTO `dict_value` VALUES (60, 1, 'operatorLogModule', 'infra:dict-value',
INSERT INTO `dict_value` VALUES (61, 1, 'operatorLogModule', 'asset:host', '主机操作', '{}', 2000, '2023-10-31 10:48:16', '2023-10-31 10:53:54', '1', '1', 0); INSERT INTO `dict_value` VALUES (61, 1, 'operatorLogModule', 'asset:host', '主机操作', '{}', 2000, '2023-10-31 10:48:16', '2023-10-31 10:53:54', '1', '1', 0);
INSERT INTO `dict_value` VALUES (62, 1, 'operatorLogModule', 'asset:host-key', '主机秘钥', '{}', 2020, '2023-10-31 10:48:17', '2023-10-31 10:54:34', '1', '1', 0); INSERT INTO `dict_value` VALUES (62, 1, 'operatorLogModule', 'asset:host-key', '主机秘钥', '{}', 2020, '2023-10-31 10:48:17', '2023-10-31 10:54:34', '1', '1', 0);
INSERT INTO `dict_value` VALUES (63, 1, 'operatorLogModule', 'asset:host-identity', '主机身份', '{}', 2030, '2023-10-31 10:48:19', '2023-10-31 10:54:26', '1', '1', 0); INSERT INTO `dict_value` VALUES (63, 1, 'operatorLogModule', 'asset:host-identity', '主机身份', '{}', 2030, '2023-10-31 10:48:19', '2023-10-31 10:54:26', '1', '1', 0);
INSERT INTO `dict_value` VALUES (64, 2, 'operatorLogType', 'authentication:login', '系统', '{}', 10, '2023-10-31 10:55:26', '2023-10-31 11:05:41', '1', '1', 0); INSERT INTO `dict_value` VALUES (64, 2, 'operatorLogType', 'authentication:login', '系统', '{}', 10, '2023-10-31 10:55:26', '2023-10-31 11:05:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (65, 2, 'operatorLogType', 'authentication:logout', '登出系统', '{}', 20, '2023-10-31 10:55:27', '2023-10-31 11:05:41', '1', '1', 0); INSERT INTO `dict_value` VALUES (65, 2, 'operatorLogType', 'authentication:logout', '登出系统', '{}', 20, '2023-10-31 10:55:27', '2023-10-31 11:05:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (66, 2, 'operatorLogType', 'authentication:update-password', '修改密码', '{}', 30, '2023-10-31 10:55:30', '2023-10-31 11:05:41', '1', '1', 0); INSERT INTO `dict_value` VALUES (66, 2, 'operatorLogType', 'authentication:update-password', '修改密码', '{}', 30, '2023-10-31 10:55:30', '2023-10-31 11:05:41', '1', '1', 0);
INSERT INTO `dict_value` VALUES (67, 2, 'operatorLogType', 'system-user:create', '创建用户', '{}', 10, '2023-10-31 10:55:42', '2023-10-31 11:05:41', '1', '1', 0); INSERT INTO `dict_value` VALUES (67, 2, 'operatorLogType', 'system-user:create', '创建用户', '{}', 10, '2023-10-31 10:55:42', '2023-10-31 11:05:41', '1', '1', 0);