Compare commits

...

4 Commits

Author SHA1 Message Date
db55b2f748 feat(operation-log): 添加操作日志功能
- 新增操作日志相关实体类、控制器、服务类和仓库类
- 实现操作日志的记录和查询功能- 添加操作日志的数据库表结构
- 优化用户相关操作的日志记录
2025-02-16 17:50:55 +08:00
ee8d7d16d0 refactor(user): 优化用户创建和更新逻辑
- 修改 UserCreateDto 和 UserUpdateDto 中的 roleIds 类型从 Set 改为 List
- 更新 UserCreateDto 和 UserUpdateDto 中的 roleId 字段注释
- 调整 UserServiceImpl 中的 create 和 addUserRole 方法,以适应新的 List 类型
- 为 UserCreateDto 和 UserUpdateDto 中的 roleIds 添加 NotEmpty 注解
2025-02-11 20:57:54 +08:00
b4da8c3bf0 refactor(system): 优化用户管理相关代码
- 调整代码格式和缩进,提高可读性
- 重构用户角色关联逻辑,提取为独立方法- 更新用户更新 DTO,支持多角色选择
2025-02-06 20:38:05 +08:00
e4bf57f3ec feat(user): 增加用户角色管理功能
- 在 User 实体中添加 roles 字段,用于存储用户拥有的所有角色
- 在 UserCreateDto 中添加 roleIds 字段,用于创建用户时指定多个角色
- 修改 UserDetailsImpl以支持多个角色
- 新增 UserRolesRepository 接口,用于管理用户角色关联
- 更新 UserServiceImpl 中的 create 方法,支持创建用户时分配多个角色
2025-02-06 15:45:00 +08:00
29 changed files with 540 additions and 166 deletions

18
pom.xml
View File

@ -142,7 +142,23 @@
<version>3.0.0</version> <version>3.0.0</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.16</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>

View File

@ -2,11 +2,10 @@ package com.zsc.edu.gateway;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* @author zhuang
*/
@SpringBootApplication @SpringBootApplication
@EnableAspectJAutoProxy
public class IotGatewayApplication { public class IotGatewayApplication {
public static void main(String[] args) { public static void main(String[] args) {

View File

@ -2,15 +2,11 @@ package com.zsc.edu.gateway.framework.security;
import com.zsc.edu.gateway.common.util.TreeUtil; import com.zsc.edu.gateway.common.util.TreeUtil;
import com.zsc.edu.gateway.exception.StateException; import com.zsc.edu.gateway.exception.StateException;
import com.zsc.edu.gateway.modules.system.entity.Dept; import com.zsc.edu.gateway.modules.system.entity.*;
import com.zsc.edu.gateway.modules.system.entity.Menu; import com.zsc.edu.gateway.modules.system.repo.*;
import com.zsc.edu.gateway.modules.system.entity.RoleAuthority;
import com.zsc.edu.gateway.modules.system.entity.User;
import com.zsc.edu.gateway.modules.system.repo.AuthorityRepository;
import com.zsc.edu.gateway.modules.system.repo.MenuRepository;
import com.zsc.edu.gateway.modules.system.repo.RoleAuthoritiesRepository;
import com.zsc.edu.gateway.modules.system.repo.UserRepository;
import com.zsc.edu.gateway.modules.system.service.DeptService; import com.zsc.edu.gateway.modules.system.service.DeptService;
import com.zsc.edu.gateway.modules.system.service.RoleService;
import com.zsc.edu.gateway.modules.system.service.impl.RoleServiceImpl;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
@ -33,6 +29,9 @@ public class JpaUserDetailsServiceImpl implements UserDetailsService {
private final AuthorityRepository authorityRepository; private final AuthorityRepository authorityRepository;
private final MenuRepository menuRepository; private final MenuRepository menuRepository;
private final DeptService deptService; private final DeptService deptService;
private final RoleRepository roleRepository;
private final RoleService roleService;
private final UserRolesRepository userRolesRepository;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@ -41,6 +40,9 @@ public class JpaUserDetailsServiceImpl implements UserDetailsService {
if (!user.getEnableState()) { if (!user.getEnableState()) {
throw new StateException("用户 '" + username + "' 已被禁用!请联系管理员"); throw new StateException("用户 '" + username + "' 已被禁用!请联系管理员");
} }
List<Long> roleIds = userRolesRepository.selectByUserId(user.getId());
List<Role> roles = roleRepository.selectByIds(roleIds);
user.setRoles(roles);
List<Dept> depts = deptService.listTree(user.deptId); List<Dept> depts = deptService.listTree(user.deptId);
List<Dept> flat = TreeUtil.flat(depts, Dept::getChildren, d -> d.setChildren(null)); List<Dept> flat = TreeUtil.flat(depts, Dept::getChildren, d -> d.setChildren(null));
Set<Long> dataScopeDeptIds = flat.stream().map(Dept::getId).collect(Collectors.toSet()); Set<Long> dataScopeDeptIds = flat.stream().map(Dept::getId).collect(Collectors.toSet());

View File

@ -11,6 +11,7 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection; import java.util.Collection;
import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -32,11 +33,12 @@ public class UserDetailsImpl implements UserDetails {
public String name; public String name;
public Dept dept; public Dept dept;
public Role role; public Role role;
public List<Role> roles;
public Set<Authority> authorities; public Set<Authority> authorities;
public Set<String> permissions; public Set<String> permissions;
public Set<Long> dataScopeDeptIds; public Set<Long> dataScopeDeptIds;
public UserDetailsImpl(Long id, String username, String password, String name, Boolean enableState, Dept dept, Set<Long> dataScopeDeptIds, Role role, Set<Authority> authorities, Set<String> permissions) { public UserDetailsImpl(Long id, String username, String password, String name, Boolean enableState, Dept dept, Set<Long> dataScopeDeptIds, Role role, Set<Authority> authorities, Set<String> permissions, List<Role> roles) {
this.id = id; this.id = id;
this.username = username; this.username = username;
this.password = password; this.password = password;
@ -47,6 +49,7 @@ public class UserDetailsImpl implements UserDetails {
this.role = role; this.role = role;
this.authorities = authorities; this.authorities = authorities;
this.permissions = permissions; this.permissions = permissions;
this.roles = roles;
} }
public static UserDetailsImpl from(User user, Set<String> permissions) { public static UserDetailsImpl from(User user, Set<String> permissions) {
@ -60,7 +63,8 @@ public class UserDetailsImpl implements UserDetails {
user.dataScopeDeptIds, user.dataScopeDeptIds,
user.role, user.role,
user.role.authorities, user.role.authorities,
permissions permissions,
user.roles
); );
} }

View File

@ -51,9 +51,9 @@ public class BulletinServiceImpl extends ServiceImpl<BulletinRepository, Bulleti
if (state != null) { if (state != null) {
bulletinVo.getState().checkStatus(state); bulletinVo.getState().checkStatus(state);
} }
bulletinVo.setEditUsername(userRepository.selectNameById(bulletinVo.getEditUserId())); bulletinVo.setEditUsername(UserRepository.selectNameById(bulletinVo.getEditUserId()));
bulletinVo.setPublishUsername(userRepository.selectNameById(bulletinVo.getPublishUserId())); bulletinVo.setPublishUsername(UserRepository.selectNameById(bulletinVo.getPublishUserId()));
bulletinVo.setCloseUsername(userRepository.selectNameById(bulletinVo.getCloseUserId())); bulletinVo.setCloseUsername(UserRepository.selectNameById(bulletinVo.getCloseUserId()));
return bulletinVo; return bulletinVo;
} }
/** /**

View File

@ -0,0 +1,48 @@
package com.zsc.edu.gateway.modules.operationLog.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
import com.zsc.edu.gateway.modules.operationLog.query.OperationLogQuery;
import com.zsc.edu.gateway.modules.operationLog.repo.OperationLogRepository;
import lombok.AllArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* @author zhuang
*/
@AllArgsConstructor
@RestController
@RequestMapping("/api/rest/log")
public class OperationController {
private OperationLogRepository repo;
/**
* 获取操作日志详情
*/
@GetMapping("/{id}")
public OperationLog crate(@PathVariable("id") Long id) {
return repo.selectById(id);
}
/**
* 获取操作日志分页
*/
@GetMapping("")
public Page<OperationLog> query(Page<OperationLog> page, OperationLogQuery query) {
return repo.selectPage(page, query.wrapper());
}
/**
* 批量删除操作日志
*/
@DeleteMapping("/batch")
public int deleteBatch(@RequestBody List<Long> ids) {
LambdaQueryWrapper<OperationLog> wrapper = new LambdaQueryWrapper<>();
wrapper.in(OperationLog::getId, ids);
return repo.delete(wrapper);
}
}

View File

@ -1,36 +0,0 @@
package com.zsc.edu.gateway.modules.operationLog.dto;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* @author zhuang
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperationLogDto {
/**
* 模块类型
*/
private OperationLog.ModelType modelType;
/**
* 操作类型
*/
private OperationLog.OperationType operationType;
/**
* 操作内容
*/
private String content;
/**
* 操作时间
*/
private LocalDateTime makeTime;
}

View File

@ -1,5 +1,8 @@
package com.zsc.edu.gateway.modules.operationLog.entity; package com.zsc.edu.gateway.modules.operationLog.entity;
import lombok.Getter;
@Getter
public class ExpressionRootObject { public class ExpressionRootObject {
private final Object object; private final Object object;
private final Object[] args; private final Object[] args;
@ -9,11 +12,4 @@ public class ExpressionRootObject {
this.args = args; this.args = args;
} }
public Object getObject() {
return object;
}
public Object[] getArgs() {
return args;
}
} }

View File

@ -0,0 +1,51 @@
package com.zsc.edu.gateway.modules.operationLog.entity;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.zsc.edu.gateway.common.enums.IState;
import lombok.Getter;
/**
* @author lenovo
*/
@Getter
public enum FunctionTypeEnum implements IEnum<String>, IState<FunctionTypeEnum> {
save("save", "save"),
update("update", "update"),
delete("delete", "delete"),
query("query", "query"),
other("other", "other");
private final String code;
private final String message;
FunctionTypeEnum(String code, String message) {
this.code = code;
this.message = message;
}
@Override
public String getValue() {
return code;
}
@Override
public String toString() {
return this.message;
}
/**
* 根据代码获取消息
*
* @param code 代码
* @return 消息
*/
public static String getMessageByCode(String code) {
for (FunctionTypeEnum type : FunctionTypeEnum.values()) {
if (type.getCode().equalsIgnoreCase(code)) {
return type.getMessage();
}
}
return other.getMessage();
}
}

View File

@ -0,0 +1,66 @@
package com.zsc.edu.gateway.modules.operationLog.entity;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.zsc.edu.gateway.common.enums.IState;
import lombok.Getter;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* @author lenovo
*/
@Getter
public enum ModuleTypeEnum implements IEnum<String>, IState<ModuleTypeEnum> {
user("user", "user"),
role("role", "role"),
menu("menu", "menu"),
dept("dept", "dept"),
device("device", "device"),
product("product", "product"),
serve("serve", "serve"),
event("event", "event"),
property("property", "property"),
notice("notice", "notice"),
bulletin("bulletin", "bulletin"),
attachment("attachment", "attachment"),
other("other", "other");
private final String code;
private final String messageCode;
private static final Map<String, ModuleTypeEnum> CODE_MAP = Arrays.stream(values())
.collect(Collectors.toMap(
type -> type.getCode().toLowerCase(),
type -> type
));
ModuleTypeEnum(String code, String message) {
this.code = code;
this.messageCode = message;
}
@Override
public String getValue() {
return code;
}
@Override
public String toString() {
return this.messageCode;
}
/**
* 根据代码获取消息
*
* @param code 代码
* @return 消息
*/
public static String getMessageByCode(String code) {
ModuleTypeEnum type = CODE_MAP.get(code.toLowerCase());
return Objects.requireNonNullElse(type, other).getMessageCode();
}
}

View File

@ -1,10 +1,8 @@
package com.zsc.edu.gateway.modules.operationLog.entity; package com.zsc.edu.gateway.modules.operationLog.entity;
import com.baomidou.mybatisplus.annotation.IEnum;
import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.annotation.TableName;
import com.zsc.edu.gateway.common.enums.IState;
import lombok.*; import lombok.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@ -27,12 +25,12 @@ public class OperationLog {
/** /**
* 模块类型 * 模块类型
*/ */
private ModelType modelType; private ModuleTypeEnum moduleType;
/** /**
* 操作类型 * 操作类型
*/ */
private OperationType operationType; private FunctionTypeEnum functionType;
/** /**
* 操作内容 * 操作内容
@ -43,53 +41,4 @@ public class OperationLog {
* 操作时间 * 操作时间
*/ */
private LocalDateTime makeTime; private LocalDateTime makeTime;
public enum ModelType implements IEnum<Integer>, IState<ModelType> {
ATTACHMENT(1, "附件模块"),
IOT(2, "物模型模块"),
MESSAGE(3, "公告消息模块"),
SYSTEM(4, "系统模块");
private final Integer value;
private final String name;
ModelType(int value, String name) {
this.value = value;
this.name = name;
}
@Override
public Integer getValue() {
return this.value;
}
@Override
public String toString() {
return this.name;
}
}
public enum OperationType implements IEnum<Integer>, IState<OperationType> {
CREATE(1, "添加"),
UPDATE(2, "更新"),
DELETE(3, "删除");
private final Integer value;
private final String name;
OperationType(int value, String name) {
this.value = value;
this.name = name;
}
@Override
public Integer getValue() {
return this.value;
}
@Override
public String toString() {
return this.name;
}
}
} }

View File

@ -13,4 +13,9 @@ public @interface OperationLogAnnotation {
* 日志内容支持SpEL表达式 * 日志内容支持SpEL表达式
*/ */
String content() default ""; String content() default "";
/**
* 操作类型例如SAVE, UPDATE, DELETE, QUERY
*/
String operationType() default "";
} }

View File

@ -1,15 +0,0 @@
package com.zsc.edu.gateway.modules.operationLog.mapper;
import com.zsc.edu.gateway.common.mapstruct.BaseMapper;
import com.zsc.edu.gateway.modules.operationLog.dto.OperationLogDto;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
import org.mapstruct.Mapper;
import org.mapstruct.MappingConstants;
import org.mapstruct.ReportingPolicy;
/**
* @author zhuang
*/
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface OperationLogMapper extends BaseMapper<OperationLog, OperationLogDto> {
}

View File

@ -0,0 +1,41 @@
package com.zsc.edu.gateway.modules.operationLog.query;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
import com.zsc.edu.gateway.modules.operationLog.entity.FunctionTypeEnum;
import com.zsc.edu.gateway.modules.operationLog.entity.ModuleTypeEnum;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
import java.util.Objects;
/**
* @author zhuang
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OperationLogQuery {
private String username;
private ModuleTypeEnum moduleType;
private FunctionTypeEnum functionType;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime publishTimeBegin;
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime publishTimeEnd;
public LambdaQueryWrapper<OperationLog> wrapper() {
LambdaQueryWrapper<OperationLog> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.like(StringUtils.hasText(this.username), OperationLog::getContent, this.username);
queryWrapper.eq(Objects.nonNull(this.moduleType), OperationLog::getModuleType, this.moduleType);
queryWrapper.eq(Objects.nonNull(this.functionType), OperationLog::getFunctionType, this.functionType);
if (publishTimeBegin != null && publishTimeEnd != null) {
queryWrapper.between(OperationLog::getMakeTime, this.publishTimeBegin, this.publishTimeEnd);
}
return queryWrapper;
}
}

View File

@ -3,5 +3,8 @@ package com.zsc.edu.gateway.modules.operationLog.repo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog; import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
/**
* @author zhuang
*/
public interface OperationLogRepository extends BaseMapper<OperationLog> { public interface OperationLogRepository extends BaseMapper<OperationLog> {
} }

View File

@ -1,7 +0,0 @@
package com.zsc.edu.gateway.modules.operationLog.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
public interface OperationLogService extends IService<OperationLog> {
}

View File

@ -1,16 +0,0 @@
package com.zsc.edu.gateway.modules.operationLog.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
import com.zsc.edu.gateway.modules.operationLog.repo.OperationLogRepository;
import com.zsc.edu.gateway.modules.operationLog.service.OperationLogService;
import lombok.AllArgsConstructor;
import org.springframework.stereotype.Service;
/**
* @author zhuang
*/
@AllArgsConstructor
@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogRepository, OperationLog> implements OperationLogService {
}

View File

@ -0,0 +1,41 @@
package com.zsc.edu.gateway.modules.operationLog.util;
import com.zsc.edu.gateway.modules.operationLog.entity.ExpressionRootObject;
import org.springframework.aop.support.AopUtils;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.CachedExpressionEvaluator;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.Expression;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ExpressionEvaluator<T> extends CachedExpressionEvaluator {
private final ParameterNameDiscoverer paramNameDiscoverer = (ParameterNameDiscoverer) new DefaultParameterNameDiscoverer();
private final Map<ExpressionKey, Expression> conditionCache = new ConcurrentHashMap<>(64);
private final Map<AnnotatedElementKey, Method> targetMethodCache = new ConcurrentHashMap<>(64);
public MethodBasedEvaluationContext createEvaluationContext(Object object, Class<?> targetClass, Method method, Object[] args) {
Method targetMethod = getTargetMethod(targetClass, method);
ExpressionRootObject root = new ExpressionRootObject(object, args);
return new MethodBasedEvaluationContext(root, targetMethod, args, this.paramNameDiscoverer);
}
public T condition(String conditionExpression, AnnotatedElementKey elementKey, MethodBasedEvaluationContext evalContext, Class<String> clazz) {
return (T) getExpression(this.conditionCache, elementKey, conditionExpression).getValue(evalContext, clazz);
}
private Method getTargetMethod(Class<?> targetClass, Method method) {
AnnotatedElementKey methodKey = new AnnotatedElementKey(method, targetClass);
Method targetMethod = this.targetMethodCache.get(methodKey);
if (targetMethod == null) {
targetMethod = AopUtils.getMostSpecificMethod(method, targetClass);
this.targetMethodCache.put(methodKey, targetMethod);
}
return targetMethod;
}
}

View File

@ -0,0 +1,120 @@
package com.zsc.edu.gateway.modules.operationLog.util;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.zsc.edu.gateway.framework.security.UserDetailsImpl;
import com.zsc.edu.gateway.modules.operationLog.entity.FunctionTypeEnum;
import com.zsc.edu.gateway.modules.operationLog.entity.ModuleTypeEnum;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLog;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLogAnnotation;
import com.zsc.edu.gateway.modules.operationLog.repo.OperationLogRepository;
import com.zsc.edu.gateway.modules.system.repo.UserRepository;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.expression.AnnotatedElementKey;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import lombok.extern.slf4j.Slf4j;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
/**
* @author zhuang
*/
@Slf4j
@Aspect
@Component
public class OperationLogAspect {
private static final ExpressionEvaluator<String> EVALUATOR = new ExpressionEvaluator<>();
@Resource
OperationLogRepository operationLogRepository;
@Resource
UserRepository userRepository;
/**
* 被执行方法上添加OperationLogAnnotation注解的才会执行这个方法
*/
@AfterReturning(pointcut = "@annotation(com.zsc.edu.gateway.modules.operationLog.entity.OperationLogAnnotation)", returning = "jsonResult")
public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
handleOperationLog(joinPoint, jsonResult);
}
/**
* 操作日志处理逻辑包括格式处理持久化
*/
protected void handleOperationLog(final JoinPoint joinPoint, Object jsonResult) {
OperationLogAnnotation operationLogAnnotation = ((MethodSignature) joinPoint.getSignature()).getMethod().
getAnnotation(OperationLogAnnotation.class);
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = attributes.getRequest();
// 获取方法名
String methodName = joinPoint.getSignature().getName().toLowerCase();
// 根据方法名判定方法类型
String methodType = FunctionTypeEnum.getMessageByCode(methodName);
// 获取类名
String className = joinPoint.getTarget().getClass().getSimpleName().toLowerCase();
String moduleType = className.substring(0, className.length() - "controller".length());
moduleType = ModuleTypeEnum.getMessageByCode(moduleType);
// 使用SecurityContextHolder获取当前用户信息
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
// content的默认值为返回结果
String content = "";
// 获取operationLogAnnotation注解的content内容
if (StringUtils.isNotBlank(operationLogAnnotation.content())) {
// 解析EL表达式
content = this.evalExpression(joinPoint, operationLogAnnotation.content());
log.info("expression:{}", content);
}
// 获取操作类型
String operationType = operationLogAnnotation.operationType();
if (StringUtils.isNotBlank(operationType)) {
content = operationType + " " + content;
}
// 添加操作人信息
content = "操作人:" + userDetails.getUsername() + " " + content;
// 操作日志保存到数据库内
OperationLog operationLog = new OperationLog();
if (StringUtils.isNotBlank(moduleType) && StringUtils.isNotBlank(methodType)) {
operationLog.setModuleType(ModuleTypeEnum.valueOf(moduleType));
operationLog.setFunctionType(FunctionTypeEnum.valueOf(methodType));
operationLog.setContent(content);
operationLog.setMakeTime(LocalDateTime.now());
operationLogRepository.insert(operationLog);
}
}
}
private String evalExpression(JoinPoint point, String expression) {
MethodSignature ms = (MethodSignature) point.getSignature();
Method method = ms.getMethod();
Object[] args = point.getArgs();
Object target = point.getTarget();
Class<?> targetClass = target.getClass();
MethodBasedEvaluationContext context = EVALUATOR.createEvaluationContext(target, target.getClass(), method, args);
AnnotatedElementKey elementKey = new AnnotatedElementKey(method, targetClass);
return EVALUATOR.condition(expression, elementKey, context, String.class);
}
}

View File

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.sun.source.tree.Tree; import com.sun.source.tree.Tree;
import com.zsc.edu.gateway.framework.mybatisplus.DataPermission; import com.zsc.edu.gateway.framework.mybatisplus.DataPermission;
import com.zsc.edu.gateway.framework.security.UserDetailsImpl; import com.zsc.edu.gateway.framework.security.UserDetailsImpl;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLogAnnotation;
import com.zsc.edu.gateway.modules.system.dto.UserCreateDto; import com.zsc.edu.gateway.modules.system.dto.UserCreateDto;
import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdateDto; import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdateDto;
import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdatePasswordDto; import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdatePasswordDto;
@ -51,6 +52,7 @@ public class UserController {
* @param csrfToken csrf令牌 * @param csrfToken csrf令牌
* @return 包含csrf令牌和登录用户的认证主体信息 * @return 包含csrf令牌和登录用户的认证主体信息
*/ */
@OperationLogAnnotation(operationType = "登录")
@RequestMapping(value = "me", method = {RequestMethod.POST, RequestMethod.GET}) @RequestMapping(value = "me", method = {RequestMethod.POST, RequestMethod.GET})
public Map<String, Object> me(@AuthenticationPrincipal Object principal, CsrfToken csrfToken) { public Map<String, Object> me(@AuthenticationPrincipal Object principal, CsrfToken csrfToken) {
Map<String, Object> map = new LinkedHashMap<>(); Map<String, Object> map = new LinkedHashMap<>();
@ -59,6 +61,10 @@ public class UserController {
return map; return map;
} }
/**
* 切换角色
*/
@OperationLogAnnotation(content = "切换角色", operationType = "更新")
@PatchMapping("/switch/{roleId}") @PatchMapping("/switch/{roleId}")
public UserDetailsImpl switchRole(@PathVariable Long roleId) { public UserDetailsImpl switchRole(@PathVariable Long roleId) {
return service.switchRole(roleId); return service.switchRole(roleId);
@ -90,6 +96,7 @@ public class UserController {
* @param dto 表单数据 * @param dto 表单数据
* @return 更新后的用户信息 * @return 更新后的用户信息
*/ */
@OperationLogAnnotation(content = "信息", operationType = "更新")
@PatchMapping("self") @PatchMapping("self")
public Boolean selfUpdate( public Boolean selfUpdate(
@AuthenticationPrincipal UserDetailsImpl userDetails, @AuthenticationPrincipal UserDetailsImpl userDetails,
@ -104,6 +111,8 @@ public class UserController {
* @param userDetails 操作用户 * @param userDetails 操作用户
* @param dto 表单数据 * @param dto 表单数据
*/ */
@OperationLogAnnotation(content = "'密码'", operationType = "更新")
@PatchMapping("self/update-password") @PatchMapping("self/update-password")
public Boolean selfUpdatePassword( public Boolean selfUpdatePassword(
@AuthenticationPrincipal UserDetailsImpl userDetails, @AuthenticationPrincipal UserDetailsImpl userDetails,
@ -138,6 +147,8 @@ public class UserController {
* @param dto 表单数据 * @param dto 表单数据
* @return 新建的用户信息 * @return 新建的用户信息
*/ */
@OperationLogAnnotation(operationType = "新建")
@PostMapping @PostMapping
@PreAuthorize("hasAuthority('system:user:create')") @PreAuthorize("hasAuthority('system:user:create')")
public Boolean create(@RequestBody UserCreateDto dto) { public Boolean create(@RequestBody UserCreateDto dto) {
@ -151,6 +162,7 @@ public class UserController {
* @param id ID * @param id ID
* @return 更新后的用户 * @return 更新后的用户
*/ */
@OperationLogAnnotation(operationType = "更新")
@PatchMapping("{id}") @PatchMapping("{id}")
@PreAuthorize("hasAuthority('system:user:update')") @PreAuthorize("hasAuthority('system:user:update')")
public Boolean update(@RequestBody UserUpdateDto dto, @PathVariable("id") Long id) { public Boolean update(@RequestBody UserUpdateDto dto, @PathVariable("id") Long id) {
@ -163,6 +175,7 @@ public class UserController {
* @param id ID * @param id ID
* @param password 新密码 * @param password 新密码
*/ */
@OperationLogAnnotation(content = "'密码'", operationType = "更新")
@PatchMapping("{id}/update-password") @PatchMapping("{id}/update-password")
@PreAuthorize("hasAuthority('system:user:update')") @PreAuthorize("hasAuthority('system:user:update')")
public Boolean updatePassword(@PathVariable("id") Long id, @RequestParam String password) { public Boolean updatePassword(@PathVariable("id") Long id, @RequestParam String password) {
@ -183,6 +196,7 @@ public class UserController {
/** /**
* 删除用户 hasAuthority('SYSTEM:USER:DELETE') * 删除用户 hasAuthority('SYSTEM:USER:DELETE')
* */ * */
@OperationLogAnnotation(operationType = "删除")
@DeleteMapping("{id}") @DeleteMapping("{id}")
@PreAuthorize("hasAuthority('system:user:delete')") @PreAuthorize("hasAuthority('system:user:delete')")
public Boolean delete(@PathVariable("id") Long id) { public Boolean delete(@PathVariable("id") Long id) {

View File

@ -6,6 +6,9 @@ import lombok.NoArgsConstructor;
import jakarta.validation.constraints.*; import jakarta.validation.constraints.*;
import java.util.List;
import java.util.Set;
/** /**
* 用户新建Dto * 用户新建Dto
* *
@ -53,9 +56,8 @@ public class UserCreateDto {
public Long deptId; public Long deptId;
/** /**
* 用户身份集合 * 用户当前身份
*/ */
@NotEmpty(message = "角色不能为空")
public Long roleId; public Long roleId;
/** /**
* 昵称 * 昵称
@ -79,4 +81,9 @@ public class UserCreateDto {
*/ */
public Integer code; public Integer code;
/**
* 用户角色id集合
*/
@NotEmpty(message = "角色集合不能为空")
public List<Long> roleIds;
} }

View File

@ -12,6 +12,9 @@ import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern; import jakarta.validation.constraints.Pattern;
import java.util.List;
import java.util.Set;
/** /**
* 用户更新Dto * 用户更新Dto
* *
@ -59,11 +62,13 @@ public class UserUpdateDto {
public String address; public String address;
/** /**
* 用户身份集合 * 用户身份
*/ */
@NotEmpty(message = "角色不能为空")
public Long roleId; public Long roleId;
public String remark; public String remark;
@NotEmpty(message = "角色集合不能为空")
public List<Long> roleIds;
} }

View File

@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.*; import lombok.*;
import java.util.List;
import java.util.Set; import java.util.Set;
/** /**
@ -77,6 +78,12 @@ public class User extends BaseEntity {
*/ */
@TableField(exist = false) @TableField(exist = false)
public Role role; public Role role;
/**
* 拥有的所有角色
*/
@TableField(exist = false)
public List<Role> roles;
/** /**
* 头像 * 头像
*/ */

View File

@ -16,5 +16,8 @@ public interface UserRepository extends BaseMapper<User> {
User selectByUsername(String username); User selectByUsername(String username);
@Select("select sys_user.name from sys_user where sys_user.id=#{id}") @Select("select sys_user.name from sys_user where sys_user.id=#{id}")
String selectNameById(Long id); static String selectNameById(Long id) {
return null;
}
} }

View File

@ -2,9 +2,15 @@ package com.zsc.edu.gateway.modules.system.repo;
import com.zsc.edu.gateway.modules.system.entity.UserRole; import com.zsc.edu.gateway.modules.system.entity.UserRole;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/** /**
* @author zhuang * @author zhuang
*/ */
public interface UserRolesRepository extends BaseMapper<UserRole> { public interface UserRolesRepository extends BaseMapper<UserRole> {
@Select("select role_id from sys_users_roles where user_id = #{userId}")
List<Long> selectByUserId(@Param("userId") Long userId);
} }

View File

@ -5,19 +5,14 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zsc.edu.gateway.exception.ConstraintException; import com.zsc.edu.gateway.exception.ConstraintException;
import com.zsc.edu.gateway.framework.security.SecurityUtil; import com.zsc.edu.gateway.framework.security.SecurityUtil;
import com.zsc.edu.gateway.framework.security.UserDetailsImpl; import com.zsc.edu.gateway.framework.security.UserDetailsImpl;
import com.zsc.edu.gateway.modules.operationLog.entity.OperationLogAnnotation;
import com.zsc.edu.gateway.modules.system.dto.UserCreateDto; import com.zsc.edu.gateway.modules.system.dto.UserCreateDto;
import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdateDto; import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdateDto;
import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdatePasswordDto; import com.zsc.edu.gateway.modules.system.dto.UserSelfUpdatePasswordDto;
import com.zsc.edu.gateway.modules.system.dto.UserUpdateDto; import com.zsc.edu.gateway.modules.system.dto.UserUpdateDto;
import com.zsc.edu.gateway.modules.system.entity.Menu; import com.zsc.edu.gateway.modules.system.entity.*;
import com.zsc.edu.gateway.modules.system.entity.Role;
import com.zsc.edu.gateway.modules.system.entity.RoleMenu;
import com.zsc.edu.gateway.modules.system.entity.User;
import com.zsc.edu.gateway.modules.system.mapper.UserMapper; import com.zsc.edu.gateway.modules.system.mapper.UserMapper;
import com.zsc.edu.gateway.modules.system.repo.MenuRepository; import com.zsc.edu.gateway.modules.system.repo.*;
import com.zsc.edu.gateway.modules.system.repo.RoleMenuRepository;
import com.zsc.edu.gateway.modules.system.repo.RoleRepository;
import com.zsc.edu.gateway.modules.system.repo.UserRepository;
import com.zsc.edu.gateway.modules.system.service.MenuService; import com.zsc.edu.gateway.modules.system.service.MenuService;
import com.zsc.edu.gateway.modules.system.service.UserService; import com.zsc.edu.gateway.modules.system.service.UserService;
import com.zsc.edu.gateway.modules.system.utils.sendMail; import com.zsc.edu.gateway.modules.system.utils.sendMail;
@ -53,14 +48,22 @@ public class UserServiceImpl extends ServiceImpl<UserRepository, User> implement
private final RoleMenuRepository RoleMenuRepository; private final RoleMenuRepository RoleMenuRepository;
private final RoleMenuRepository roleMenuRepository; private final RoleMenuRepository roleMenuRepository;
private final MenuRepository menuRepository; private final MenuRepository menuRepository;
private final UserRolesRepository userRolesRepository;
@Override @Override
public Boolean create(UserCreateDto dto) { public Boolean create(UserCreateDto dto) {
User user = new User(); User user = new User();
dto.setRoleId(dto.getRoleIds().get(0));
userMapper.convert(dto, user); userMapper.convert(dto, user);
return save(user); boolean saveSuccess = save(user);
if (!saveSuccess) {
return false;
}
if (dto.getRoleIds() != null && !dto.getRoleIds().isEmpty()) {
addUserRole(dto.getRoleIds(), user.getId());
}
return true;
} }
@Override @Override
public Boolean update(UserUpdateDto dto, Long id) { public Boolean update(UserUpdateDto dto, Long id) {
User user = getById(id); User user = getById(id);
@ -72,6 +75,11 @@ public class UserServiceImpl extends ServiceImpl<UserRepository, User> implement
if (user.getEmail().equals(dto.getEmail()) && existsByEmail) { if (user.getEmail().equals(dto.getEmail()) && existsByEmail) {
throw new ConstraintException("email", dto.email, "邮箱地址已存在"); throw new ConstraintException("email", dto.email, "邮箱地址已存在");
} }
if (dto.getRoleIds() != null && !dto.getRoleIds().isEmpty()) {
userRolesRepository.delete(new LambdaQueryWrapper<UserRole>().eq(UserRole::getUserId, id));
dto.setRoleId(dto.getRoleIds().get(0));
addUserRole(dto.getRoleIds(), user.getId());
}
BeanUtils.copyProperties(dto, user); BeanUtils.copyProperties(dto, user);
return updateById(user); return updateById(user);
} }
@ -149,4 +157,16 @@ public class UserServiceImpl extends ServiceImpl<UserRepository, User> implement
return userDetails; return userDetails;
} }
public Boolean addUserRole(List<Long> roleIds, Long userId) {
List<UserRole> userRoles = roleIds.stream()
.map(roleId -> {
UserRole userRole = new UserRole();
userRole.setUserId(userId);
userRole.setRoleId(roleId);
return userRole;
})
.collect(Collectors.toList());
userRolesRepository.insert(userRoles);
return true;
}
} }

View File

@ -54,3 +54,6 @@ storage:
attachment: ./storage/attachment attachment: ./storage/attachment
temp: ./storage/temp temp: ./storage/temp
jwt:
secret: your_secret_key_here
expiration: 3600

View File

@ -766,3 +766,16 @@ VALUES (1, 1, 'user1', 'password1', '13800138000', 'user1@example.com', '张三'
'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '备注4', '启用', 4), 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '备注4', '启用', 4),
(5, 5, 'user5', 'password5', '13800138004', 'user5@example.com', '孙七', 'avatar5.jpg', '杭州市', 'admin', (5, 5, 'user5', 'password5', '13800138004', 'user5@example.com', '孙七', 'avatar5.jpg', '杭州市', 'admin',
'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '备注5', '启用', 5); 'admin', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP, '备注5', '启用', 5);
INSERT INTO operation_log (module_type, function_type, content, make_time)
VALUES ('user', 'create', 'Created a new user', '2023-10-01 10:00:00'),
('role', 'update', 'Updated role permissions', '2023-10-01 10:15:00'),
('menu', 'delete', 'Deleted a menu item', '2023-10-01 10:30:00'),
('dept', 'create', 'Created a new department', '2023-10-01 10:45:00'),
('device', 'update', 'Updated device settings', '2023-10-01 11:00:00'),
('product', 'create', 'Added a new product', '2023-10-01 11:15:00'),
('serve', 'delete', 'Removed a service', '2023-10-01 11:30:00'),
('event', 'create', 'Logged an event', '2023-10-01 11:45:00'),
('property', 'update', 'Updated property value', '2023-10-01 12:00:00'),
('notice', 'create', 'Sent a notice', '2023-10-01 12:15:00');

View File

@ -0,0 +1,29 @@
create table operation_log
(
id bigint generated by default as identity
constraint operation_log_pk
primary key,
module_type varchar not null,
function_type varchar not null,
content varchar,
make_time timestamp
);
comment
on column operation_log.id is '主键';
comment
on column operation_log.module_type is '模块类型';
comment
on column operation_log.function_type is '操作类型';
comment
on column operation_log.content is '操作内容';
comment
on column operation_log.make_time is '操作时间';
alter table operation_log
owner to gitea;