diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..1a56d7b --- /dev/null +++ b/compose.yaml @@ -0,0 +1,21 @@ +services: + mongodb: + image: 'mongo:latest' + environment: + - 'MONGO_INITDB_DATABASE=mydatabase' + - 'MONGO_INITDB_ROOT_PASSWORD=secret' + - 'MONGO_INITDB_ROOT_USERNAME=root' + ports: + - '27017' + postgres: + image: 'postgres:latest' + environment: + - 'POSTGRES_DB=mydatabase' + - 'POSTGRES_PASSWORD=secret' + - 'POSTGRES_USER=myuser' + ports: + - '5432' + redis: + image: 'redis:latest' + ports: + - '6379' diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1f1bdf6 --- /dev/null +++ b/pom.xml @@ -0,0 +1,220 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.4.0 + + + com.zsc.edu + dify + 0.0.1-SNAPSHOT + dify-backend + dify-backend + + + + + + + + + + + + + + + 17 + 3.5.9 + 1.6.2 + + + + + + + + + org.springframework.boot + spring-boot-starter-aop + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-freemarker + + + org.springframework.boot + spring-boot-starter-integration + + + org.springframework.boot + spring-boot-starter-mail + + + org.springframework.boot + spring-boot-starter-quartz + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-devtools + runtime + true + + + org.springframework.boot + spring-boot-docker-compose + runtime + true + + + org.postgresql + postgresql + 42.6 + + + com.baomidou + mybatis-plus-spring-boot3-starter + ${mybatis-plus.version} + + + com.baomidou + mybatis-plus-jsqlparser + ${mybatis-plus.version} + + + org.springframework.boot + spring-boot-configuration-processor + true + + + org.projectlombok + lombok + true + + + + org.mapstruct + mapstruct + ${mapstruct.version} + provided + + + org.mapstruct + mapstruct-processor + ${mapstruct.version} + provided + + + + org.apache.commons + commons-lang3 + 3.17.0 + + + + commons-codec + commons-codec + 1.17.1 + + + org.apache.tika + tika-core + 3.0.0 + + + + io.jsonwebtoken + jjwt + 0.9.1 + + + com.alibaba + fastjson + 2.0.21 + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.integration + spring-integration-test + test + + + org.springframework.restdocs + spring-restdocs-mockmvc + test + + + org.springframework.security + spring-security-test + test + + + + + + + org.asciidoctor + asciidoctor-maven-plugin + 2.2.1 + + + generate-docs + prepare-package + + process-asciidoc + + + html + book + + + + + + org.springframework.restdocs + spring-restdocs-asciidoctor + ${spring-restdocs.version} + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + diff --git a/src/main/java/com/zsc/edu/dify/DifyBackendApplication.java b/src/main/java/com/zsc/edu/dify/DifyBackendApplication.java new file mode 100644 index 0000000..112c8f3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/DifyBackendApplication.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.EnableAspectJAutoProxy; + +@SpringBootApplication +@EnableAspectJAutoProxy +public class DifyBackendApplication { + + public static void main(String[] args) { + SpringApplication.run(DifyBackendApplication.class, args); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/FirstTimeInitializer.java b/src/main/java/com/zsc/edu/dify/FirstTimeInitializer.java new file mode 100644 index 0000000..f5352b8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/FirstTimeInitializer.java @@ -0,0 +1,89 @@ +package com.zsc.edu.dify; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.User; +import com.zsc.edu.dify.modules.system.repo.DeptRepository; +import com.zsc.edu.dify.modules.system.service.AuthorityService; +import com.zsc.edu.dify.modules.system.service.DeptService; +import com.zsc.edu.dify.modules.system.service.RoleService; +import com.zsc.edu.dify.modules.system.service.UserService; +import lombok.AllArgsConstructor; +import org.springframework.boot.CommandLineRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +/** + * 系统初始化程序 + * + * @author harry_yao + */ +@AllArgsConstructor +@Component +@Profile("!test") +public class FirstTimeInitializer implements CommandLineRunner { + + private final AuthorityService authorityService; + private final UserService userService; + private final RoleService roleService; + private final DeptService deptService; + private final DeptRepository deptRepo; + private final PasswordEncoder passwordEncoder; + + //TODO 初始化赋权给admin + @Override + public void run(String... args) { +// if (authorityService.count() == 0L) { +// Authority userPerm = new Authority(null, "用户模块", "用户管理", "SYSTEM:USER", true, null); +// Authority rolePerm = new Authority(null, "角色模块", "角色管理", "SYSTEM:ROLE", true, null); +// Authority deptPerm = new Authority(null, "部门模块", "部门管理", "SYSTEM:DEPT", true, null); +// Authority AuthorityPerm = new Authority(null, "权限模块", "权限管理", "SYSTEM:AUTHORITY", true, null); +// authorityService.saveBatch(List.of(userPerm, rolePerm, deptPerm, AuthorityPerm)); +// List authorities = new ArrayList<>(); +// authorities.add(new Authority(userPerm.getId(), "用户管理", "用户列表", "SYSTEM:USER:QUERY", true, null)); +// authorities.add(new Authority(userPerm.getId(), "用户管理", "用户新增", "SYSTEM:USER:CREATE",true, null)); +// authorities.add(new Authority(userPerm.getId(), "用户管理", "用户修改", "SYSTEM:USER:UPDATE",true, null)); +// authorities.add(new Authority(userPerm.getId(), "用户管理", "用户删除", "SYSTEM:USER:DELETE",true, null)); +// authorities.add(new Authority(rolePerm.getId(), "角色管理", "角色列表", "SYSTEM:ROLE:QUERY", true, null)); +// authorities.add(new Authority(rolePerm.getId(), "角色管理", "角色新增", "SYSTEM:ROLE:CREATE",true, null)); +// authorities.add(new Authority(rolePerm.getId(), "角色管理", "角色修改", "SYSTEM:ROLE:UPDATE",true, null)); +// authorities.add(new Authority(rolePerm.getId(), "角色管理", "角色删除", "SYSTEM:ROLE:DELETE",true, null)); +// authorities.add(new Authority(deptPerm.getId(), "部门管理", "部门列表", "SYSTEM:DEPT:QUERY", true, null)); +// authorities.add(new Authority(deptPerm.getId(), "部门管理", "部门新增", "SYSTEM:DEPT:CREATE",true, null)); +// authorities.add(new Authority(deptPerm.getId(), "部门管理", "部门修改", "SYSTEM:DEPT:UPDATE",true, null)); +// authorities.add(new Authority(deptPerm.getId(), "部门管理", "部门删除", "SYSTEM:DEPT:DELETE",true, null)); +// authorities.add(new Authority(AuthorityPerm.getId(), "权限管理", "权限列表", "SYSTEM:AUTHORITY:QUERY", true, null)); +// authorities.add(new Authority(AuthorityPerm.getId(), "权限管理", "权限新增", "SYSTEM:AUTHORITY:CREATE",true, null)); +// authorities.add(new Authority(AuthorityPerm.getId(), "权限管理", "权限修改", "SYSTEM:AUTHORITY:UPDATE",true, null)); +// authorities.add(new Authority(AuthorityPerm.getId(), "权限管理", "权限删除", "SYSTEM:AUTHORITY:DELETE",true, null)); +// authorityService.saveBatch(authorities); +// } + if (roleService.count() == 0L) { + Role admin = new Role(); + admin.setName("管理员"); + admin.setEnabled(true); + roleService.save(admin); + } + if (deptService.count() == 0L) { + Dept dept = new Dept(); + dept.setName("总公司"); + deptService.save(dept); + } + if (userService.count() == 0L) { + Dept dept = deptService.getOne(new QueryWrapper<>()); + Role role = roleService.getOne(new QueryWrapper<>()); + User user = new User(); + user.setUsername("admin"); + user.setPassword(passwordEncoder.encode("admin")); + user.setPhone("15913375741"); + user.setEmail("admin@zsc.edu.cn"); + user.setName("admin"); + user.setRoleId(role.getId()); + user.setDeptId(dept.getId()); + userService.save(user); + } + + } +} diff --git a/src/main/java/com/zsc/edu/dify/common/enums/EnableState.java b/src/main/java/com/zsc/edu/dify/common/enums/EnableState.java new file mode 100644 index 0000000..d174b2f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/common/enums/EnableState.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.common.enums; + +import com.baomidou.mybatisplus.annotation.IEnum; + +public enum EnableState implements IEnum { + ENABLE(Boolean.TRUE), + DISABLE(Boolean.FALSE); + + private boolean value; + + EnableState(Boolean value) { + this.value = value; + } + + @Override + public Boolean getValue() { + return this.value; + } + + public EnableState reverse() { + return this == ENABLE ? DISABLE : ENABLE; + } +} diff --git a/src/main/java/com/zsc/edu/dify/common/enums/IState.java b/src/main/java/com/zsc/edu/dify/common/enums/IState.java new file mode 100644 index 0000000..27a7464 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/common/enums/IState.java @@ -0,0 +1,36 @@ +package com.zsc.edu.dify.common.enums; + + +import com.zsc.edu.dify.exception.StateException; + +import java.util.EnumSet; + +/** + * @author harry_yao + */ +public interface IState> { + + /** + * 用于检查对象当前状态是否等于correctStatus + * + * @param correctState 正确状态 + */ + default void checkStatus(T correctState) { + if (this != correctState) { + throw new StateException(correctState.getClass(), this, correctState); + } + } + + /** + * 用于检查对象当前状态是否在集合correctStates中 + * + * @param correctStates 正确状态集合 + */ + @SuppressWarnings("SuspiciousMethodCalls") + default void checkStatus(EnumSet correctStates) { + if (!correctStates.contains(this)) { + throw new StateException(this.getClass(), this, correctStates); + } + } + +} diff --git a/src/main/java/com/zsc/edu/dify/common/mapstruct/BaseMapper.java b/src/main/java/com/zsc/edu/dify/common/mapstruct/BaseMapper.java new file mode 100644 index 0000000..f2e602a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/common/mapstruct/BaseMapper.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.common.mapstruct; + +import org.mapstruct.MappingTarget; + +import java.util.List; + +public interface BaseMapper { + D toDto(E entity); + + E toEntity(D dto); + + List toDto(List entityList); + + List toEntity(List dtoList); + + /** + * 更新实体类 + * + * @param dto + * @param entity + */ + void convert(D dto, @MappingTarget E entity); +} diff --git a/src/main/java/com/zsc/edu/dify/common/util/TreeUtil.java b/src/main/java/com/zsc/edu/dify/common/util/TreeUtil.java new file mode 100644 index 0000000..138ff42 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/common/util/TreeUtil.java @@ -0,0 +1,133 @@ +package com.zsc.edu.dify.common.util; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collectors; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * @Description: 树操作方法工具类 + * @Copyright: Copyright (c) 赵侠客 + * @Date: 2024-07-22 10:42 + * @Version: 1.0 + */ +public class TreeUtil { + + /** + * 将list合成树 + * + * @param list 需要合成树的List + * @param rootCheck 判断E中为根节点的条件,如:x->x.getPId()==-1L , x->x.getParentId()==null,x->x.getParentMenuId()==0 + * @param parentCheck 判断E中为父节点条件,如:(x,y)->x.getId().equals(y.getPId()) + * @param setSubChildren E中设置下级数据方法,如:Menu::setSubMenus + * @param 泛型实体对象 + * @return 合成好的树 + */ + public static List makeTree(List list, Predicate rootCheck, BiFunction parentCheck, BiConsumer> setSubChildren) { + return list.stream().filter(rootCheck).peek(x -> setSubChildren.accept(x, makeChildren(x, list, parentCheck, setSubChildren))).collect(Collectors.toList()); + } + + /** + * 将树打平成list + * + * @param tree 需要打平的树 + * @param getSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param setSubChildren 将下级数据置空方法,如:x->x.setSubMenus(null) + * @param 泛型实体对象 + * @return 打平后的数据 + */ + public static List flat(List tree, Function> getSubChildren, Consumer setSubChildren) { + List res = new ArrayList<>(); + forPostOrder(tree, item -> { + setSubChildren.accept(item); + res.add(item); + }, getSubChildren); + return res; + } + + /** + * 前序遍历 + * + * @param tree 需要遍历的树 + * @param consumer 遍历后对单个元素的处理方法,如:x-> System.out.println(x)、 System.out::println打印元素 + * @param setSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param 泛型实体对象 + */ + public static void forPreOrder(List tree, Consumer consumer, Function> setSubChildren) { + for (E l : tree) { + consumer.accept(l); + List es = setSubChildren.apply(l); + if (es != null && es.size() > 0) { + forPreOrder(es, consumer, setSubChildren); + } + } + } + + /** + * 层序遍历 + * + * @param tree 需要遍历的树 + * @param consumer 遍历后对单个元素的处理方法,如:x-> System.out.println(x)、 System.out::println打印元素 + * @param setSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param 泛型实体对象 + */ + public static void forLevelOrder(List tree, Consumer consumer, Function> setSubChildren) { + Queue queue = new LinkedList<>(tree); + while (!queue.isEmpty()) { + E item = queue.poll(); + consumer.accept(item); + List childList = setSubChildren.apply(item); + if (childList != null && !childList.isEmpty()) { + queue.addAll(childList); + } + } + } + + /** + * 后序遍历 + * + * @param tree 需要遍历的树 + * @param consumer 遍历后对单个元素的处理方法,如:x-> System.out.println(x)、 System.out::println打印元素 + * @param setSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param 泛型实体对象 + */ + public static void forPostOrder(List tree, Consumer consumer, Function> setSubChildren) { + for (E item : tree) { + List childList = setSubChildren.apply(item); + if (childList != null && !childList.isEmpty()) { + forPostOrder(childList, consumer, setSubChildren); + } + consumer.accept(item); + } + } + + /** + * 对树所有子节点按comparator排序 + * + * @param tree 需要排序的树 + * @param comparator 排序规则Comparator,如:Comparator.comparing(MenuVo::getRank)按Rank正序 ,(x,y)->y.getRank().compareTo(x.getRank()),按Rank倒序 + * @param getChildren 获取下级数据方法,如:MenuVo::getSubMenus + * @param 泛型实体对象 + * @return 排序好的树 + */ + public static List sort(List tree, Comparator comparator, Function> getChildren) { + for (E item : tree) { + List childList = getChildren.apply(item); + if (childList != null && !childList.isEmpty()) { + sort(childList, comparator, getChildren); + } + } + tree.sort(comparator); + return tree; + } + + private static List makeChildren(E parent, List allData, BiFunction parentCheck, BiConsumer> children) { + return allData.stream() + .filter(x -> parentCheck.apply(parent, x)) + .peek(x -> children.accept(x, makeChildren(x, allData, parentCheck, children))) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/ApiException.java b/src/main/java/com/zsc/edu/dify/exception/ApiException.java new file mode 100644 index 0000000..b0cf71d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/ApiException.java @@ -0,0 +1,32 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class ApiException extends RuntimeException { + + public ApiException() { + super("发生服务器内部异常,请联系管理员提交故障"); + } + + public ApiException(String message) { + super(message); + } + + public ApiException(String message, Throwable cause) { + super(message, cause); + } + + public ApiException(Throwable cause) { + super(cause); + } + + public ApiException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/exception/ApiExceptionHandler.java b/src/main/java/com/zsc/edu/dify/exception/ApiExceptionHandler.java new file mode 100644 index 0000000..ef4ace2 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/ApiExceptionHandler.java @@ -0,0 +1,68 @@ +package com.zsc.edu.dify.exception; + +//import com.zsc.study.module.common.domain.ResponseResult; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import java.util.Map; + +/** + * @author harry_yao + */ +@Slf4j +@AllArgsConstructor +@RestControllerAdvice +public class ApiExceptionHandler { + + @Resource + private ObjectMapper objectMapper; + @ExceptionHandler(value = {ConstraintException.class}) + public ResponseEntity handleException(ConstraintException ex) throws JsonProcessingException { + log.error("ConstraintException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.BAD_REQUEST); + } + + @ExceptionHandler(value = {NotExistException.class}) + public ResponseEntity handleException(NotExistException ex) throws JsonProcessingException { + log.error("NotExistException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(value = {StateException.class}) + public ResponseEntity handleException(StateException ex) throws JsonProcessingException { + log.error("StateException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(value = {StorageException.class}) + public ResponseEntity handleException(StorageException ex) throws JsonProcessingException { + log.error("StorageException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.NOT_FOUND); + } + + @ExceptionHandler(value = {UserHasNoIdentityException.class}) + public ResponseEntity handleException(UserHasNoIdentityException ex) throws JsonProcessingException { + log.error("UserHasNoIdentityException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(value = {ValidateException.class}) + public ResponseEntity handleException(ValidateException ex) throws JsonProcessingException { + log.error("ValidateException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.UNAUTHORIZED); + } + + @ExceptionHandler(value = {ApiException.class}) + public ResponseEntity handleException(ApiException ex) throws JsonProcessingException { + log.error("ApiException: {}", objectMapper.writeValueAsString(Map.of("msg", ex.getMessage()))); + return new ResponseEntity<>(objectMapper.writeValueAsString(Map.of("msg", ex.getMessage())), HttpStatus.INTERNAL_SERVER_ERROR); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/exception/ConstraintException.java b/src/main/java/com/zsc/edu/dify/exception/ConstraintException.java new file mode 100644 index 0000000..6c4929f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/ConstraintException.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class ConstraintException extends ApiException { + + public ConstraintException(String fieldName, Object fieldValue) { + super(String.format("字段%s的值:'%s'不符合要求。", fieldName, fieldValue)); + } + + public ConstraintException(String fieldName, Object fieldValue, String message) { + super(String.format("字段%s的值:'%s'不符合要求,%s", fieldName, fieldValue, message)); + } + + public ConstraintException(String message) { + super(message); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/ExceptionResult.java b/src/main/java/com/zsc/edu/dify/exception/ExceptionResult.java new file mode 100644 index 0000000..1df8d1a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/ExceptionResult.java @@ -0,0 +1,17 @@ +package com.zsc.edu.dify.exception; + +import lombok.AllArgsConstructor; + +import java.time.LocalDateTime; + +/** + * @author harry_yao + */ +@AllArgsConstructor +public class ExceptionResult { + + public final String msg; + public final Object code; + public final LocalDateTime timestamp; + +} diff --git a/src/main/java/com/zsc/edu/dify/exception/JwtAuthenticationException.java b/src/main/java/com/zsc/edu/dify/exception/JwtAuthenticationException.java new file mode 100644 index 0000000..544f875 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/JwtAuthenticationException.java @@ -0,0 +1,12 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.security.core.AuthenticationException; + +/** + * @author Yao + */ +public class JwtAuthenticationException extends AuthenticationException { + public JwtAuthenticationException(String msg) { + super(msg); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/NotExistException.java b/src/main/java/com/zsc/edu/dify/exception/NotExistException.java new file mode 100644 index 0000000..12c4127 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/NotExistException.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.NOT_FOUND) +public class NotExistException extends ApiException { + + public NotExistException(Class entity) { + super(String.format("%s对象不存在", entity.getSimpleName())); + } + + public NotExistException() { + super("对象不存在"); + } + + public NotExistException(String message) { + super(message); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/OutlineException.java b/src/main/java/com/zsc/edu/dify/exception/OutlineException.java new file mode 100644 index 0000000..442df45 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/OutlineException.java @@ -0,0 +1,19 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class OutlineException extends ApiException { + + public OutlineException() { + super("设备不在线"); + } + + public OutlineException(String message) { + super(message); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/StateException.java b/src/main/java/com/zsc/edu/dify/exception/StateException.java new file mode 100644 index 0000000..eb494c6 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/StateException.java @@ -0,0 +1,19 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class StateException extends ApiException { + + public StateException(Class statusClass, Object currentStatus, Object correctStatus) { + super(String.format("%s当前的状态值'%s'不符合要求,正确的状态值可以是:%s。", statusClass.getSimpleName(), currentStatus, correctStatus)); + } + + public StateException(String message) { + super(message); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/StorageException.java b/src/main/java/com/zsc/edu/dify/exception/StorageException.java new file mode 100644 index 0000000..e336933 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/StorageException.java @@ -0,0 +1,19 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class StorageException extends ApiException { + + public StorageException() { + super("文件存储失败"); + } + + public StorageException(String message) { + super(message); + } +} diff --git a/src/main/java/com/zsc/edu/dify/exception/UserHasNoIdentityException.java b/src/main/java/com/zsc/edu/dify/exception/UserHasNoIdentityException.java new file mode 100644 index 0000000..053513a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/UserHasNoIdentityException.java @@ -0,0 +1,21 @@ +package com.zsc.edu.dify.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.security.authentication.DisabledException; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public class UserHasNoIdentityException extends DisabledException { + + public UserHasNoIdentityException() { + super("没有给用户分配身份"); + } + + public UserHasNoIdentityException(String message) { + super(message); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/exception/ValidateException.java b/src/main/java/com/zsc/edu/dify/exception/ValidateException.java new file mode 100644 index 0000000..e0c4db6 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/exception/ValidateException.java @@ -0,0 +1,21 @@ +package com.zsc.edu.dify.exception; + + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +@ResponseStatus(HttpStatus.UNAUTHORIZED) +public class ValidateException extends ApiException { + + public ValidateException() { + super("验证码失效或错误!"); + } + + public ValidateException(String message) { + super(message); + } + + public ValidateException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/AppConfig.java b/src/main/java/com/zsc/edu/dify/framework/AppConfig.java new file mode 100644 index 0000000..a6625fd --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/AppConfig.java @@ -0,0 +1,17 @@ +package com.zsc.edu.dify.framework; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +/** + * @author zhuang + */ +@Configuration +public class AppConfig { + + @Bean + public RestClient restClient() { + return RestClient.builder().build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/JsonExceptionUtil.java b/src/main/java/com/zsc/edu/dify/framework/JsonExceptionUtil.java new file mode 100644 index 0000000..fd53e3b --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/JsonExceptionUtil.java @@ -0,0 +1,21 @@ +package com.zsc.edu.dify.framework; + +import org.springframework.http.HttpStatus; + +import java.util.Calendar; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * @author harry yao + */ +public class JsonExceptionUtil { + public static Map jsonExceptionResult(HttpStatus code, String message, String path) { + Map exceptionMap = new LinkedHashMap<>(); + exceptionMap.put("timestamp", Calendar.getInstance().getTime()); + exceptionMap.put("message", message); + exceptionMap.put("path", path); + exceptionMap.put("code", code.value()); + return exceptionMap; + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/SpringBeanUtil.java b/src/main/java/com/zsc/edu/dify/framework/SpringBeanUtil.java new file mode 100644 index 0000000..209d54c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/SpringBeanUtil.java @@ -0,0 +1,29 @@ +package com.zsc.edu.dify.framework; + +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.lang.NonNull; +import org.springframework.stereotype.Component; + +/** + * @author harry yao + */ +@Component +public class SpringBeanUtil implements ApplicationContextAware { + + private static ApplicationContext applicationContext; + + public static T getBean(Class beanClass) { + return applicationContext.getBean(beanClass); + } + + public static Object getBean(String beanName) { + return applicationContext.getBean(beanName); + } + + @Override + public void setApplicationContext(@NonNull ApplicationContext applicationContext) throws BeansException { + SpringBeanUtil.applicationContext = applicationContext; + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/WebMvcConfiguration.java b/src/main/java/com/zsc/edu/dify/framework/WebMvcConfiguration.java new file mode 100644 index 0000000..a2608a8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/WebMvcConfiguration.java @@ -0,0 +1,47 @@ +package com.zsc.edu.dify.framework; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.AllArgsConstructor; +import org.springframework.boot.autoconfigure.web.WebProperties; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.lang.NonNull; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import org.springframework.web.servlet.resource.PathResourceResolver; +import org.springframework.web.servlet.resource.ResourceResolverChain; + +import java.util.List; + +/** + * @author harry_yao + */ +@Configuration +@AllArgsConstructor +public class WebMvcConfiguration implements WebMvcConfigurer { + + private final WebProperties webProperties; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/**") + .addResourceLocations(webProperties.getResources().getStaticLocations()) + .resourceChain(webProperties.getResources().getChain().isCache()) + .addResolver(new FallbackPathResourceResolver()); + } + + private static class FallbackPathResourceResolver extends PathResourceResolver { + @Override + public Resource resolveResource( + HttpServletRequest request, + @NonNull String requestPath, + @NonNull List locations, + @NonNull ResourceResolverChain chain) { + Resource resource = super.resolveResource(request, requestPath, locations, chain); + if (resource == null) { + resource = super.resolveResource(request, "/index.html", locations, chain); + } + return resource; + } + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/async/AsyncConfiguration.java b/src/main/java/com/zsc/edu/dify/framework/async/AsyncConfiguration.java new file mode 100644 index 0000000..f7bde0c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/async/AsyncConfiguration.java @@ -0,0 +1,52 @@ +package com.zsc.edu.dify.framework.async; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.AsyncConfigurer; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.lang.reflect.Method; +import java.util.concurrent.Executor; + +/** + * @author harry_yao + */ + +@Slf4j +@EnableAsync +@Configuration +//@Profile("!test") // test 环境下不启动异步 +public class AsyncConfiguration implements AsyncConfigurer { + +// @Bean +// public AsyncTaskExecutor taskExecutor() { +// ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); +// executor.setCorePoolSize(20); +// executor.setMaxPoolSize(20); +// executor.setQueueCapacity(500); +// executor.setThreadNamePrefix("Executor"); +// executor.initialize(); +// return executor; +// } + + @Override + public Executor getAsyncExecutor() { + ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); + executor.setCorePoolSize(20); + executor.setMaxPoolSize(20); + executor.setQueueCapacity(500); + executor.setThreadNamePrefix("Executor-"); + executor.initialize(); + return executor; + } + + @Override + public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() { +// return AsyncConfigurer.super.getAsyncUncaughtExceptionHandler(); + return (Throwable throwable, Method method, Object... obj)->{ + log.info("Method name -{}, Exception message -{}", method.getName(),throwable); + }; + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/json/JsonConfig.java b/src/main/java/com/zsc/edu/dify/framework/json/JsonConfig.java new file mode 100644 index 0000000..f1bf319 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/json/JsonConfig.java @@ -0,0 +1,19 @@ +package com.zsc.edu.dify.framework.json; + +import com.fasterxml.jackson.annotation.JsonInclude; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author veryao + */ +@Configuration +public class JsonConfig { + + @Bean + public Jackson2ObjectMapperBuilderCustomizer customizer() { + return builder -> builder.serializationInclusion(JsonInclude.Include.NON_NULL) + .serializationInclusion(JsonInclude.Include.NON_EMPTY); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/json/JsonbTypeHandler.java b/src/main/java/com/zsc/edu/dify/framework/json/JsonbTypeHandler.java new file mode 100644 index 0000000..fdff3dd --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/json/JsonbTypeHandler.java @@ -0,0 +1,40 @@ +package com.zsc.edu.dify.framework.json; + +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.postgresql.util.PGobject; + +import java.lang.reflect.Field; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +@MappedTypes({Object.class}) +@MappedJdbcTypes(JdbcType.VARCHAR) +public class JsonbTypeHandler extends JacksonTypeHandler { + + private final Class clazz; + + public JsonbTypeHandler(Class clazz) { + super(clazz); + if (clazz == null) { + throw new IllegalArgumentException("Type argument cannot be null"); + } + this.clazz = clazz; + } + + // 自3.5.6版本开始支持泛型,需要加上此构造. + public JsonbTypeHandler(Class type, Field field, Class clazz) { + super(type, field); + this.clazz = clazz; + } + + @Override + public void setNonNullParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { + PGobject jsonbObject = new PGobject(); + jsonbObject.setType("jsonb"); + jsonbObject.setValue(toJson(parameter)); + ps.setObject(i, jsonbObject); + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/message/email/EmailProperties.java b/src/main/java/com/zsc/edu/dify/framework/message/email/EmailProperties.java new file mode 100644 index 0000000..d09b76c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/message/email/EmailProperties.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.framework.message.email; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author harry_yao + */ +@Data +@ConfigurationProperties("spring.mail") +@Configuration +public class EmailProperties { + public String username; +} diff --git a/src/main/java/com/zsc/edu/dify/framework/message/email/EmailSender.java b/src/main/java/com/zsc/edu/dify/framework/message/email/EmailSender.java new file mode 100644 index 0000000..560e341 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/message/email/EmailSender.java @@ -0,0 +1,106 @@ +package com.zsc.edu.dify.framework.message.email; + + +import com.zsc.edu.dify.modules.attachment.service.AttachmentService; +import com.zsc.edu.dify.modules.message.entity.Notice; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateException; +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.AddressException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; + +import java.io.IOException; +import java.io.StringWriter; +import java.util.Objects; +import java.util.Set; + + +/** + * @author pegnzheng + */ +@AllArgsConstructor +@Component +public class EmailSender { + + private final static String TEMPLATE = "message.ftl"; + + private final EmailProperties config; + private final Configuration freemarkerConfig; + @Resource + private final JavaMailSender sender; + private final AttachmentService attachmentService; + + @Async + public void send(String email, Notice notice) { + if (StringUtils.hasText(email)) { + return; + } + InternetAddress to; + try { + to = new InternetAddress(email); + to.validate(); + } catch (AddressException e) { + return; + } + send(new InternetAddress[]{to}, notice); + } + + @Async + public void send(Set emails, Notice notice) { + InternetAddress[] to = emails.stream().filter(Objects::nonNull).map(email -> + { + try { + return new InternetAddress(email); + } catch (AddressException e) { + return null; + } + }).filter(Objects::nonNull).filter(internetAddress -> { + try { + internetAddress.validate(); + } catch (AddressException e) { + return false; + } + return true; + }).toArray(InternetAddress[]::new); + if (to.length == 0) { + return; + } + send(to, notice); + } + + private void send(InternetAddress[] to, Notice notice) { + try { + MimeMessage mimeMessage = sender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true); + helper.setTo(to); + helper.setFrom(config.username); + helper.setSubject(notice.getTitle()); + if (notice.html) { + StringWriter sw = new StringWriter(); + Template tp = freemarkerConfig.getTemplate(TEMPLATE, "UTF-8"); + tp.process(notice, sw); + helper.setText(sw.toString(), true); + } else { + helper.setText(notice.content); + } +// if (Objects.nonNull(message.attachments)) { +// for (Attachment attachment : message.attachments) { +// helper.addAttachment(attachment.fileName, attachmentService.loadAsResource(attachment.id), attachment.mimeType); +// } +// } + sender.send(mimeMessage); + } catch (MessagingException | IOException | TemplateException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/message/sms/SmsProperties.java b/src/main/java/com/zsc/edu/dify/framework/message/sms/SmsProperties.java new file mode 100644 index 0000000..2f3310a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/message/sms/SmsProperties.java @@ -0,0 +1,17 @@ +package com.zsc.edu.dify.framework.message.sms; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +/** + * @author harry_yao + */ +@Data +@ConfigurationProperties("sms") +@Configuration +public class SmsProperties { + public String apiKey = "4d8de516324886549d0ba3ddc4bf6f47"; + public String singleSendUrl = "https://sms.yunpian.com/v2/sms/single_send.json"; + public String batchSendUrl = "https://sms.yunpian.com/v2/sms/batch_send.json"; +} diff --git a/src/main/java/com/zsc/edu/dify/framework/message/sms/SmsSender.java b/src/main/java/com/zsc/edu/dify/framework/message/sms/SmsSender.java new file mode 100644 index 0000000..74f34e7 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/message/sms/SmsSender.java @@ -0,0 +1,45 @@ +package com.zsc.edu.dify.framework.message.sms; + + +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.util.StringUtils; +import org.springframework.web.client.RestClient; + +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author pegnzheng + */ +@AllArgsConstructor +@Component +public class SmsSender { + + private final SmsProperties properties; + @Resource + private final RestClient restClient; + + @Async + public void send(String mobile, String text) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("apikey", properties.apiKey); + params.add("mobile", mobile); + params.add("text", text); + restClient.post().uri(properties.singleSendUrl).body(params); + } + + @Async + public void send(Set mobiles, String text) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.add("apikey", properties.apiKey); + params.add("mobile", mobiles.stream().filter(StringUtils::hasText).collect(Collectors.joining(","))); + params.add("text", text); + restClient.post().uri(properties.batchSendUrl).body(params); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataPermission.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataPermission.java new file mode 100644 index 0000000..e66c0d0 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataPermission.java @@ -0,0 +1,25 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +import java.lang.annotation.*; + +/** + * @author vertoryao + */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface DataPermission { + /** + * 目标表的别名 + */ + String tableAlias() default ""; + /** + * 部门表的别名 + */ + String deptAlias() default "dept_id"; + + /** + * 用户表的别名 + */ + String userAlias() default "create_id"; +} diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataPermissionContext.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataPermissionContext.java new file mode 100644 index 0000000..ee657c9 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataPermissionContext.java @@ -0,0 +1,18 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +public class DataPermissionContext { + + private static final ThreadLocal contextHolder = new ThreadLocal<>(); + + public static void set(DataPermission dataPermission) { + contextHolder.set(dataPermission); + } + + public static DataPermission get() { + return contextHolder.get(); + } + + public static void clear() { + contextHolder.remove(); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeAspect.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeAspect.java new file mode 100644 index 0000000..b6d0737 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeAspect.java @@ -0,0 +1,44 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.stereotype.Component; + +import java.lang.reflect.Method; + +/** + * 数据权限注解切面 + */ +@Aspect +@Component +public class DataScopeAspect { + + /** + * 环绕通知,拦截带有 @DataPermission 注解的方法 + * @param joinPoint + * @throws Throwable + */ + @Around("@annotation(DataPermission)()") + public Object around(ProceedingJoinPoint joinPoint) throws Throwable { + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + + // 获取方法上的 @DataPermission 注解 + DataPermission dataPermission = method.getAnnotation(DataPermission.class); + + if (dataPermission != null) { + // 将注解信息存储在上下文中,供 MyBatis 拦截器使用 + DataPermissionContext.set(dataPermission); + } + + try { + // 执行目标方法 + return joinPoint.proceed(); + } finally { + // 方法执行完毕,清除数据权限上下文,避免内存泄露 + DataPermissionContext.clear(); + } + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeHandler.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeHandler.java new file mode 100644 index 0000000..0b64ab2 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeHandler.java @@ -0,0 +1,127 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.baomidou.mybatisplus.extension.plugins.handler.MultiDataPermissionHandler; +import com.zsc.edu.dify.framework.security.SecurityUtil; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import lombok.extern.slf4j.Slf4j; +import net.sf.jsqlparser.expression.Expression; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.expression.operators.relational.EqualsTo; +import net.sf.jsqlparser.expression.operators.relational.InExpression; +import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList; +import net.sf.jsqlparser.schema.Column; +import net.sf.jsqlparser.schema.Table; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * 数据权限拼装逻辑处理 + * + */ +@Slf4j +public class DataScopeHandler implements MultiDataPermissionHandler { + + /** + * 获取数据权限 SQL 片段。 + *

{@link MultiDataPermissionHandler#getSqlSegment(Table, Expression, String)} 方法不能覆盖原有的 where 数据,如果 return 了 null 则表示不追加任何 where 条件

+ * + * @param table 所执行的数据库表信息,可以通过此参数获取表名和表别名 + * @param where 原有的 where 条件信息 + * @param mappedStatementId Mybatis MappedStatementId 根据该参数可以判断具体执行方法 + * @return JSqlParser 条件表达式,返回的条件表达式会拼接在原有的表达式后面(不会覆盖原有的表达式) + */ + @Override + public Expression getSqlSegment(Table table, Expression where, String mappedStatementId) { +// try { + // 获取当前线程中的数据权限信息 + DataPermission dataPermission = DataPermissionContext.get(); + if (Objects.isNull(dataPermission)) { + return null; + } + return buildDataScopeByAnnotation(dataPermission); + + } + /** + * DataScope注解方式,拼装数据权限 + * @param dataScope + * @return + */ + private Expression buildDataScopeByAnnotation(DataPermission dataScope) { + UserDetailsImpl userInfo = SecurityUtil.getUserInfo(); + if (userInfo == null) { + return null; + } + Set dataScopeDeptIds = userInfo.getDataScopeDeptIds(); + DataScopeType dataScopeType = userInfo.getRole().getDataScope(); + // 本人ID + Long dataScopeCreateId = userInfo.getId(); + // 本人部门ID + Long deptId = userInfo.getDept().getId(); + // 表别名 + String tableAlias = dataScope.tableAlias(); + // 部门限制范围的字段名称 + String deptAlias = dataScope.deptAlias(); + // 本人限制范围的字段名称 + String userAlias = dataScope.userAlias(); + + // 拼装数据权限 + return switch (dataScopeType) { + // 全部数据权限 + case DATA_SCOPE_ALL -> null; + // 本部门数据权限, 构建部门eq表达式 + case DATA_SCOPE_DEPT -> equalsTo(tableAlias, deptAlias, deptId); + // 本部门及以下数据权限,构建部门in表达式 + case DATA_SCOPE_DEPT_AND_CHILD -> inExpression(tableAlias, deptAlias, dataScopeDeptIds); + // 本人数据权限,构建本人eq表达式 + case DATA_SCOPE_SELF -> equalsTo(tableAlias, userAlias, dataScopeCreateId); + }; + } + + /** + * 构建eq表达式 + * @param tableAlias 表别名 + * @param itemAlias 字段别名 + * @param itemId id + * @return + */ + private EqualsTo equalsTo(String tableAlias, String itemAlias, Long itemId) { + if (Objects.nonNull(itemId)) { + EqualsTo equalsTo = new EqualsTo(); + equalsTo.withLeftExpression(buildColumn(tableAlias, itemAlias)); + equalsTo.setRightExpression(new LongValue(itemId)); + return equalsTo; + } else { + return null; + } + } + + private InExpression inExpression(String tableAlias, String itemAlias, Set itemIds) { + if (!itemIds.isEmpty()) { + InExpression deptIdInExpression = new InExpression(); + ParenthesedExpressionList deptIds = new ParenthesedExpressionList<>(itemIds.stream().map(LongValue::new).collect(Collectors.toList())); + deptIdInExpression.withLeftExpression(buildColumn(tableAlias, itemAlias)); + deptIdInExpression.setRightExpression(deptIds); + return deptIdInExpression; + } else { + return null; + } + } + + + /** + * 构建Column + * + * @param tableAlias 表别名 + * @param columnName 字段名称 + * @return 带表别名字段 + */ + public static Column buildColumn(String tableAlias, String columnName) { + if (StringUtils.isNotEmpty(tableAlias)) { + columnName = tableAlias + "." + columnName; + } + return new Column(columnName); + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeType.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeType.java new file mode 100644 index 0000000..3da84e5 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/DataScopeType.java @@ -0,0 +1,35 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +import com.baomidou.mybatisplus.annotation.IEnum; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public enum DataScopeType implements IEnum { + /** + * 全部数据权限 + */ + DATA_SCOPE_ALL(1), + /** + * 部门数据权限 + */ + DATA_SCOPE_DEPT(2), + /** + * 部门及以下数据权限 + */ + DATA_SCOPE_DEPT_AND_CHILD(3), + /** + * 仅个人数据权限 + */ + DATA_SCOPE_SELF(4); + /** + * 自定义数据权限 + */ +// DATA_SCOPE_CUSTOM(5); + + private final int value; + + @Override + public Integer getValue() { + return this.value; + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/ListTypeHandler.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/ListTypeHandler.java new file mode 100644 index 0000000..13d2325 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/ListTypeHandler.java @@ -0,0 +1,50 @@ +package com.zsc.edu.dify.framework.mybatisplus; + + +import org.apache.ibatis.type.BaseTypeHandler; +import org.apache.ibatis.type.JdbcType; +import org.apache.ibatis.type.MappedJdbcTypes; +import org.apache.ibatis.type.MappedTypes; +import org.springframework.util.StringUtils; + +import java.sql.CallableStatement; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +/** + * @author : wshuo + * @date : 2023/1/11 15:59 + */ +@MappedJdbcTypes(JdbcType.VARCHAR) +@MappedTypes({ArrayList.class}) +public class ListTypeHandler extends BaseTypeHandler> { + + private static final String DELIM = ","; + + @Override + public void setNonNullParameter(PreparedStatement preparedStatement, int i, ArrayList strings, JdbcType jdbcType) throws SQLException { + String value = StringUtils.collectionToDelimitedString(strings, DELIM); + preparedStatement.setString(i, value); + } + + @Override + public ArrayList getNullableResult(ResultSet resultSet, String s) throws SQLException { + String value = resultSet.getString(s); + return new ArrayList(List.of(StringUtils.tokenizeToStringArray(value, DELIM))); + } + + @Override + public ArrayList getNullableResult(ResultSet resultSet, int i) throws SQLException { + String value = resultSet.getString(i); + return new ArrayList(List.of(StringUtils.tokenizeToStringArray(value, DELIM))); + } + + @Override + public ArrayList getNullableResult(CallableStatement callableStatement, int i) throws SQLException { + String value = callableStatement.getString(i); + return new ArrayList(List.of(StringUtils.tokenizeToStringArray(value, DELIM))); + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/MyMetaObjectHandler.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/MyMetaObjectHandler.java new file mode 100644 index 0000000..adc0706 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/MyMetaObjectHandler.java @@ -0,0 +1,50 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; +import com.zsc.edu.dify.framework.security.SecurityUtil; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import lombok.extern.slf4j.Slf4j; +import org.apache.ibatis.reflection.MetaObject; +import org.springframework.stereotype.Component; + +import java.time.LocalDateTime; + +/** + * @author Yao + */ +@Slf4j +@Component +public class MyMetaObjectHandler implements MetaObjectHandler { + + @Override + public void insertFill(MetaObject metaObject) { + + UserDetailsImpl userInfo = SecurityUtil.getUserInfo(); + if (userInfo.getUsername() == null) { + userInfo.setUsername("system"); + } + if (userInfo.getDeptId() == null) { + userInfo.setDeptId(2L); + } + if (userInfo.getCreateId() == null) { + userInfo.setCreateId(1L); + } + this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now()); + this.strictInsertFill(metaObject, "createBy", String.class, userInfo.getUsername()); + this.strictInsertFill(metaObject, "deptId", Long.class, userInfo.getDeptId()); + this.strictInsertFill(metaObject, "createId", Long.class, userInfo.getCreateId()); + + } + + @Override + public void updateFill(MetaObject metaObject) { + UserDetailsImpl userInfo = SecurityUtil.getUserInfo(); + if (userInfo.getUsername() == null) { + userInfo.setUsername("system"); + } + this.strictUpdateFill(metaObject, "updateTime", LocalDateTime::now, LocalDateTime.class); + this.strictUpdateFill(metaObject, "updateBy", userInfo::getUsername, String.class); + + } + +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/mybatisplus/MybatisPlusConfig.java b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/MybatisPlusConfig.java new file mode 100644 index 0000000..0ece86b --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/mybatisplus/MybatisPlusConfig.java @@ -0,0 +1,28 @@ +package com.zsc.edu.dify.framework.mybatisplus; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * @author Yao + */ +@MapperScan(basePackages = "com.zsc.edu.dify.modules.**.repo") +@Configuration +public class MybatisPlusConfig { + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + // 添加数据权限插件 + DataPermissionInterceptor dataPermissionInterceptor = new DataPermissionInterceptor(new DataScopeHandler()); + // 添加自定义的数据权限处理器 + interceptor.addInnerInterceptor(dataPermissionInterceptor); + // 添加分页插件 + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.POSTGRE_SQL)); + return interceptor; + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/redis/RedisUtils.java b/src/main/java/com/zsc/edu/dify/framework/redis/RedisUtils.java new file mode 100644 index 0000000..604edf5 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/redis/RedisUtils.java @@ -0,0 +1,72 @@ +package com.zsc.edu.dify.framework.redis; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.ValueOperations; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * @author zhuang + */ +@Component +public class RedisUtils { + + @Autowired + private StringRedisTemplate stringRedisTemplate; + + /** + * 设置键值对 + * + * @param key 键 + * @param value 值 + */ + public void set(String key, String value) { + ValueOperations ops = stringRedisTemplate.opsForValue(); + ops.set(key, value); + } + + /** + * 设置键值对并设置过期时间 + * + * @param key 键 + * @param value 值 + * @param timeout 过期时间 + * @param unit 时间单位 + */ + public void set(String key, String value, long timeout, TimeUnit unit) { + ValueOperations ops = stringRedisTemplate.opsForValue(); + ops.set(key, value, timeout, unit); + } + + /** + * 获取键值对 + * + * @param key 键 + * @return 值 + */ + public String get(String key) { + ValueOperations ops = stringRedisTemplate.opsForValue(); + return ops.get(key); + } + + /** + * 检查键是否存在 + * + * @param key 键 + * @return 是否存在 + */ + public boolean hasKey(String key) { + return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key)); + } + + /** + * 删除键 + * + * @param key 键 + */ + public void delete(String key) { + stringRedisTemplate.delete(key); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/response/GlobalResponseHandler.java b/src/main/java/com/zsc/edu/dify/framework/response/GlobalResponseHandler.java new file mode 100644 index 0000000..39557b7 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/response/GlobalResponseHandler.java @@ -0,0 +1,71 @@ +//package com.zsc.edu.dify.framework.response; +// +//import com.fasterxml.jackson.core.JsonProcessingException; +//import com.fasterxml.jackson.databind.ObjectMapper; +//import jakarta.annotation.Resource; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.core.MethodParameter; +//import org.springframework.http.HttpHeaders; +//import org.springframework.http.MediaType; +//import org.springframework.http.converter.HttpMessageConverter; +//import org.springframework.http.server.ServerHttpRequest; +//import org.springframework.http.server.ServerHttpResponse; +//import org.springframework.web.bind.annotation.RestControllerAdvice; +//import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; +// +///** +// * 响应统一封装 +// *

+// * 将响应数据,封装成统一的数据格式。 +// *

+// * 通过本处理器,将接口方法返回的数据,统一封装到 ResponseResult 的 data 字段中,如果接口方法返回为 void,则 data 字段的值为 null。 +// * +// * @author zhuang +// */ +//@Slf4j +//@RestControllerAdvice(basePackages = "com.zsc.edu.dify") +//public class GlobalResponseHandler implements ResponseBodyAdvice { +// +// @Resource +// private ObjectMapper objectMapper; +// +// /** +// * 此组件是否支持给定的控制器方法返回类型和选定的 {@code HttpMessageConverter} 类型。 +// * +// * @return 如果应该调用 {@link #beforeBodyWrite} ,则为 {@code true};否则为false。 +// */ +// @Override +// public boolean supports(MethodParameter returnType, Class> converterType) { +// // 返回类型不为ResponseResult,才需要封装 +// return returnType.getParameterType() != ResponseResult.class; +// } +// +// /** +// * 统一封装返回响应数据 +// */ +// @Override +// public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, +// Class> selectedConverterType, ServerHttpRequest request, +// ServerHttpResponse response) { +// +// // 数据封装为ResponseResult:将接口方法返回的数据,封装到 ResponseResult.data 字段中。 +// ResponseResult responseResult = ResponseResult.success(body); +// +// // 返回类型不是 String:直接返回 +// if (returnType.getParameterType() != String.class) { +// return responseResult; +// } +// +// // 返回类型是 String:不能直接返回,需要进行额外处理 +// // 1. 将 Content-Type 设为 application/json ;返回类型是String时,默认 Content-Type = text/plain +// HttpHeaders headers = response.getHeaders(); +// headers.setContentType(MediaType.APPLICATION_JSON); +// // 2. 将 ResponseResult 转为 Json字符串 再返回 +// // (否则会报错 java.lang.ClassCastException: com.example.core.model.ResponseResult cannot be cast to java.lang.String) +// try { +// return objectMapper.writeValueAsString(responseResult); +// } catch (JsonProcessingException e) { +// throw new RuntimeException(e); +// } +// } +//} diff --git a/src/main/java/com/zsc/edu/dify/framework/response/HttpStatusEnum.java b/src/main/java/com/zsc/edu/dify/framework/response/HttpStatusEnum.java new file mode 100644 index 0000000..bcbc7b3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/response/HttpStatusEnum.java @@ -0,0 +1,93 @@ +//package com.zsc.edu.dify.framework.response; +// +//import lombok.Getter; +// +///** +// * Http状态返回枚举 +// * +// * @author javadog +// **/ +//@Getter +//public enum HttpStatusEnum { +// +// +// /** +// * 操作成功 +// */ +// SUCCESS(200, "操作成功"), +// /** +// * 对象创建成功 +// */ +// CREATED(201, "对象创建成功"), +// /** +// * 请求已经被接受 +// */ +// ACCEPTED(202, "请求已经被接受"), +// /** +// * 操作已经执行成功,但是没有返回数据 +// */ +// NO_CONTENT(204, "操作已经执行成功,但是没有返回数据"), +// /** +// * 资源已被移除 +// */ +// MOVED_PERM(301, "资源已被移除"), +// /** +// * 重定向 +// */ +// SEE_OTHER(303, "重定向"), +// /** +// * 资源没有被修改 +// */ +// NOT_MODIFIED(304, "资源没有被修改"), +// /** +// * 参数列表错误(缺少,格式不匹配) +// */ +// BAD_REQUEST(400, "参数列表错误(缺少,格式不匹配)"), +// /** +// * 未授权 +// */ +// UNAUTHORIZED(401, "未授权"), +// /** +// * 访问受限,授权过期 +// */ +// FORBIDDEN(403, "访问受限,授权过期"), +// /** +// * 资源,服务未找到 +// */ +// NOT_FOUND(404, "资源,服务未找!"), +// /** +// * 不允许的http方法 +// */ +// BAD_METHOD(405, "不允许的http方法"), +// /** +// * 资源冲突,或者资源被锁 +// */ +// CONFLICT(409, "资源冲突,或者资源被锁"), +// /** +// * 不支持的数据,媒体类型 +// */ +// UNSUPPORTED_TYPE(415, "不支持的数据,媒体类型"), +// /** +// * 系统内部错误 +// */ +// ERROR(500, "系统内部错误"), +// /** +// * 接口未实现 +// */ +// NOT_IMPLEMENTED(501, "接口未实现"), +// /** +// * 系统警告消息 +// */ +// WARN(601, "系统警告消息"); +// +// private final Integer code; +// private final String message; +// +// HttpStatusEnum(Integer code, String message) { +// +// +// this.code = code; +// this.message = message; +// } +//} +// diff --git a/src/main/java/com/zsc/edu/dify/framework/response/ResponseResult.java b/src/main/java/com/zsc/edu/dify/framework/response/ResponseResult.java new file mode 100644 index 0000000..5349397 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/response/ResponseResult.java @@ -0,0 +1,264 @@ +//package com.zsc.edu.dify.framework.response; +// +//import lombok.AllArgsConstructor; +//import lombok.Data; +//import lombok.NoArgsConstructor; +// +///** +// * 返回结果集 +// * +// * @author javadog +// **/ +//@Data +//@AllArgsConstructor +//@NoArgsConstructor +//public class ResponseResult { +// +// /** +// * 状态码 +// */ +// private Integer code; +// +// /** +// * 状态信息 +// */ +// private Boolean status; +// +// /** +// * 返回信息 +// */ +// private String message; +// +// /** +// * 数据 +// */ +// private T data; +// +// /** +// * 全参数方法 +// * +// * @param code 状态码 +// * @param status 状态 +// * @param message 返回信息 +// * @param data 返回数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// private static ResponseResult response(Integer code, Boolean status, String message, T data) { +// +// +// ResponseResult responseResult = new ResponseResult<>(); +// responseResult.setCode(code); +// responseResult.setStatus(status); +// responseResult.setMessage(message); +// responseResult.setData(data); +// return responseResult; +// } +// +// /** +// * 全参数方法 +// * +// * @param code 状态码 +// * @param status 状态 +// * @param message 返回信息 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// private static ResponseResult response(Integer code, Boolean status, String message) { +// +// +// ResponseResult responseResult = new ResponseResult<>(); +// responseResult.setCode(code); +// responseResult.setStatus(status); +// responseResult.setMessage(message); +// return responseResult; +// } +// +// /** +// * 成功返回(无参) +// * +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success() { +// +//// return response(HttpStatus.OK.value(), true, HttpStatus.OK.getReasonPhrase(), null); +// return response(HttpStatusEnum.SUCCESS.getCode(), true, HttpStatusEnum.SUCCESS.getMessage(), null); +// } +// +// /** +// * 成功返回(枚举参数) +// * +// * @param httpResponseEnum 枚举参数 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success(HttpStatusEnum httpResponseEnum) { +// +// +// return response(httpResponseEnum.getCode(), true, httpResponseEnum.getMessage()); +// } +// +// /** +// * 成功返回(状态码+返回信息) +// * +// * @param code 状态码 +// * @param message 返回信息 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success(Integer code, String message) { +// +// +// return response(code, true, message); +// } +// +// /** +// * 成功返回(返回信息 + 数据) +// * +// * @param message 返回信息 +// * @param data 数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success(String message, T data) { +// +// +// return response(HttpStatusEnum.SUCCESS.getCode(), true, message, data); +// } +// +// /** +// * 成功返回(状态码+返回信息+数据) +// * +// * @param code 状态码 +// * @param message 返回信息 +// * @param data 数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success(Integer code, String message, T data) { +// +// +// return response(code, true, message, data); +// } +// +// /** +// * 成功返回(数据) +// * +// * @param data 数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success(T data) { +// +// +// return response(HttpStatusEnum.SUCCESS.getCode(), true, HttpStatusEnum.SUCCESS.getMessage(), data); +// } +// +// /** +// * 成功返回(返回信息) +// * +// * @param message 返回信息 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult success(String message) { +// +// +// return response(HttpStatusEnum.SUCCESS.getCode(), true, message, null); +// } +// +// /** +// * 失败返回(无参) +// * +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail() { +// +// +// return response(HttpStatusEnum.ERROR.getCode(), false, HttpStatusEnum.ERROR.getMessage(), null); +// } +// +// /** +// * 失败返回(枚举) +// * +// * @param httpResponseEnum 枚举 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail(HttpStatusEnum httpResponseEnum) { +// +// +// return response(httpResponseEnum.getCode(), false, httpResponseEnum.getMessage()); +// } +// +// /** +// * 失败返回(状态码+返回信息) +// * +// * @param code 状态码 +// * @param message 返回信息 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail(Integer code, String message) { +// +// +// return response(code, false, message); +// } +// +// /** +// * 失败返回(返回信息+数据) +// * +// * @param message 返回信息 +// * @param data 数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail(String message, T data) { +// +// +// return response(HttpStatusEnum.ERROR.getCode(), false, message, data); +// } +// +// /** +// * 失败返回(状态码+返回信息+数据) +// * +// * @param code 状态码 +// * @param message 返回消息 +// * @param data 数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail(Integer code, String message, T data) { +// +// +// return response(code, false, message, data); +// } +// +// /** +// * 失败返回(数据) +// * +// * @param data 数据 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail(T data) { +// +// +// return response(HttpStatusEnum.ERROR.getCode(), false, HttpStatusEnum.ERROR.getMessage(), data); +// } +// +// /** +// * 失败返回(返回信息) +// * +// * @param message 返回信息 +// * @param 泛型 +// * @return {@link ResponseResult} +// */ +// public static ResponseResult fail(String message) { +// +// +// return response(HttpStatusEnum.ERROR.getCode(), false, message, null); +// } +//} diff --git a/src/main/java/com/zsc/edu/dify/framework/response/Result.java b/src/main/java/com/zsc/edu/dify/framework/response/Result.java new file mode 100644 index 0000000..d805c26 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/response/Result.java @@ -0,0 +1,51 @@ +//package com.zsc.edu.dify.framework.response; +// +//import lombok.*; +// +///** +// * 返回响应,统一封装实体 +// * +// * @param 数据实体泛型 +// * @author zhuang +// */ +//@Getter +//@ToString +//@EqualsAndHashCode +//@AllArgsConstructor(access = AccessLevel.PRIVATE) +//public class Result { +// +// private String userMessage; +// +// /** +// * 错误码
+// * 调用成功时,为 null。
+// * 示例:A0211 +// */ +// private String errorCode; +// +// /** +// * 错误信息
+// * 调用成功时,为 null。
+// * 示例:"用户输入密码错误次数超限" +// */ +// private String errorMessage; +// +// /** +// * 数据实体(泛型)
+// * 当接口没有返回数据时,为 null。 +// */ +// private T data; +// +// +// public static Result success(T data) { +// return new Result<>("操作成功!", null, null, data); +// } +// +// +// public static Result fail(String userMessage, String errorCode, String errorMessage) { +// return new Result<>(userMessage, errorCode, errorMessage, null); +// } +// +//} +// +// diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java new file mode 100644 index 0000000..e10ac4f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomAccessDeniedHandler.java @@ -0,0 +1,47 @@ +package com.zsc.edu.dify.framework.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zsc.edu.dify.exception.ExceptionResult; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.security.web.access.AccessDeniedHandler; +import org.springframework.security.web.csrf.MissingCsrfTokenException; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * @author harry_yao + */ +@AllArgsConstructor +@Component +public class CustomAccessDeniedHandler implements AccessDeniedHandler { + + private final ObjectMapper objectMapper; + + @Override + public void handle(HttpServletRequest request, HttpServletResponse response, + AccessDeniedException ex) throws IOException, ServletException { + response.setContentType("application/json;charset=utf-8"); + ExceptionResult result; + if (ex instanceof MissingCsrfTokenException) { + System.out.println("MissingCsrfTokenException"); + // 会话已注销,返回401 + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + result = new ExceptionResult("凭证已过期,请重新登录", HttpStatus.UNAUTHORIZED.value(), + LocalDateTime.now()); + } else { + // 403 + response.setStatus(HttpStatus.FORBIDDEN.value()); + result = new ExceptionResult("禁止操作", HttpStatus.FORBIDDEN.value(), + LocalDateTime.now()); + } + response.getWriter().print(objectMapper.writeValueAsString(result)); + response.flushBuffer(); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationEntryPoint.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..d71c30e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationEntryPoint.java @@ -0,0 +1,35 @@ +package com.zsc.edu.dify.framework.security;///* + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.AuthenticationEntryPoint; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.util.Map; + +/** + * 认证处理 + * 当用户尝试访问安全的REST资源而不提供任何凭据时,将调用此方法发送401 响应 + * @author Yao + */ +@Slf4j +@Component +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + @Resource + private ObjectMapper objectMapper; + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { + response.setCharacterEncoding("UTF-8"); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.setContentType("application/json"); + response.getWriter().println(objectMapper.writeValueAsString(Map.of("msg", "认证失败: " + authException.getMessage()))); + response.flushBuffer(); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationFailureHandler.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationFailureHandler.java new file mode 100644 index 0000000..01a389f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationFailureHandler.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.framework.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zsc.edu.dify.exception.ExceptionResult; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * @author harry_yao + */ +@AllArgsConstructor +@Component +public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler { + + private final ObjectMapper objectMapper; + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, + AuthenticationException exception) throws IOException { + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType("application/json;charset=utf-8"); + ExceptionResult result = new ExceptionResult(exception.getMessage(), HttpStatus.UNAUTHORIZED.value(), + LocalDateTime.now()); + response.getWriter().print(objectMapper.writeValueAsString(result)); + response.flushBuffer(); + } + +} + diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationSuccessHandler.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationSuccessHandler.java new file mode 100644 index 0000000..9107f02 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomAuthenticationSuccessHandler.java @@ -0,0 +1,45 @@ +package com.zsc.edu.dify.framework.security; + +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.AllArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.web.authentication.AuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; + +/** + * @author harry_yao + */ +@AllArgsConstructor +@Component +public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler { + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { +// response.sendRedirect("/api/rest/user/me"); + request.getRequestDispatcher("/api/rest/user/me").forward(request, response); +// Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); +// String sessionId = request.getRequestedSessionId(); +// String remoteAddr = request.getRemoteAddr(); +// User user = userService.getOne(((UserDetailsImpl) principal).getId()); +// String agent = request.getHeader("User-Agent"); +// UserAgent userAgent = new UserAgent(agent); +// OnlineUser onlineUser = onlineUserService.create( +// sessionId, +// user.username, +// user.name, +// null,//user.identity.dept.name, +// remoteAddr, +// userAgent.getBrowser().getName(), +// userAgent.getOperatingSystem().getName(), +// LocalDateTime.now() +// ); +// loginLogService.create(onlineUser.username, onlineUser.name, onlineUser.deptName, +// onlineUser.ip, onlineUser.browser, onlineUser.os, +// onlineUser.loginTime, LoginLog.Result.登陆成功); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java b/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java new file mode 100644 index 0000000..0dc4b20 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/CustomSessionInformationExpiredStrategy.java @@ -0,0 +1,33 @@ +package com.zsc.edu.dify.framework.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.HttpStatus; +import org.springframework.security.web.session.SessionInformationExpiredEvent; +import org.springframework.security.web.session.SessionInformationExpiredStrategy; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.time.LocalDateTime; +import java.util.Map; + +/** + * @author harry_yao + */ +@Component +public class CustomSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy { + @Override + public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException { + + HttpServletResponse response = event.getResponse(); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType("application/json;charset=utf-8"); + ObjectMapper objectMapper = new ObjectMapper(); + response.getWriter().print(objectMapper.writeValueAsString(Map.of( + "msg", "会话已过期(有可能是您同时登录了太多的太多的客户端)", + "code", HttpStatus.UNAUTHORIZED.value(), + "timestamp", LocalDateTime.now() + ))); + response.flushBuffer(); + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java b/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java new file mode 100644 index 0000000..0570adb --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/JpaUserDetailsServiceImpl.java @@ -0,0 +1,55 @@ +package com.zsc.edu.dify.framework.security; + +import com.zsc.edu.dify.common.util.TreeUtil; +import com.zsc.edu.dify.exception.StateException; +import com.zsc.edu.dify.modules.system.entity.*; +import com.zsc.edu.dify.modules.system.repo.*; +import com.zsc.edu.dify.modules.system.service.DeptService; +import com.zsc.edu.dify.modules.system.service.RoleService; +import lombok.AllArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author harry_yao + */ +@AllArgsConstructor +@Service +public class JpaUserDetailsServiceImpl implements UserDetailsService { + + private final UserRepository userRepo; + private final AuthorityRepository authorityRepository; + private final MenuRepository menuRepository; + private final DeptService deptService; + private final RoleRepository roleRepository; + private final RoleService roleService; + private final UserRolesRepository userRolesRepository; + + @Override + @Transactional(rollbackFor = Exception.class) + public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { + User user = userRepo.selectByUsername(username); + if (!user.getEnableState()) { + throw new StateException("用户 '" + username + "' 已被禁用!请联系管理员"); + } + List roleIds = userRolesRepository.selectByUserId(user.getId()); + List roles = roleRepository.selectByIds(roleIds); + user.setRoles(roles); + List depts = deptService.listTree(user.deptId); + List flat = TreeUtil.flat(depts, Dept::getChildren, d -> d.setChildren(null)); + Set dataScopeDeptIds = flat.stream().map(Dept::getId).collect(Collectors.toSet()); + user.setDataScopeDeptIds(dataScopeDeptIds); +// List roleAuthorities= roleAuthoritiesRepository.selectByRoleId(user.getRoleId()); +// user.role.authorities = authorityRepository.selectAuthoritiesByRoleId(user.getRoleId()); + List menus = menuRepository.selectByRoleId(user.getRoleId()); + Set permissions = menus.stream().map(Menu::getPermissions).collect(Collectors.toSet()); + return UserDetailsImpl.from(user, permissions); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/JsonAuthenticationFilter.java b/src/main/java/com/zsc/edu/dify/framework/security/JsonAuthenticationFilter.java new file mode 100644 index 0000000..8497e2c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/JsonAuthenticationFilter.java @@ -0,0 +1,44 @@ +package com.zsc.edu.dify.framework.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.http.MediaType; +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +import java.io.IOException; +import java.util.Map; + +public class JsonAuthenticationFilter extends UsernamePasswordAuthenticationFilter { + + @Override + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) + throws AuthenticationException { + if (!request.getMethod().equals("POST")) { + throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); + } + if (request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) { + try { + Map map = new ObjectMapper().readValue(request.getInputStream(), Map.class); + String username = map.get("username").toString(); + String password = map.get("password").toString(); + username = (username != null) ? username : ""; + username = username.trim(); + password = (password != null) ? password : ""; + password = password.trim(); + UsernamePasswordAuthenticationToken authRequest = + UsernamePasswordAuthenticationToken.unauthenticated(username, password); + // Allow subclasses to set the "details" property + setDetails(request, authRequest); + return this.getAuthenticationManager().authenticate(authRequest); + } catch (IOException e) { + e.printStackTrace(); + } + } + return super.attemptAuthentication(request, response); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/SecurityBeanConfig.java b/src/main/java/com/zsc/edu/dify/framework/security/SecurityBeanConfig.java new file mode 100644 index 0000000..9a19c9a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/SecurityBeanConfig.java @@ -0,0 +1,30 @@ +package com.zsc.edu.dify.framework.security; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.core.session.SessionRegistryImpl; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.session.HttpSessionEventPublisher; + +/** + * @author harry_yao + */ +@Configuration +public class SecurityBeanConfig { + @Bean + public PasswordEncoder passwordEncoder() { + return PasswordEncoderFactories.createDelegatingPasswordEncoder(); + } + + @Bean + public SessionRegistry sessionRegistry() { + return new SessionRegistryImpl(); + } + + @Bean + public HttpSessionEventPublisher httpSessionEventPublisher() { + return new HttpSessionEventPublisher(); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/SecurityUtil.java b/src/main/java/com/zsc/edu/dify/framework/security/SecurityUtil.java new file mode 100644 index 0000000..12559db --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/SecurityUtil.java @@ -0,0 +1,49 @@ +package com.zsc.edu.dify.framework.security; + +import org.springframework.security.authentication.AnonymousAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.util.Objects; +import java.util.Optional; + +/** + * @author Yao + */ +public class SecurityUtil { + + public static UserDetailsImpl getUserInfo() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (Objects.isNull(authentication)) { + return new UserDetailsImpl(); + } + return (UserDetailsImpl) authentication.getPrincipal(); + } + + public static void setUserInfo(UserDetailsImpl user) { + // 重新加载用户信息并更新SecurityContext + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities()) + ); + } + + public static Optional getCurrentAuditor() { + try { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication instanceof AnonymousAuthenticationToken) { + return Optional.of("system"); + } else { + if (authentication == null) { + return Optional.of("system"); + } + UserDetailsImpl user = (UserDetailsImpl) authentication.getPrincipal(); + return Optional.of(user.getUsername()); + } + } catch (Exception ex) { + // log.error("get user Authentication failed: " + ex.getMessage(), ex); + return Optional.of("system"); + } + } + +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java b/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java new file mode 100644 index 0000000..1d6bbde --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/SpringSecurityConfig.java @@ -0,0 +1,148 @@ +package com.zsc.edu.dify.framework.security; + +import jakarta.annotation.Resource; +import lombok.AllArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl; +import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; +import org.springframework.security.web.context.HttpSessionSecurityContextRepository; + +import javax.sql.DataSource; + +/** + * @author harry_yao + */ +@AllArgsConstructor +@EnableMethodSecurity +@Configuration +public class SpringSecurityConfig { + + private final UserDetailsService userDetailsService; + private final CustomAuthenticationFailureHandler customAuthenticationFailureHandler; + private final CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler; + private final CustomAuthenticationEntryPoint customAuthenticationEntryPoint; + private final CustomAccessDeniedHandler customAccessDeniedHandler; + private final SessionRegistry sessionRegistry; + private final SecurityBeanConfig securityBeanConfig; + private final CustomSessionInformationExpiredStrategy customSessionInformationExpiredStrategy; + + @Resource + private final DataSource dataSource; + +// @Bean +// public BCryptPasswordEncoder bCryptPasswordEncoder() { +// return new BCryptPasswordEncoder(); +// }; + + @Bean + public PersistentTokenRepository persistentTokenRepository() { + JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl(); + tokenRepository.setDataSource(dataSource); + return tokenRepository; + + } + + @Bean + AuthenticationManager authenticationManager() { + DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); + daoAuthenticationProvider.setUserDetailsService(userDetailsService); + daoAuthenticationProvider.setPasswordEncoder(securityBeanConfig.passwordEncoder()); + return new ProviderManager(daoAuthenticationProvider); + } + + @Bean + public JsonAuthenticationFilter jsonAuthenticationFilter() throws Exception { + JsonAuthenticationFilter filter = new JsonAuthenticationFilter(); + filter.setAuthenticationSuccessHandler(customAuthenticationSuccessHandler); + filter.setAuthenticationFailureHandler(customAuthenticationFailureHandler); + filter.setFilterProcessesUrl("/api/rest/user/login"); + filter.setAuthenticationManager(authenticationManager()); + // 将登录后的请求信息保存到Session中,不然会报null + filter.setSecurityContextRepository(new HttpSessionSecurityContextRepository()); + return filter; + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + + return http + .authorizeHttpRequests(auth -> auth + .requestMatchers(HttpMethod.GET, "/api/rest/user/menu","/api/rest/user/register","/api/rest/user/send-email").permitAll() + .requestMatchers(HttpMethod.POST, "/api/rest/user/login","/api/rest/user/register").permitAll() + .requestMatchers("/api/rest/user/me").permitAll() + .requestMatchers("/api/**").authenticated() + ) + // 不用注解,直接通过判断路径实现动态访问权限 +// .requestMatchers("/api/**").access((authentication, object) -> { +// //表示请求的 URL 地址和数据库的地址是否匹配上了 +// boolean isMatch = false; +// //获取当前请求的 URL 地址 +// String requestURI = object.getRequest().getRequestURI(); +// List menuWithRole = menuService.getMenuWithRole(); +// for (MenuWithRoleVO m : menuWithRole) { +// AntPathMatcher antPathMatcher = new AntPathMatcher(); +// if (antPathMatcher.match(m.getUrl(), requestURI)) { +// isMatch = true; +// //说明找到了请求的地址了 +// //这就是当前请求需要的角色 +// List roles = m.getRoles(); +// //获取当前登录用户的角色 +// Collection authorities = authentication.get().getAuthorities(); +// for (GrantedAuthority authority : authorities) { +// for (Role role : roles) { +// if (authority.getAuthority().equals(role.getName())) { +// //说明当前登录用户具备当前请求所需要的角色 +// return new AuthorizationDecision(true); +// } +// } +// } +// } +// } +// if (!isMatch) { +// //说明请求的 URL 地址和数据库的地址没有匹配上,对于这种请求,统一只要登录就能访问 +// if (authentication.get() instanceof AnonymousAuthenticationToken) { +// return new AuthorizationDecision(false); +// } else { +// //说明用户已经认证了 +// return new AuthorizationDecision(true); +// } +// } +// return new AuthorizationDecision(false); +// })) + .addFilterAt(jsonAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) + .formLogin(form -> form + .loginPage("/user/login") + .loginProcessingUrl("/api/rest/user/login") + .successHandler(customAuthenticationSuccessHandler) + .failureHandler(customAuthenticationFailureHandler)) + .logout(logout -> logout + .logoutUrl("/api/user/logout") + .logoutSuccessHandler((request, response, authentication) -> {})) + // 添加自定义未授权和未登录结果返回 + .exceptionHandling(exception -> exception + .authenticationEntryPoint(customAuthenticationEntryPoint) + .accessDeniedHandler(customAccessDeniedHandler) + ) + .rememberMe(rememberMe -> rememberMe + .userDetailsService(userDetailsService) + .tokenRepository(persistentTokenRepository())) + .csrf(csrf -> csrf.ignoringRequestMatchers("/api/internal/**", "/api/rest/user/logout","/api/rest/user/register")) + .sessionManagement(session -> session + .maximumSessions(3) + .sessionRegistry(sessionRegistry) + .expiredSessionStrategy(customSessionInformationExpiredStrategy)) + .build(); + + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java b/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java new file mode 100644 index 0000000..6b38d42 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/security/UserDetailsImpl.java @@ -0,0 +1,101 @@ +package com.zsc.edu.dify.framework.security; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.User; +import lombok.*; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author harry yao + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +@JsonIgnoreProperties("password") +public class UserDetailsImpl implements UserDetails { + + public Long id; + public String username; + public String password; + public Boolean enableState; + public String name; + public Dept dept; + public Role role; + public List roles; + public Set authorities; + public Set permissions; + public Set dataScopeDeptIds; + public Long deptId; + public Long createId; + + public UserDetailsImpl(Long id, String username, String password, String name, Boolean enableState, Dept dept, Set dataScopeDeptIds, Role role, Set authorities, Set permissions, List roles, Long deptId, Long createId) { + this.id = id; + this.username = username; + this.password = password; + this.enableState = enableState; + this.name = name; + this.dept = dept; + this.dataScopeDeptIds = dataScopeDeptIds; + this.role = role; + this.authorities = authorities; + this.permissions = permissions; + this.roles = roles; + } + + public static UserDetailsImpl from(User user, Set permissions) { + return new UserDetailsImpl( + user.id, + user.username, + user.password, + user.name, + user.enableState, + user.dept, + user.dataScopeDeptIds, + user.role, + user.role.authorities, + permissions, + user.roles, + user.deptId, + user.createId + ); + } + + @Override + public Collection getAuthorities() { +// return authorities.stream().map(authority -> new SimpleGrantedAuthority(authority.getName())).collect(Collectors.toSet()); + return permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet()); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return enableState; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/framework/storage/StorageProperties.java b/src/main/java/com/zsc/edu/dify/framework/storage/StorageProperties.java new file mode 100644 index 0000000..86e544e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/storage/StorageProperties.java @@ -0,0 +1,25 @@ +package com.zsc.edu.dify.framework.storage; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +/** + * @author harry_yao + */ +@ConfigurationProperties("storage") +@Component +public class StorageProperties { + + /** + * 附件存储路径 + */ + @Value("${storage.attachment}") + public String attachment; + + /** + * 临时文件存储路径 + */ + @Value("${storage.temp}") + public String temp; +} diff --git a/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageException.java b/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageException.java new file mode 100644 index 0000000..f3b006f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageException.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.framework.storage.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) +public class StorageException extends RuntimeException { + + public StorageException() { + super("文件存储出错"); + } + + public StorageException(String message) { + super(message); + } + + public StorageException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageFileEmptyException.java b/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageFileEmptyException.java new file mode 100644 index 0000000..cab3f98 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageFileEmptyException.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.framework.storage.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.BAD_REQUEST) +public class StorageFileEmptyException extends StorageException { + + public StorageFileEmptyException() { + super("存储的是空文件!"); + } + + public StorageFileEmptyException(String message) { + super(message); + } + + public StorageFileEmptyException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageFileNotFoundException.java b/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageFileNotFoundException.java new file mode 100644 index 0000000..4983064 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/framework/storage/exception/StorageFileNotFoundException.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.framework.storage.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * @author harry_yao + */ +@ResponseStatus(HttpStatus.NOT_FOUND) +public class StorageFileNotFoundException extends StorageException { + + public StorageFileNotFoundException() { + super("文件不存在!"); + } + + public StorageFileNotFoundException(String message) { + super(message); + } + + public StorageFileNotFoundException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/attachment/controller/AttachmentController.java b/src/main/java/com/zsc/edu/dify/modules/attachment/controller/AttachmentController.java new file mode 100644 index 0000000..80f42c3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/attachment/controller/AttachmentController.java @@ -0,0 +1,107 @@ +package com.zsc.edu.dify.modules.attachment.controller; + +import com.zsc.edu.dify.exception.StorageException; +import com.zsc.edu.dify.modules.attachment.entity.Attachment; +import com.zsc.edu.dify.modules.attachment.service.AttachmentService; +import lombok.AllArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.http.ContentDisposition; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +/** + * 附件Controller + * + * @author harry_yao + */ +@AllArgsConstructor +@RestController +@RequestMapping("api/rest/attachment") +public class AttachmentController { + + private final AttachmentService service; + + /** + * 上传附件 + * + * @param type 附件功能类型 + * @param file 文件 + * @return 附件信息 + */ + @PostMapping() + public Attachment upload( + @RequestParam(required = false, defaultValue = "其他") Attachment.Type type, + @RequestParam("file") MultipartFile file + ) { + try { + if (type == null) { + type = Attachment.Type.其他; + } + return service.store(type, file); + } catch (IOException e) { + throw new StorageException("文件上传出错"); + } + } + + /** + * 下载附件 + * + * @param id 附件ID + * @return 附件文件内容 + */ + @GetMapping("{id}") + public ResponseEntity download( + @PathVariable("id") String id + ) { + Attachment.Wrapper wrapper = service.loadAsWrapper(id); + if (wrapper.attachment.fileName != null) { + ContentDisposition contentDisposition = ContentDisposition.builder("attachment").filename(wrapper.attachment.fileName, StandardCharsets.UTF_8).build(); + return ResponseEntity.ok(). + header(HttpHeaders.CONTENT_DISPOSITION, contentDisposition.toString()). + header(HttpHeaders.CONTENT_TYPE, wrapper.attachment.mimeType). + body(wrapper.resource); + } + return ResponseEntity.ok(wrapper.resource); + } + /** + * 根据附件ID获取附件信息 + * */ + @GetMapping("find/{id}") + public Attachment getAttachmentInfo(@PathVariable("id") String id) { + return service.getById(id); + } + + + /** + * 批量上传附件 + */ + @PostMapping("multipartFile") + public List uploadMultipleFiles( + @RequestParam(defaultValue = "其他") Attachment.Type type, + @RequestParam("files") List files + ) throws IOException { + List attachments = new ArrayList<>(); + for (MultipartFile file : files) { + if (!file.isEmpty()) { + attachments.add(service.stores(type, file)); + } + } + return attachments; + } + + /** + * 根据附件ID删除附件信息 + */ + @DeleteMapping("delete/{id}") + public Boolean delete(@PathVariable("id") String id) { + return service.delete(id); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/attachment/dto/AttachmentDto.java b/src/main/java/com/zsc/edu/dify/modules/attachment/dto/AttachmentDto.java new file mode 100644 index 0000000..145f633 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/attachment/dto/AttachmentDto.java @@ -0,0 +1,64 @@ +package com.zsc.edu.dify.modules.attachment.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author ftz + * 创建时间:29/1/2024 上午10:00 + * 描述: 附件Dto + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AttachmentDto { + /** + * 文件名 文件名详细说明 + */ + + private String saveFilename; + + /** + * 文件UUID 返回给前端的文件UUID + */ + + private String uuid; + + /** + * 上传时的文件名 原文件名 + */ + + private String originFilename; + + /** + * 文件大小 + */ + + private Long fileSize; + + /** + * 文件类型 + */ + + private String fileType; + + /** + * 文件扩展名 + */ + + private String extendName; + + /** + * 票据id 附件对应的票据id + */ + + private Long ticketId; + private String remark; + + /** + *文件url + */ + + private String fileUrl; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/attachment/entity/Attachment.java b/src/main/java/com/zsc/edu/dify/modules/attachment/entity/Attachment.java new file mode 100644 index 0000000..d63fc5d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/attachment/entity/Attachment.java @@ -0,0 +1,98 @@ +package com.zsc.edu.dify.modules.attachment.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import lombok.*; +import org.springframework.core.io.FileSystemResource; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 附件 + * + * @author harry_yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +@TableName(value ="attachment") +public class Attachment implements Serializable { + + /** + * ID,用文件和文件名的SHA-1值生成 + */ + @TableId + public String id; + + /** + * 文件名 + */ + + public String fileName; + + /** + * 附件作用类型 + */ + + public String mimeType; + + /** + * 附件功能类型 + */ +// @Column(nullable = false) +// @Enumerated(EnumType.STRING) +// public Type type = Type.其他; + + /** + * 文件上传时间 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime uploadTime; + + /** + * 文件下载链接 + */ + @JsonSerialize + @TableField(exist = false) + public String url; + + public Attachment(String id, String fileName, String mimeType, Type type, LocalDateTime uploadTime) { + this.id = id; + this.fileName = fileName; + this.mimeType = mimeType; + // this.type = type; + this.uploadTime = uploadTime; + this.url = "/api/rest/attachment/" + id; + } + + public void setId(String id) { + this.id = id; + this.url = "/api/rest/attachment/" + id; + } + + public String getUrl() { + return "/api/rest/attachment/" + id; + } + + /** + * 枚举类:附件功能类型 + */ + public enum Type { + 其他, + 头像 + } + + @AllArgsConstructor + public static final class Wrapper { + + public Attachment attachment; + + public FileSystemResource resource; + + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/attachment/repo/AttachmentRepository.java b/src/main/java/com/zsc/edu/dify/modules/attachment/repo/AttachmentRepository.java new file mode 100644 index 0000000..40dd79d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/attachment/repo/AttachmentRepository.java @@ -0,0 +1,11 @@ +package com.zsc.edu.dify.modules.attachment.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.attachment.entity.Attachment; + +/** + * @author ftz + * 创建时间:29/1/2024 上午9:55 + */ +public interface AttachmentRepository extends BaseMapper{ +} diff --git a/src/main/java/com/zsc/edu/dify/modules/attachment/service/AttachmentService.java b/src/main/java/com/zsc/edu/dify/modules/attachment/service/AttachmentService.java new file mode 100644 index 0000000..4c707a8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/attachment/service/AttachmentService.java @@ -0,0 +1,28 @@ +package com.zsc.edu.dify.modules.attachment.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.modules.attachment.entity.Attachment; +import org.springframework.core.io.Resource; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +/** +* @author fantianzhi + * @description 针对表【attach_file(票据附件表)】的数据库操作Service + * @createDate 2024-01-28 19:48:22 + */ +public interface AttachmentService extends IService { + + Attachment store(Attachment.Type type, MultipartFile file) throws IOException; + Attachment stores(Attachment.Type type, MultipartFile file)throws IOException; + + Resource loadAsResource(String id); + + Attachment.Wrapper loadAsWrapper(String id); + + List selectList(List dis); + + Boolean delete(String id); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/attachment/service/impl/AttachmentServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/attachment/service/impl/AttachmentServiceImpl.java new file mode 100644 index 0000000..9bdc697 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/attachment/service/impl/AttachmentServiceImpl.java @@ -0,0 +1,223 @@ +package com.zsc.edu.dify.modules.attachment.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.framework.storage.StorageProperties; +import com.zsc.edu.dify.framework.storage.exception.StorageFileEmptyException; +import com.zsc.edu.dify.framework.storage.exception.StorageFileNotFoundException; +import com.zsc.edu.dify.modules.attachment.entity.Attachment; +import com.zsc.edu.dify.modules.attachment.repo.AttachmentRepository; +import com.zsc.edu.dify.modules.attachment.service.AttachmentService; +import jakarta.annotation.PostConstruct; +import org.apache.commons.codec.binary.Hex; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.tika.Tika; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; + +/** + * 附件Service + * + * @author harry_yao + */ +@Service +public class AttachmentServiceImpl extends ServiceImpl implements AttachmentService { + + final static int[] illegalChars = {34, 60, 62, 124, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 58, 42, 63, 92, 47}; + + private final AttachmentRepository repo; + private final Path attachmentPath; + private final Path tempPath; + + public AttachmentServiceImpl(AttachmentRepository repo, StorageProperties storageProperties) { + this.repo = repo; + this.attachmentPath = Paths.get(storageProperties.attachment); + this.tempPath = Paths.get(storageProperties.temp); + } + + @PostConstruct + public void init() throws IOException { + if (Files.notExists(attachmentPath)) { + Files.createDirectories(attachmentPath); + } + if (Files.notExists(tempPath)) { + Files.createDirectories(tempPath); + } + } + + public Attachment store(Attachment.Type type, MultipartFile file) throws IOException { + if (file.isEmpty()) { + throw new StorageFileEmptyException(); + } + MessageDigest digest = DigestUtils.getSha1Digest(); + String filename = file.getOriginalFilename(); + if (filename != null) { + digest.update(filename.getBytes()); + } + Path temp = tempPath.resolve(String.valueOf(System.nanoTime())); + byte[] fileContent = file.getBytes(); + ByteArrayInputStream input = new ByteArrayInputStream(fileContent); + Tika tika = new Tika(); + String mimeType = tika.detect(input, filename); + OutputStream output = Files.newOutputStream(temp); + digest.update(fileContent); + output.write(fileContent); + input.close(); + output.flush(); + output.close(); + String sha1 = Hex.encodeHexString(digest.digest()); + return save(temp, sha1, filename, mimeType, type); + } + + @Override + public Attachment stores(Attachment.Type type, MultipartFile file) throws IOException{ + if (file.isEmpty()) { + throw new StorageFileEmptyException("上传的文件不能为空"); + } + + String filename = file.getOriginalFilename(); + if (filename == null || filename.trim().isEmpty()) { + throw new IllegalArgumentException("文件名不能为空"); + } + + MessageDigest digest = DigestUtils.getSha1Digest(); + digest.update(filename.getBytes(StandardCharsets.UTF_8)); + + Path temp = tempPath.resolve(String.valueOf(System.nanoTime())); + byte[] fileContent = file.getBytes(); + + // 使用try-with-resources自动关闭流 + try (ByteArrayInputStream input = new ByteArrayInputStream(fileContent); + OutputStream output = Files.newOutputStream(temp)) { + Tika tika = new Tika(); + String mimeType = tika.detect(input, filename); + digest.update(fileContent); + output.write(fileContent); + String sha1 = Hex.encodeHexString(digest.digest()); + return save(temp, sha1, filename, mimeType, type); + } + + } + public Attachment store(Attachment.Type type, File file) throws IOException { + MessageDigest digest = DigestUtils.getSha1Digest(); + String filename = file.getName(); + if (filename != null) { + digest.update(filename.getBytes()); + } + Tika tika = new Tika(); + String mimeType = tika.detect(file); + String sha1 = Hex.encodeHexString(digest.digest()); + return save(file.toPath(), sha1, filename, mimeType, type); + } + + public Attachment store(Attachment.Type type, Path file) throws IOException { + String filename = file.getFileName().toString(); + MessageDigest digest = DigestUtils.getSha1Digest(); + if (StringUtils.hasText(filename)) { + digest.update(filename.getBytes()); + } + Tika tika = new Tika(); + String mimeType = tika.detect(file); + InputStream input = Files.newInputStream(file); + byte[] buf = new byte[8192]; + int n; + while ((n = input.read(buf)) > 0) { + digest.update(buf, 0, n); + } + String sha1 = Hex.encodeHexString(digest.digest()); + return save(file, sha1, filename, mimeType, type); + } + + @Override + public Resource loadAsResource(String id) { + Path file = attachmentPath.resolve(id); + FileSystemResource resource = new FileSystemResource(file); + if (resource.exists() || resource.isReadable()) { + return resource; + } else { + throw new StorageFileNotFoundException(); + } + } + + @Override + public Attachment.Wrapper loadAsWrapper(String id) { + Path file = attachmentPath.resolve(id); + FileSystemResource resource = new FileSystemResource(file); + if (!resource.exists() || !resource.isReadable()) { + throw new StorageFileNotFoundException(); + } + Attachment attachment = repo.selectById(id); //.orElseThrow(NotExistException::new); + return new Attachment.Wrapper(attachment, resource); + } + + public Attachment findById(String id) { + return repo.selectById(id); //.orElseThrow(NotExistException::new); + } + + public List findAllById(Collection ids) { + return (ids != null && !ids.isEmpty()) ? repo.selectList(new LambdaQueryWrapper().in(Attachment::getId, ids)) : new ArrayList<>(); + } + + public Path convertToTempPath(String fileName) { + fileName = fileName.replace("/", ""); + Path path; + try { + path = tempPath.resolve(fileName); + } catch (Exception e) { + StringBuilder cleanName = new StringBuilder(); + for (int i = 0; i < fileName.length(); i++) { + int c = fileName.charAt(i); + if (Arrays.binarySearch(illegalChars, c) < 0) { + cleanName.append((char) c); + } + } + path = tempPath.resolve(cleanName.toString()); + } + return path; + } + + private Attachment save(Path temp, String id, String filename, String mimeType, Attachment.Type type) throws IOException { + Path dest = attachmentPath.resolve(id); + if (Files.exists(dest)) { + return findById(id); + } + Files.move(temp, dest); + Attachment attachment = new Attachment(id, filename, mimeType, type, LocalDateTime.now()); + repo.insert(attachment); + return attachment; + } + + @Override + public List selectList(List dis) { + return repo.selectList(new LambdaQueryWrapper().in(Attachment::getId, dis)); + } + + @Override + public Boolean delete(String id) { + if (Files.exists(attachmentPath.resolve(id))) { + try { + repo.deleteById(id); + Files.delete(attachmentPath.resolve(id)); + return true; + } catch (IOException e) { + log.error("删除文件失败", e); + } + } + return false; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/controller/BulletinController.java b/src/main/java/com/zsc/edu/dify/modules/message/controller/BulletinController.java new file mode 100644 index 0000000..7d70e80 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/controller/BulletinController.java @@ -0,0 +1,155 @@ +package com.zsc.edu.dify.modules.message.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.framework.mybatisplus.DataPermission; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.BulletinDto; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import com.zsc.edu.dify.modules.message.query.BulletinQuery; +import com.zsc.edu.dify.modules.message.service.BulletinService; +import com.zsc.edu.dify.modules.message.vo.BulletinVo; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 公告Controller + * + * @author harry_yao + */ +@AllArgsConstructor +@RestController +@RequestMapping("/api/rest/bulletin") +public class BulletinController { + + private final BulletinService service; + /** + * 普通用户查看公告详情 + * + * @param id ID + * @return 公告 + */ + @GetMapping("/self/{id}") + public BulletinVo selfDetail(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable("id") Long id) { + return service.detail(userDetails,id, Bulletin.State.publish); + } + + /** + * 普通用户分页查询公告 + * + * @param query 查询表单 + * @return 分页数据 + */ + @DataPermission + @GetMapping("/self") + public Page getBulletins(Page page, BulletinQuery query) { + query.setState(Bulletin.State.publish); + return service.page(page, query.wrapper()); + } + + /** + * 管理查询公告详情 + * + * @param id ID + * @return 公告 + */ + @GetMapping("/{id}") + @PreAuthorize("hasAuthority('message:bulletin:query')") + public BulletinVo detail(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable("id") Long id) { + return service.detail(userDetails,id, null); + } + + /** + * 管理员分页查询公告 + * + * @param query 查询参数 + * @return 分页数据 + */ + @DataPermission + @GetMapping + @PreAuthorize("hasAuthority('message:bulletin:query')") + public Page query(Page page, BulletinQuery query) { + return service.page(page, query.wrapper()); + } + + /** + * 创建公告 + * + * @param userDetails 操作用户 + * @param dto 表单数据 + * @return 公告 + */ + @PostMapping + @PreAuthorize("hasAuthority('message:bulletin:create')") + public Bulletin create(@AuthenticationPrincipal UserDetailsImpl userDetails, @RequestBody BulletinDto dto) { + return service.create(userDetails, dto); + } + + /** + * 更新公告,只能修改"编辑中"或"已发布"的公告,"已发布"的公告修改后会改为"编辑中" + * + * @param userDetails 操作用户 + * @param dto 表单数据 + * @param id ID + * @return 公告 + */ + @PatchMapping("/{id}") + @PreAuthorize("hasAuthority('message:bulletin:update')") + public Boolean update(@AuthenticationPrincipal UserDetailsImpl userDetails, @RequestBody BulletinDto dto, @PathVariable("id") Long id) { + return service.update(userDetails, dto, id); + } + + /** + * 切换公告置顶状态 + * + * @param id ID + * @return 公告 + */ + @PatchMapping("/{id}/toggle-top") + @PreAuthorize("hasAuthority('message:bulletin:update')") + public Boolean toggleTop(@PathVariable("id") Long id) { + return service.toggleTop(id); + } + + /** + * 发布公告,只能发布"编辑中"的公告 + * + * @param userDetails 操作用户 + * @param ids IDs + * @return 公告 + */ + @PatchMapping("/publish") + @PreAuthorize("hasAuthority('message:bulletin:update')") + public Boolean publish(@AuthenticationPrincipal UserDetailsImpl userDetails, @RequestBody List ids) { + return service.publish(userDetails, ids); + } + + /** + * 关闭公告,只能关闭"已发布"的公告 + * + * @param userDetails 操作用户 + * @param id ID + * @return 公告 + */ + @PatchMapping("/{id}/toggleClose") + @PreAuthorize("hasAuthority('message:bulletin:update')") + public Boolean toggleClose(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable("id") Long id) { + return service.close(userDetails, id); + } + + /** + * 删除公告,只能删除"编辑中"的公告 + * + * @param id ID + * @return 公 + */ + @DeleteMapping("/{id}") + @PreAuthorize("hasAuthority('message:bulletin:delete')") + public Boolean delete(@PathVariable("id") Long id) { + return service.delete(id); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/controller/UserNoticeController.java b/src/main/java/com/zsc/edu/dify/modules/message/controller/UserNoticeController.java new file mode 100644 index 0000000..ae51dbf --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/controller/UserNoticeController.java @@ -0,0 +1,128 @@ +package com.zsc.edu.dify.modules.message.controller; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.framework.mybatisplus.DataPermission; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.UserNoticeDto; +import com.zsc.edu.dify.modules.message.query.AdminNoticeQuery; +import com.zsc.edu.dify.modules.message.query.UserNoticeQuery; +import com.zsc.edu.dify.modules.message.service.UserNoticeService; +import com.zsc.edu.dify.modules.message.vo.AdminNoticeVo; +import com.zsc.edu.dify.modules.message.vo.UserNoticeVo; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 用户消息Controller + * + * @author harry_yao + */ +@AllArgsConstructor +@RestController +@RequestMapping("api/rest/notice") +public class UserNoticeController { + + private final UserNoticeService service; + + /** + * 普通用户查看消息详情 + * + * @param userDetails 操作用户 + * @param noticeId 消息ID + * @return 用户消息详情 + */ + @GetMapping("/self/{noticeId}") + public UserNoticeVo selfDetail(@AuthenticationPrincipal UserDetailsImpl userDetails, @PathVariable("noticeId") Long noticeId) { + return service.detail(noticeId, userDetails.getId()); + } + + /** + * 普通用户分页查询消息,不能设置查询参数userId和username + * + * @param userDetails 操作用户 + * @param query 查询参数 + * @return 分页数据 + */ + @GetMapping("/self") + public IPage selfPage(Page pageParam, @AuthenticationPrincipal UserDetailsImpl userDetails, UserNoticeQuery query) { + query.userId = userDetails.id; + query.name = null; + return service.page(pageParam, query); + } + + /** + * 普通用户统计自己未读消息数量 + * + * @param userDetails 操作用户 + * @return 数据 + */ + @GetMapping("/countUnread") + public int countUnread(@AuthenticationPrincipal UserDetailsImpl userDetails) { + return service.countUnread(userDetails); + } + + /** + * 普通用户确认消息已读,如果提交的已读消息ID集合为空,则将所有未读消息设为已读 + * + * @param userDetails 操作用户 + * @param noticeIds 已读消息ID集合 + * @return 确认已读数量 + */ + @PatchMapping("/read") + public Boolean acknowledge(@AuthenticationPrincipal UserDetailsImpl userDetails, @RequestBody List noticeIds) { + return service.markAsRead(userDetails, noticeIds); + } + + /** + * 普通用户确认消息已读,如果提交的已读消息ID集合为空,则将所有未读消息设为已读 + * + * @param userDetails 操作用户 + * @return 确认已读数量 + */ + @PatchMapping("/readAll") + public Boolean readAll(@AuthenticationPrincipal UserDetailsImpl userDetails) { + return service.markAllAsRead(userDetails); + } + + /** + * 管理查询消息详情 + * + * @param noticeId 消息ID + * @return 用户消息详情 + */ + @GetMapping("/{userId}/{noticeId}") + @PreAuthorize("hasAuthority('message:notice:query')") + public UserNoticeVo detail(@PathVariable("userId") Long userId, @PathVariable("noticeId") Long noticeId) { + return service.detail(noticeId, userId); + } + + /** + * 管理员分页查询消息 + * + * @param query 查询参数 + * @return 分页数据 + */ + @DataPermission(tableAlias = "su") + @GetMapping + @PreAuthorize("hasAuthority('message:notice:query')") + public IPage page(Page page, AdminNoticeQuery query) { + return service.getAdminNoticePage(page, query); + } + + /** + * 管理员手动创建消息 + * + * @param dto 表单数据 + * @return 消息列表 + */ + @PostMapping + @PreAuthorize("hasAuthority('message:notice:create')") + public Boolean create(@RequestBody UserNoticeDto dto) { + return service.createByAdmin(dto); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/dto/BulletinAttachmentDto.java b/src/main/java/com/zsc/edu/dify/modules/message/dto/BulletinAttachmentDto.java new file mode 100644 index 0000000..ecf9889 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/dto/BulletinAttachmentDto.java @@ -0,0 +1,18 @@ +package com.zsc.edu.dify.modules.message.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BulletinAttachmentDto { + + private Long bulletinId; + + private String attachmentId; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/dto/BulletinDto.java b/src/main/java/com/zsc/edu/dify/modules/message/dto/BulletinDto.java new file mode 100644 index 0000000..61d65b5 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/dto/BulletinDto.java @@ -0,0 +1,44 @@ +package com.zsc.edu.dify.modules.message.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; + +import java.util.List; + +/** + * @author harry_yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class BulletinDto { + + /** + * 标题 + */ + @NotBlank(message = "接收用户不能为空") + public String title; + + /** + * 是否置顶 + */ + public Boolean top; + + /** + * 内容 + */ + @NotBlank(message = "消息内容不能为空") + public String content; + /** + * 备注 + */ + public String remark; + + /** + * 附件ID集合 + */ + private List attachmentIds; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/dto/PageDto.java b/src/main/java/com/zsc/edu/dify/modules/message/dto/PageDto.java new file mode 100644 index 0000000..cfa36fa --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/dto/PageDto.java @@ -0,0 +1,24 @@ +package com.zsc.edu.dify.modules.message.dto; + +import lombok.Data; + +import java.util.List; + +/** + * @author zhuang + */ +@Data +public class PageDto { + /** + * 总条数 + */ + private Long total; + /** + * 总页数 + */ + private Integer pages; + /** + * 集合 + */ + private List list; +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/message/dto/UserNoticeDto.java b/src/main/java/com/zsc/edu/dify/modules/message/dto/UserNoticeDto.java new file mode 100644 index 0000000..48ef75b --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/dto/UserNoticeDto.java @@ -0,0 +1,61 @@ +package com.zsc.edu.dify.modules.message.dto; + +import com.zsc.edu.dify.modules.message.entity.NoticeType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import java.util.Set; + +/** + * @author harry_yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserNoticeDto { + + /** + * 用户ID集合 + */ + @NotEmpty(message = "接收用户不能为空") + public Set userIds; + + /** + * 消息类型 + */ + @NotNull(message = "消息类型不能为空") + public NoticeType type; + + /** + * 是否需要发送邮件 + */ + public Boolean email; + + /** + * 是否需要发送短信 + */ + public Boolean sms; + + /** + * 消息内容是否富文本,True则以富文本形式发送 + */ + public Boolean html; + + /** + * 消息标题 + */ + @NotBlank(message = "消息标题不能为空") + public String title; + + /** + * 消息内容 + */ + @NotBlank(message = "消息内容不能为空") + public String content; + + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/entity/Bulletin.java b/src/main/java/com/zsc/edu/dify/modules/message/entity/Bulletin.java new file mode 100644 index 0000000..fae6233 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/entity/Bulletin.java @@ -0,0 +1,135 @@ +package com.zsc.edu.dify.modules.message.entity; + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zsc.edu.dify.common.enums.IState; +import com.zsc.edu.dify.modules.system.entity.BaseEntity; +import com.zsc.edu.dify.modules.attachment.entity.Attachment; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * 系统公告Domain + * + * @author zhuang + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@TableName("sys_bulletin") +public class Bulletin extends BaseEntity { + + /** + * 标题 + */ + public String title; + + /** + * 状态 + */ + public State state = State.edit; + + /** + * 部门ID(权限) + */ + public Long deptId; + + /** + * 是否置顶 + */ + public Boolean top; + + /** + * 编辑者ID + */ + public Long editUserId; + + /** + * 编辑者 + */ + @TableField(exist = false) + public String editUsername; + + /** + * 编辑时间 + */ + public LocalDateTime editTime; + + /** + * 审核发布者ID + */ + public Long publishUserId; + + /** + * 审核发布者 + */ + @TableField(exist = false) + public String publishUsername; + + /** + * 审核发布时间 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime publishTime; + + /** + * 关闭者ID + */ + public Long closeUserId; + + /** + * 关闭者 + */ + @TableField(exist = false) + public String closeUsername; + + /** + * 关闭时间 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime closeTime; + + /** + * 内容 + */ + public String content; + + /** + * 已读状态 + */ + @TableField(exist = false) + public Boolean isRead; + + /** + * 附件列表 + */ + @TableField(exist = false) + public List attachments; + + public enum State implements IEnum, IState { + edit(1,"编辑中"), + publish(2,"已发布"), + close(3,"已关闭"); + + private final Integer value; + private final String name; + + State(int value, String name) { + this.value=value; + this.name = name; + } + @Override + public Integer getValue() { + return this.value; + } + @Override + public String toString() { + return this.name; + } + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/entity/BulletinAttachment.java b/src/main/java/com/zsc/edu/dify/modules/message/entity/BulletinAttachment.java new file mode 100644 index 0000000..0922962 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/entity/BulletinAttachment.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.modules.message.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * @author zhuang + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@TableName("sys_bulletin_attach") +public class BulletinAttachment { + + private Long bulletinId; + + private String attachmentId; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/entity/Notice.java b/src/main/java/com/zsc/edu/dify/modules/message/entity/Notice.java new file mode 100644 index 0000000..f7e2c99 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/entity/Notice.java @@ -0,0 +1,62 @@ +package com.zsc.edu.dify.modules.message.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import com.zsc.edu.dify.modules.system.entity.BaseEntity; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +/** + * 消息 + * + * @author harry_yao + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@TableName("sys_notice") +public class Notice extends BaseEntity { + + /** + * 消息类型 + */ + public NoticeType type = NoticeType.MESSAGE; + + /** + * 是否系统生成 + */ + public Boolean system = false; + + /** + * 是否需要发送邮件 + */ + public Boolean email; + + + /** + * 是否需要发送短信 + */ + public Boolean sms; + + /** + * 消息内容是否富文本,True则以富文本形式发送 + */ + public Boolean html; + + /** + * 标题 + */ + public String title; + + /** + * 消息内容 + */ + public String content; + + /** + * 部门ID(权限) + */ + public Long deptId; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/entity/NoticePayload.java b/src/main/java/com/zsc/edu/dify/modules/message/entity/NoticePayload.java new file mode 100644 index 0000000..cd15409 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/entity/NoticePayload.java @@ -0,0 +1,39 @@ +package com.zsc.edu.dify.modules.message.entity; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * 消息内容 + * + * @author harry_yao + */ +public abstract class NoticePayload { + + public NoticeType type; + public String content; + public Boolean html; + + public static class Other extends NoticePayload { + public Other(String content) { + this.content = content; + this.type = NoticeType.MESSAGE; + } + } + + public static class ResetPassword extends NoticePayload { + public String username; + public String password; + public LocalDateTime resetTime; + + public ResetPassword(String username, String password, LocalDateTime resetTime) { + this.username = username; + this.password = password; + this.resetTime = resetTime; + this.type = NoticeType.NOTICE; + this.content = String.format("尊敬的用户%s,您的密码已于%s被管理员重置,新密码为%s," + + "请及时登录系统修改密码!", username, resetTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), password); + } + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/entity/NoticeType.java b/src/main/java/com/zsc/edu/dify/modules/message/entity/NoticeType.java new file mode 100644 index 0000000..8fb7c07 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/entity/NoticeType.java @@ -0,0 +1,32 @@ +package com.zsc.edu.dify.modules.message.entity; + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.zsc.edu.dify.common.enums.IState; + +/** + * 消息类型 + * + * @author zhuang + */ +public enum NoticeType implements IEnum, IState { + MESSAGE(1, "消息"), + NOTICE(2, "通知"); + + private final Integer value; + private final String name; + + NoticeType(Integer value, String name) { + this.value = value; + this.name = name; + } + + @Override + public Integer getValue() { + return value; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/entity/UserNotice.java b/src/main/java/com/zsc/edu/dify/modules/message/entity/UserNotice.java new file mode 100644 index 0000000..8fd37e3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/entity/UserNotice.java @@ -0,0 +1,49 @@ +package com.zsc.edu.dify.modules.message.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户消息 + * + * @author harry_yao + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@TableName("sys_user_notice") +public class UserNotice implements Serializable { + + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 用户ID + */ + public Long userId; + + + /** + * 消息ID + */ + public Long noticeId; + + + /** + * 是否已读 + */ + public Boolean isRead; + + /** + * 阅读时间 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime readTime; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/mapper/BulletinAttachmentMapper.java b/src/main/java/com/zsc/edu/dify/modules/message/mapper/BulletinAttachmentMapper.java new file mode 100644 index 0000000..75bcf83 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/mapper/BulletinAttachmentMapper.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.modules.message.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.message.dto.BulletinAttachmentDto; +import com.zsc.edu.dify.modules.message.entity.BulletinAttachment; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author zhuang + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface BulletinAttachmentMapper extends BaseMapper { + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/mapper/BulletinMapper.java b/src/main/java/com/zsc/edu/dify/modules/message/mapper/BulletinMapper.java new file mode 100644 index 0000000..e425c0d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/mapper/BulletinMapper.java @@ -0,0 +1,14 @@ +package com.zsc.edu.dify.modules.message.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.message.dto.BulletinDto; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author zhuang + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface BulletinMapper extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/mapper/NoticeMapper.java b/src/main/java/com/zsc/edu/dify/modules/message/mapper/NoticeMapper.java new file mode 100644 index 0000000..503ae3e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/mapper/NoticeMapper.java @@ -0,0 +1,14 @@ +package com.zsc.edu.dify.modules.message.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.message.dto.UserNoticeDto; +import com.zsc.edu.dify.modules.message.entity.Notice; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author zhuang + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface NoticeMapper extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/query/AdminNoticeQuery.java b/src/main/java/com/zsc/edu/dify/modules/message/query/AdminNoticeQuery.java new file mode 100644 index 0000000..ff20071 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/query/AdminNoticeQuery.java @@ -0,0 +1,39 @@ +package com.zsc.edu.dify.modules.message.query; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AdminNoticeQuery { + /** + * 用户ID + */ + public Long userId; + + /** + * 标题,模糊查询 + */ + public String title; + + /** + * 消息创建时间区间起始 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime createAtBegin; + + /** + * 消息创建时间区间终止 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime createAtEnd; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/query/BulletinQuery.java b/src/main/java/com/zsc/edu/dify/modules/message/query/BulletinQuery.java new file mode 100644 index 0000000..3116683 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/query/BulletinQuery.java @@ -0,0 +1,41 @@ +package com.zsc.edu.dify.modules.message.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +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; + +/** + * 系统公告Query + * + * @author harry_yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor + +public class BulletinQuery { + + private String title; + private Bulletin.State state; + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime publishTimeBegin; + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + private LocalDateTime publishTimeEnd; + + public LambdaQueryWrapper wrapper() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(StringUtils.hasText(this.title), Bulletin::getTitle, this.title); + queryWrapper.eq(Objects.nonNull(this.state), Bulletin::getState, this.state); + if (publishTimeBegin != null && publishTimeEnd != null) { + queryWrapper.between(Bulletin::getPublishTime, this.publishTimeBegin, this.publishTimeEnd); + } + return queryWrapper; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/query/NoticeQuery.java b/src/main/java/com/zsc/edu/dify/modules/message/query/NoticeQuery.java new file mode 100644 index 0000000..08ff6ba --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/query/NoticeQuery.java @@ -0,0 +1,26 @@ +package com.zsc.edu.dify.modules.message.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.modules.message.entity.Notice; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +import java.util.Set; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class NoticeQuery { + Set noticeIds; + + public LambdaQueryWrapper wrapper() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StringUtils.hasText((CharSequence) this.noticeIds), Notice::getId, this.noticeIds); + return queryWrapper; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/query/UserNoticeQuery.java b/src/main/java/com/zsc/edu/dify/modules/message/query/UserNoticeQuery.java new file mode 100644 index 0000000..b52db23 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/query/UserNoticeQuery.java @@ -0,0 +1,63 @@ +package com.zsc.edu.dify.modules.message.query; + +import com.zsc.edu.dify.modules.message.entity.NoticeType; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.format.annotation.DateTimeFormat; + +import java.time.LocalDateTime; + +/** + * 用户消息Query + * + * @author harry_yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserNoticeQuery { + + /** + * 用户ID + */ + public Long userId; + + /** + * 标题,模糊查询 + */ + public String title; + + /** + * 消息类型 + */ + public NoticeType type; + + /** + * 用户名或真实姓名,用户名准确查询,姓名模糊查询 + */ + public String name; + + /** + * 是否系统自动生成 + */ + public Boolean system; + + /** + * 是否已读 + */ + public Boolean isRead; + + /** + * 消息创建时间区间起始 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime createAtBegin; + + /** + * 消息创建时间区间终止 + */ + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + public LocalDateTime createAtEnd; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/repo/BulletinAttachmentRepository.java b/src/main/java/com/zsc/edu/dify/modules/message/repo/BulletinAttachmentRepository.java new file mode 100644 index 0000000..42fa27e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/repo/BulletinAttachmentRepository.java @@ -0,0 +1,11 @@ +package com.zsc.edu.dify.modules.message.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.message.entity.BulletinAttachment; + +/** + * @author zhuang + */ +public interface BulletinAttachmentRepository extends BaseMapper { + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/repo/BulletinRepository.java b/src/main/java/com/zsc/edu/dify/modules/message/repo/BulletinRepository.java new file mode 100644 index 0000000..eeccd25 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/repo/BulletinRepository.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.modules.message.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import com.zsc.edu.dify.modules.message.query.BulletinQuery; +import com.zsc.edu.dify.modules.message.vo.BulletinVo; +import org.apache.ibatis.annotations.Param; + + +/** + * 公告Repo + * + * @author harry_yao + */ +public interface BulletinRepository extends BaseMapper { + + + BulletinVo selectByBulletinId(@Param("bulletinId") Long bulletinId); + + IPage selectPageByConditions(Page page, @Param("query") BulletinQuery query); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/repo/NoticeRepository.java b/src/main/java/com/zsc/edu/dify/modules/message/repo/NoticeRepository.java new file mode 100644 index 0000000..16768b2 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/repo/NoticeRepository.java @@ -0,0 +1,14 @@ +package com.zsc.edu.dify.modules.message.repo; + + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.message.entity.Notice; + +/** + * 消息Repo + * + * @author harry_yao + */ +public interface NoticeRepository extends BaseMapper { + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/repo/UserNoticeRepository.java b/src/main/java/com/zsc/edu/dify/modules/message/repo/UserNoticeRepository.java new file mode 100644 index 0000000..4f75014 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/repo/UserNoticeRepository.java @@ -0,0 +1,25 @@ +package com.zsc.edu.dify.modules.message.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.modules.message.entity.UserNotice; +import com.zsc.edu.dify.modules.message.query.AdminNoticeQuery; +import com.zsc.edu.dify.modules.message.query.UserNoticeQuery; +import com.zsc.edu.dify.modules.message.vo.AdminNoticeVo; +import com.zsc.edu.dify.modules.message.vo.UserNoticeVo; +import org.apache.ibatis.annotations.Param; + +/** + * 用户消息Repo + * + * @author harry_yao + */ +public interface UserNoticeRepository extends BaseMapper { + + UserNoticeVo selectByNoticeIdAndUserId(@Param("noticeId") Long noticeId, @Param("userId") Long userId); + + IPage page(Page page, @Param("query") UserNoticeQuery query); + + IPage pageAdmin(Page page, @Param("query") AdminNoticeQuery query); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/service/BulletinService.java b/src/main/java/com/zsc/edu/dify/modules/message/service/BulletinService.java new file mode 100644 index 0000000..c0eb237 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/service/BulletinService.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.modules.message.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.BulletinDto; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import com.zsc.edu.dify.modules.message.query.BulletinQuery; +import com.zsc.edu.dify.modules.message.vo.BulletinVo; + +import java.util.List; + +/** + * 系统公告Service + * + * @author harry_yao + */ + +public interface BulletinService extends IService { + + BulletinVo detail(UserDetailsImpl userDetails, Long id, Bulletin.State state); + + Bulletin create(UserDetailsImpl userDetails, BulletinDto dto); + + Boolean update(UserDetailsImpl userDetails, BulletinDto dto, Long id); + + Boolean toggleTop(Long id); + + boolean publish(UserDetailsImpl userDetails, List id); + + Boolean close(UserDetailsImpl userDetails,Long id); + + IPage selectPageByConditions(Page page, BulletinQuery query); + + Boolean delete(Long id); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/service/UserNoticeService.java b/src/main/java/com/zsc/edu/dify/modules/message/service/UserNoticeService.java new file mode 100644 index 0000000..b1a7090 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/service/UserNoticeService.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.modules.message.service; + +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.UserNoticeDto; +import com.zsc.edu.dify.modules.message.entity.UserNotice; +import com.zsc.edu.dify.modules.message.query.AdminNoticeQuery; +import com.zsc.edu.dify.modules.message.query.UserNoticeQuery; +import com.zsc.edu.dify.modules.message.vo.AdminNoticeVo; +import com.zsc.edu.dify.modules.message.vo.UserNoticeVo; + +import java.util.List; + +/** + * 用户消息Service + * + * @author harry_yao + */ +public interface UserNoticeService extends IService { + + Boolean createByAdmin(UserNoticeDto dto); + + UserNoticeVo detail(Long noticeId, Long userId); + + IPage page(Page page, UserNoticeQuery query); + + Integer countUnread(UserDetailsImpl userDetails); + + boolean markAsRead(UserDetailsImpl userDetails, List noticeIds); + + + boolean markAllAsRead(UserDetailsImpl userDetails); + + IPage getAdminNoticePage(Page page, AdminNoticeQuery query); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/service/impl/BulletinServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/message/service/impl/BulletinServiceImpl.java new file mode 100644 index 0000000..1989d42 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/service/impl/BulletinServiceImpl.java @@ -0,0 +1,201 @@ +package com.zsc.edu.dify.modules.message.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.BulletinDto; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import com.zsc.edu.dify.modules.message.entity.BulletinAttachment; +import com.zsc.edu.dify.modules.message.mapper.BulletinMapper; +import com.zsc.edu.dify.modules.message.query.BulletinQuery; +import com.zsc.edu.dify.modules.message.repo.BulletinAttachmentRepository; +import com.zsc.edu.dify.modules.message.repo.BulletinRepository; +import com.zsc.edu.dify.modules.message.service.BulletinService; +import com.zsc.edu.dify.modules.message.vo.BulletinVo; +import com.zsc.edu.dify.modules.system.repo.UserRepository; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.stream.Collectors; + +import static com.zsc.edu.dify.modules.message.entity.Bulletin.State.*; + +/** + * 系统公告Service + * + * @author harry_yao + */ +@AllArgsConstructor +@Service +public class BulletinServiceImpl extends ServiceImpl implements BulletinService { + + private final BulletinMapper mapper; + private final BulletinRepository repo; + private final UserRepository userRepository; + private final BulletinAttachmentRepository bulletinAttachmentRepository; + /** + * 查询公告详情 + * + * @param id ID + * @param state 公告状态 + * @return 公告详情 + */ + @Override + public BulletinVo detail(UserDetailsImpl userDetails, Long id, Bulletin.State state) { + BulletinVo bulletinVo = repo.selectByBulletinId(id); + if (state != null) { + bulletinVo.getState().checkStatus(state); + } + bulletinVo.setEditUsername(UserRepository.selectNameById(bulletinVo.getEditUserId())); + bulletinVo.setPublishUsername(UserRepository.selectNameById(bulletinVo.getPublishUserId())); + bulletinVo.setCloseUsername(UserRepository.selectNameById(bulletinVo.getCloseUserId())); + return bulletinVo; + } + /** + * 新建公告 + * + * @param userDetails 操作用户 + * @param dto 表单数据 + * @return 新建的公告 + */ + @Override + public Bulletin create(UserDetailsImpl userDetails, BulletinDto dto) { + boolean existsByName=count(new LambdaQueryWrapper().eq(Bulletin::getTitle,dto.getTitle())) > 0; + if(existsByName){ + throw new ConstraintException("title", dto.title, "标题已存在"); + } + Bulletin bulletin = mapper.toEntity(dto); + bulletin.setEditUserId(userDetails.getId()); + save(bulletin); + if (dto.getAttachmentIds() != null) { + insertInto(bulletin.getId(), dto.getAttachmentIds()); + } + return bulletin; + } + /** + * 修改公告,只能修改"编辑中"或"已发布"的公告 + * + * @param userDetails 操作用户 + * @param dto 表单数据 + * @return 已修改的公告 + */ + @Override + public Boolean update(UserDetailsImpl userDetails,BulletinDto dto, Long id) { + Bulletin bulletin = getById(id); + bulletin.state.checkStatus(EnumSet.of(edit, publish)); + mapper.convert(dto, bulletin); + bulletin.setCreateBy(userDetails.getName()); + bulletin.setCreateTime(LocalDateTime.now()); + bulletin.state = edit; + if (dto.getAttachmentIds() != null) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(BulletinAttachment::getBulletinId, bulletin.getId()); + bulletinAttachmentRepository.delete(queryWrapper); + insertInto(bulletin.getId(), dto.getAttachmentIds()); + } + return updateById(bulletin); + } + /** + * 批量发布公告,只能发布"编辑中"的公告 + * + * @param userDetails 操作用户 + * @param ids ids + * @return 已发布的公告 + */ + @Override + public boolean publish(UserDetailsImpl userDetails, List ids) { + if (ids == null || ids.isEmpty()) { + throw new RuntimeException("您输入的集合为空"); + } + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.in(Bulletin::getId, ids); + List bulletins = repo.selectList(queryWrapper); + boolean allEditable = true; + for (Bulletin bulletin : bulletins) { + try { + bulletin.state.checkStatus(EnumSet.of(edit)); + } catch (Exception e) { + allEditable = false; + break; + } + } + if (!allEditable) { + throw new RuntimeException("存在公告状态不是编辑中的,无法发布"); + } + return this.lambdaUpdate().in(Bulletin::getId, ids) + .eq(Bulletin::getState, edit) + .set(Bulletin::getState, publish) + .set(Bulletin::getPublishTime, LocalDateTime.now()) + .set(Bulletin::getPublishUserId, userDetails.getId()) + .update(); + } + + /** + * 切换关闭状态,只能关闭"已发布"的公告,只能开启“已关闭”的公告 + * + * @param userDetails 操作用户 + * @param id ID + * @return 已关闭的公告 + */ + @Override + public Boolean close(UserDetailsImpl userDetails, Long id) { + Bulletin bulletin = getById(id); + bulletin.top = false; + if(bulletin.state==close){ + bulletin.state = edit; + return updateById(bulletin); + } + bulletin.state.checkStatus(publish); + bulletin.state = close; + bulletin.setCloseUserId(userDetails.getId()); + bulletin.setCloseTime(LocalDateTime.now()); + return updateById(bulletin); + } + + /** + * 切换公告置顶状态 + * + * @param id ID + * @return 被更新的公告 + */ + @Override + public Boolean toggleTop(Long id) { + Bulletin bulletin = getById(id); + bulletin.top = !bulletin.top; + return updateById(bulletin); + } + + /** + *为公告添加附件 + * + * @param bulletinId bulletinId + * @param attachmentIds attachments + */ + public void insertInto(Long bulletinId, List attachmentIds) { + List bulletinAttachments = attachmentIds.stream() + .map(attachmentId -> new BulletinAttachment(bulletinId, attachmentId)) + .collect(Collectors.toList()); + bulletinAttachmentRepository.insert(bulletinAttachments); + } + + @Override + public IPage selectPageByConditions(Page page, BulletinQuery query) { + return baseMapper.selectPageByConditions(page, query); + } + + @Override + public Boolean delete(Long id) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(BulletinAttachment::getBulletinId, id); + List bulletinAttachments = bulletinAttachmentRepository.selectList(queryWrapper); + if (!bulletinAttachments.isEmpty()) { + bulletinAttachmentRepository.delete(queryWrapper); + } + return removeById(id); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/service/impl/UserNoticeServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/message/service/impl/UserNoticeServiceImpl.java new file mode 100644 index 0000000..41bd309 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/service/impl/UserNoticeServiceImpl.java @@ -0,0 +1,195 @@ +package com.zsc.edu.dify.modules.message.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.framework.message.email.EmailSender; +import com.zsc.edu.dify.framework.message.sms.SmsSender; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.UserNoticeDto; +import com.zsc.edu.dify.modules.message.entity.*; +import com.zsc.edu.dify.modules.message.mapper.NoticeMapper; +import com.zsc.edu.dify.modules.message.query.AdminNoticeQuery; +import com.zsc.edu.dify.modules.message.query.UserNoticeQuery; +import com.zsc.edu.dify.modules.message.repo.NoticeRepository; +import com.zsc.edu.dify.modules.message.repo.UserNoticeRepository; +import com.zsc.edu.dify.modules.message.service.UserNoticeService; +import com.zsc.edu.dify.modules.message.vo.AdminNoticeVo; +import com.zsc.edu.dify.modules.message.vo.UserNoticeVo; +import com.zsc.edu.dify.modules.system.entity.User; +import com.zsc.edu.dify.modules.system.repo.UserRepository; +import lombok.AllArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; + + +/** + * 用户消息Service + * + * @author harry_yao + */ +@AllArgsConstructor +@Service +public class UserNoticeServiceImpl extends ServiceImpl implements UserNoticeService { + + private final NoticeRepository noticeRepo; + private final EmailSender emailSender; + private final SmsSender smsSender; + private final UserRepository userRepository; + private final NoticeMapper noticeMapper; + + + /** + * 查询消息详情 + * + * @param noticeId 消息ID + * @param userId 用户ID + * @return 查询详情 + */ + + @Override + public UserNoticeVo detail(Long noticeId, Long userId) { + UserNoticeVo userNoticeVo = baseMapper.selectByNoticeIdAndUserId(noticeId, userId); + if (userNoticeVo == null) { + throw new RuntimeException("您输入的信息有误,请检查输入ID信息是否正确"); + } + return userNoticeVo; + } + + /** + * 分页查询用户消息 + * + * @param query 查询表单 + * @param page 分页参数 + * @return 页面数据 + */ + @Override + public IPage page(Page page, UserNoticeQuery query) { + return baseMapper.page(page, query); + } + + /** + * 统计用户未读消息数量 + * + * @param userDetails 操作用户 + * @return 未读消息数量 + */ + @Override + public Integer countUnread(UserDetailsImpl userDetails) { + LambdaQueryWrapper lambdaQueryWrapper = new LambdaQueryWrapper<>(); + lambdaQueryWrapper.eq(UserNotice::getUserId, userDetails.getId()) + .eq(UserNotice::getIsRead, true); + return Math.toIntExact(baseMapper.selectCount(lambdaQueryWrapper)); + } + + /** + * 设为已读 + * + * @param userDetails 操作用户 + * @param messageIds 消息ID集合,如为空,则将该用户的所有未读消息设为已读 + * @return 修改记录数量 + */ + @Override + public boolean markAsRead(UserDetailsImpl userDetails, List messageIds) { + if (CollectionUtils.isEmpty(messageIds)) { + throw new RuntimeException("您输入的集合为空"); + } + return this.lambdaUpdate().eq(UserNotice::getUserId, userDetails.getId()) + .in(UserNotice::getNoticeId, messageIds) + .set(UserNotice::getIsRead, true) + .set(UserNotice::getReadTime, LocalDateTime.now()) + .update(); + } + + /** + * 全部已读 + */ + @Override + public boolean markAllAsRead(UserDetailsImpl userDetails) { + return this.lambdaUpdate().eq(UserNotice::getUserId, userDetails.getId()) + .set(UserNotice::getIsRead, true) + .set(UserNotice::getReadTime, LocalDateTime.now()) + .update(); + } + + /** + * 管理员查询消息分页 + * + * @return 消息设置列表 + */ + @Override + public IPage getAdminNoticePage(Page page, AdminNoticeQuery query) { + return baseMapper.pageAdmin(page, query); + } + + + /** + * + * 管理员手动创建用户消息并发送 + * + * @param dto 表单数据 + * @return 创建的用户消息列表 + */ + @Transactional + @Override + public Boolean createByAdmin(UserNoticeDto dto) { + Set users = new HashSet<>(userRepository.selectList(new LambdaQueryWrapper().in(User::getId, dto.userIds))); + Notice notice = noticeMapper.toEntity(dto); + noticeRepo.insert(notice); + Set userNotices = users.stream() + .map(user -> new UserNotice(null, user.getId(), notice.getId(), true, null)) + .collect(Collectors.toSet()); + send(users, notice); + return saveBatch(userNotices); + } + + + /** + * 以邮件、短信等形式发送消息,只有非html格式的消息才能以短信方式发送 + * + * @param users 接收者 + * @param notice 消息 + */ + @Async + void send(Set users, Notice notice) { + if (notice.email) { + emailSender.send(users.stream().map(User::getEmail).collect(Collectors.toSet()), notice); + } + if (notice.sms && !notice.html) { + smsSender.send(users.stream().map(User::getPhone).collect(Collectors.toSet()), notice.content); + } + } + + + /** + * 系统自动创建用户消息并发送 + * + * @param receivers 接收者 + * @param payload 消息内容 + */ + @Transactional + public Boolean createBySystem(Set receivers, NoticePayload payload) { + AtomicBoolean email = new AtomicBoolean(false); + AtomicBoolean sms = new AtomicBoolean(false); + Optional.of(noticeRepo.selectById(payload.type)).ifPresent(message -> { + email.set(message.email); + sms.set(message.sms); + }); + Notice notice = new Notice(payload.type, true, email.get(), sms.get(), + payload.html, payload.type.name(), payload.content, null); + noticeRepo.insert(notice); + Set userNotices = receivers.stream().map(user -> + new UserNotice(null, user.getId(), notice.getId(), true, null)).collect(Collectors.toSet()); + send(receivers, notice); + return saveBatch(userNotices); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/vo/AdminNoticeVo.java b/src/main/java/com/zsc/edu/dify/modules/message/vo/AdminNoticeVo.java new file mode 100644 index 0000000..00b0f0f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/vo/AdminNoticeVo.java @@ -0,0 +1,55 @@ +package com.zsc.edu.dify.modules.message.vo; + +import com.zsc.edu.dify.modules.message.entity.NoticeType; +import lombok.Data; + +/** + * @author zhuang + */ +@Data +public class AdminNoticeVo { + /** + * 用户消息id + */ + private Long id; + /** + * 接收用户数 + */ + private Long userCount; + /** + * 已读用户数 + */ + private Long readCount; + /** + * 消息类型 + */ + public NoticeType type = NoticeType.MESSAGE; + /** + * 是否系统消息 + */ + public Boolean system; + /** + * 是否邮件 + */ + public Boolean email; + /** + * 是否短信 + */ + public Boolean sms; + /** + * 是否html + */ + public Boolean html; + /** + * 标题 + */ + public String title; + /** + * 内容 + */ + public String content; + /** + * 备注 + */ + private String remark; +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/message/vo/BulletinVo.java b/src/main/java/com/zsc/edu/dify/modules/message/vo/BulletinVo.java new file mode 100644 index 0000000..84945c3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/vo/BulletinVo.java @@ -0,0 +1,98 @@ +package com.zsc.edu.dify.modules.message.vo; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.zsc.edu.dify.modules.attachment.entity.Attachment; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.List; + +/** + * @author zhuang + */ +@Data +@JsonInclude +public class BulletinVo { + /** + * 公告id + */ + private Long id; + /** + * 公告标题 + */ + private String title; + /** + * 公告属性 + */ + private Bulletin.State state = Bulletin.State.edit; + /** + * 公告置顶状态 + */ + private Boolean top; + /** + * 公告编辑者ID + */ + private Long editUserId; + /** + * 公告编辑者名称 + */ + private String editUsername; + /** + * 公告编辑时间 + */ + private LocalDateTime editTime; + /** + * 公告发布者ID + */ + private Long publishUserId; + /** + * 公告发布者名称 + */ + private String publishUsername; + /** + * 公告发布时间 + */ + private LocalDateTime publishTime; + /** + * 公告关闭者ID + */ + private Long closeUserId; + /** + * 公告关闭者名称 + */ + private String closeUsername; + /** + * 公告关闭时间 + */ + private LocalDateTime closeTime; + /** + * 公告内容 + */ + private String content; + /** + * 创建时间 + */ + private LocalDateTime createTime; + /** + * 创建者 + */ + private String createBy; + /** + * 更新时间 + */ + private LocalDateTime updateTime; + /** + * 更新者 + */ + private String updateBy; + /** + * 备注 + */ + private String remark; + /** + * 附件 + */ + List attachments; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/message/vo/UserNoticeVo.java b/src/main/java/com/zsc/edu/dify/modules/message/vo/UserNoticeVo.java new file mode 100644 index 0000000..3e7a466 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/message/vo/UserNoticeVo.java @@ -0,0 +1,80 @@ +package com.zsc.edu.dify.modules.message.vo; + +import com.zsc.edu.dify.modules.message.entity.NoticeType; +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author zhuang + */ +@Data +public class UserNoticeVo { + /** + * 用户消息id + */ + private Long noticeId; + /** + * 是否已读 + */ + public Boolean isRead; + /** + * 阅读时间 + */ + public LocalDateTime readTime; + + /** + * 用户名 + */ + public String username; + + /** + * 消息类型 + */ + public NoticeType type = NoticeType.MESSAGE; + /** + * 是否系统消息 + */ + public Boolean system; + /** + * 是否邮件 + */ + public Boolean email; + /** + * 是否短信 + */ + public Boolean sms; + /** + * 是否html + */ + public Boolean html; + /** + * 标题 + */ + public String title; + /** + * 内容 + */ + public String content; + /** + * 备注 + */ + private String remark; + /** + * 创建时间 + */ + private LocalDateTime createTime; + /** + * 创建人 + */ + private String createBy; + /** + * 更新时间 + */ + private LocalDateTime updateTime; + /** + * 更新人 + */ + private String updateBy; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/controller/OperationController.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/controller/OperationController.java new file mode 100644 index 0000000..707bada --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/controller/OperationController.java @@ -0,0 +1,52 @@ +package com.zsc.edu.dify.modules.operationLog.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.modules.operationLog.entity.OperationLog; +import com.zsc.edu.dify.modules.operationLog.query.OperationLogQuery; +import com.zsc.edu.dify.modules.operationLog.repo.OperationLogRepository; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +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}") + @PreAuthorize("hasAuthority('operationLog:query')") + public OperationLog crate(@PathVariable("id") Long id) { + return repo.selectById(id); + } + + /** + * 获取操作日志分页 + */ + @GetMapping("") + @PreAuthorize("hasAuthority('operationLog:query')") + public Page query(Page page, OperationLogQuery query) { + return repo.selectPage(page, query.wrapper()); + } + + /** + * 批量删除操作日志 + */ + @DeleteMapping("/batch") + @PreAuthorize("hasAuthority('operationLog:delete')") + public int deleteBatch(@RequestBody List ids) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(OperationLog::getId, ids); + return repo.delete(wrapper); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/ExpressionRootObject.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/ExpressionRootObject.java new file mode 100644 index 0000000..2d11a44 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/ExpressionRootObject.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.modules.operationLog.entity; + +import lombok.Getter; + +@Getter +public class ExpressionRootObject { + private final Object object; + private final Object[] args; + + public ExpressionRootObject(Object object, Object[] args) { + this.object = object; + this.args = args; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/FunctionTypeEnum.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/FunctionTypeEnum.java new file mode 100644 index 0000000..162d28c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/FunctionTypeEnum.java @@ -0,0 +1,50 @@ +package com.zsc.edu.dify.modules.operationLog.entity; + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.zsc.edu.dify.common.enums.IState; +import lombok.Getter; + +/** + * @author lenovo + */ + +@Getter +public enum FunctionTypeEnum implements IEnum, IState { + create("create", "create"), + update("update", "update"), + delete("delete", "delete"), + 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(); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/ModuleTypeEnum.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/ModuleTypeEnum.java new file mode 100644 index 0000000..200b32d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/ModuleTypeEnum.java @@ -0,0 +1,66 @@ +package com.zsc.edu.dify.modules.operationLog.entity; + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.zsc.edu.dify.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, IState { + 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 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(); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/OperationLog.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/OperationLog.java new file mode 100644 index 0000000..f9169e1 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/OperationLog.java @@ -0,0 +1,44 @@ +package com.zsc.edu.dify.modules.operationLog.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.time.LocalDateTime; + +/** + * @author zhuang + */ +@Setter +@Getter +@NoArgsConstructor +@AllArgsConstructor +@TableName("operation_log") +public class OperationLog { + /** + * 主键 + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 模块类型 + */ + private ModuleTypeEnum moduleType; + + /** + * 操作类型 + */ + private FunctionTypeEnum functionType; + + /** + * 操作内容 + */ + private String content; + + /** + * 操作时间 + */ + private LocalDateTime makeTime; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/OperationLogAnnotation.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/OperationLogAnnotation.java new file mode 100644 index 0000000..296f8a8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/entity/OperationLogAnnotation.java @@ -0,0 +1,21 @@ +package com.zsc.edu.dify.modules.operationLog.entity; + +import java.lang.annotation.*; + +/** + * @author zhuang + */ +@Target({ElementType.PARAMETER, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface OperationLogAnnotation { + /** + * 日志内容,支持SpEL表达式 + */ + String content() default ""; + + /** + * 操作类型,例如:SAVE, UPDATE, DELETE + */ + String operationType() default ""; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/query/OperationLogQuery.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/query/OperationLogQuery.java new file mode 100644 index 0000000..da412ec --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/query/OperationLogQuery.java @@ -0,0 +1,41 @@ +package com.zsc.edu.dify.modules.operationLog.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.modules.operationLog.entity.OperationLog; +import com.zsc.edu.dify.modules.operationLog.entity.FunctionTypeEnum; +import com.zsc.edu.dify.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 wrapper() { + LambdaQueryWrapper 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; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/repo/OperationLogRepository.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/repo/OperationLogRepository.java new file mode 100644 index 0000000..f7fbb1c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/repo/OperationLogRepository.java @@ -0,0 +1,10 @@ +package com.zsc.edu.dify.modules.operationLog.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.operationLog.entity.OperationLog; + +/** + * @author zhuang + */ +public interface OperationLogRepository extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/util/ExpressionEvaluator.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/util/ExpressionEvaluator.java new file mode 100644 index 0000000..cd47c7a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/util/ExpressionEvaluator.java @@ -0,0 +1,41 @@ +package com.zsc.edu.dify.modules.operationLog.util; + + +import com.zsc.edu.dify.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 extends CachedExpressionEvaluator { + private final ParameterNameDiscoverer paramNameDiscoverer = (ParameterNameDiscoverer) new DefaultParameterNameDiscoverer(); + private final Map conditionCache = new ConcurrentHashMap<>(64); + private final Map 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/operationLog/util/OperationLogAspect.java b/src/main/java/com/zsc/edu/dify/modules/operationLog/util/OperationLogAspect.java new file mode 100644 index 0000000..4dd08ea --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/operationLog/util/OperationLogAspect.java @@ -0,0 +1,120 @@ +package com.zsc.edu.dify.modules.operationLog.util; + +import com.baomidou.mybatisplus.core.toolkit.StringUtils; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.operationLog.entity.FunctionTypeEnum; +import com.zsc.edu.dify.modules.operationLog.entity.ModuleTypeEnum; +import com.zsc.edu.dify.modules.operationLog.entity.OperationLog; +import com.zsc.edu.dify.modules.operationLog.entity.OperationLogAnnotation; +import com.zsc.edu.dify.modules.operationLog.repo.OperationLogRepository; +import com.zsc.edu.dify.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 EVALUATOR = new ExpressionEvaluator<>(); + + @Resource + OperationLogRepository operationLogRepository; + @Resource + UserRepository userRepository; + + /** + * 被执行方法上添加OperationLogAnnotation注解的才会执行这个方法 + */ + @AfterReturning(pointcut = "@annotation(com.zsc.edu.dify.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); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/controller/AuthorityController.java b/src/main/java/com/zsc/edu/dify/modules/system/controller/AuthorityController.java new file mode 100644 index 0000000..ca3f1cc --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/controller/AuthorityController.java @@ -0,0 +1,80 @@ +package com.zsc.edu.dify.modules.system.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.modules.system.dto.AuthorityDto; +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.query.AuthorityQuery; +import com.zsc.edu.dify.modules.system.service.AuthorityService; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +/** + * 权限Controller + * + * @author zhuang + */ +@AllArgsConstructor +@RestController +@RequestMapping("/api/rest/authority") +public class AuthorityController { + private AuthorityService service; + + /** + * 返回权限列表 hasAuthority('SYSTEM:AUTHORITY:QUERY') + * + * @param query 查询表单 + * @return 权限列表 + */ + @GetMapping + @PreAuthorize("hasAuthority('SYSTEM:AUTHORITY:QUERY')") + public Page query(AuthorityQuery query, Page page) { + return service.page(page, query.wrapper()); + } + + + /** + * 新建权限 hasAuthority('SYSTEM:AUTHORITY:CREATE') + * + * @param dto 表单数据 + * @return Authority 新建的权限 + */ + @PostMapping + @PreAuthorize("hasAuthority('SYSTEM:AUTHORITY:CREATE')") + public Authority create(@RequestBody AuthorityDto dto) { + return service.create(dto); + } + + /** + * 更新权限 hasAuthority('SYSTEM:AUTHORITY:UPDATE') + * + * @param dto 表单数据 + * @param id 权限ID + * @return Dept 更新后的权限信息 + */ + @PatchMapping("/{id}") + @PreAuthorize("hasAuthority('SYSTEM:AUTHORITY:UPDATE')") + public Boolean update(@RequestBody AuthorityDto dto, @PathVariable("id") Long id) { + return service.update(dto, id); + } + /*** + * 删除权限 hasAuthority('SYSTEM:AUTHORITY:DELETE') + * @param id 权限ID + * @return Boolean 是否删除成功 + */ + @DeleteMapping("/{id}") + @PreAuthorize("hasAuthority('SYSTEM:AUTHORITY:DELETE')") + public Boolean delete(@PathVariable("id") Long id) { + return service.removeById(id); + } + /** + * 更新权限启用状态 + * */ + @PatchMapping("/toggle/{id}") + @PreAuthorize("hasAuthority('SYSTEM:AUTHORITY:TOGGLE')") + public Boolean toggle(@PathVariable("id") Long id) { + return service.toggle(id); + } + + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/controller/DeptController.java b/src/main/java/com/zsc/edu/dify/modules/system/controller/DeptController.java new file mode 100644 index 0000000..e98070f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/controller/DeptController.java @@ -0,0 +1,109 @@ +package com.zsc.edu.dify.modules.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.framework.mybatisplus.DataPermission; +import com.zsc.edu.dify.modules.system.dto.DeptDto; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.User; +import com.zsc.edu.dify.modules.system.query.DeptQuery; +import com.zsc.edu.dify.modules.system.service.DeptService; +import com.zsc.edu.dify.modules.system.service.UserService; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * 部门Controller + * + * @author pengzheng + */ +@AllArgsConstructor +@RestController +@RequestMapping("/api/rest/dept") +public class DeptController { + + private final DeptService service; + private final UserService userService; + + /** + * 返回管理部门分页 hasAuthority('SYSTEM:DEPT:QUERY') + * + * @param query 查询表单 + * @return 部门列表 + */ + @DataPermission + @GetMapping + @PreAuthorize("hasAuthority('system:dept:query')") + public Page query(DeptQuery query, Page page) { + return service.page(page, query.wrapper()); + } + + /** + * 返回管理部门列表 hasAuthority('SYSTEM:DEPT:QUERY') + * + * @param id 指定部门id + * @return 部门列表 + */ + @GetMapping("/tree") + @PreAuthorize("hasAuthority('system:dept:query')") + public List tree(@RequestParam Long id) { + return service.listTree(id); + } + + /** + * 新建管理部门 hasAuthority('SYSTEM:DEPT:CREATE') + * + * @param dto 表单数据 + * @return Dept 新建的管理部门 + */ + @PostMapping + @PreAuthorize("hasAuthority('system:dept:create')") + public Dept create(@RequestBody DeptDto dto) { + return service.create(dto); + } + + /** + * 更新管理部门 hasAuthority('SYSTEM:DEPT:UPDATE') + * + * @param dto 表单数据 + * @param id 部门ID + * @return Dept 更新后的部门 + */ + @PatchMapping("/{id}") + @PreAuthorize("hasAuthority('system:dept:update')") + public Boolean update(@RequestBody DeptDto dto, @PathVariable("id") Long id) { + return service.edit(dto, id); + } + + /*** + * 删除管理部门 hasAuthority('SYSTEM:DEPT:DELETE') + * @param id 部门ID + * @return Boolean 是否删除成功 + */ + @DeleteMapping("/{id}") + @PreAuthorize("hasAuthority('system:dept:delete')") + public Boolean delete(@PathVariable("id") Long id) { + // 是否存在用户绑定此部门 + boolean hasUser = userService.count(new LambdaQueryWrapper().eq(User::getDeptId, id)) > 0; + if (hasUser) { + throw new ConstraintException("存在与本部门绑定的用户,请先删除用户"); + } + return service.removeById(id); + } + + /** + * 更新管理部门状态 + * */ + @PatchMapping("/toggle/{id}") + @PreAuthorize("hasAuthority('system:dept:update')") + public Boolean toggle(@PathVariable("id") Long id) { + return service.toggle(id); + } + + +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/controller/MenuController.java b/src/main/java/com/zsc/edu/dify/modules/system/controller/MenuController.java new file mode 100644 index 0000000..8a0f889 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/controller/MenuController.java @@ -0,0 +1,71 @@ +package com.zsc.edu.dify.modules.system.controller; + +import com.zsc.edu.dify.framework.mybatisplus.DataPermission; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.dto.MenuDto; +import com.zsc.edu.dify.modules.system.entity.Menu; +import com.zsc.edu.dify.modules.system.service.MenuService; +import com.zsc.edu.dify.modules.system.vo.MenuVo; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +/** + * @author zhuang + */ +@AllArgsConstructor +@RestController +@RequestMapping("/api/rest/menu") +public class MenuController { + + private final MenuService service; + + /** + * 新建菜单 + */ + @PostMapping + @PreAuthorize("hasAuthority('system:menu:create')") + public Menu create(@RequestBody MenuDto dto) { + return service.create(dto); + } + + /** + * 更新菜单 + */ + @PatchMapping("/{id}") + @PreAuthorize("hasAuthority('system:menu:update')") + public Menu update(@RequestBody MenuDto dto, @PathVariable("id") Long id) { + return service.update(dto, id); + } + + /** + * 删除菜单 + */ + @DeleteMapping("/{id}") + @PreAuthorize("hasAuthority('system:menu:delete')") + public Boolean delete(@PathVariable("id") Long id) { + return service.delete(id); + } + + /** + * 根据名字返回树 + */ + @DataPermission + @GetMapping("/tree") + @PreAuthorize("hasAuthority('system:menu:query')") + public List tree(@AuthenticationPrincipal UserDetailsImpl userDetails, @RequestParam String name) { + return service.getTree(userDetails, name); + } + + /** + * 根据ID查询菜单详情 + */ + @GetMapping("/{id}") + @PreAuthorize("hasAuthority('system:menu:query')") + public Menu detail(@PathVariable("id") Long id) { + return service.detail(id); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/controller/RoleController.java b/src/main/java/com/zsc/edu/dify/modules/system/controller/RoleController.java new file mode 100644 index 0000000..1d11872 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/controller/RoleController.java @@ -0,0 +1,102 @@ +package com.zsc.edu.dify.modules.system.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.framework.mybatisplus.DataPermission; +//import com.zsc.edu.dify.modules.system.dto.RoleAuthCreateDto; +import com.zsc.edu.dify.modules.system.dto.RoleDto; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.query.RoleQuery; +import com.zsc.edu.dify.modules.system.service.RoleService; +import com.zsc.edu.dify.modules.system.vo.RoleVo; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +/** + * 角色Controller + * + * @author harry yao + */ +@AllArgsConstructor +@RestController +@RequestMapping("/api/rest/role") +public class RoleController { + + + private final RoleService service; + + /** + * 返回所有角色列表 hasAuthority('SYSTEM:ROLE:QUERY') + * + * @return 所有角色列表 + */ + @DataPermission + @GetMapping + @PreAuthorize("hasAuthority('system:role:query')") + public Page query(RoleQuery query, Page page) { + return service.page(page, query.wrapper()); + } + + /** + * 新建角色 hasAuthority('SYSTEM:ROLE:CREATE') + * + * @param dto 表单数据 + * @return Role 新建的角色 + */ + @PostMapping + @PreAuthorize("hasAuthority('system:role:create')") + public Role create(@RequestBody RoleDto dto) { + return service.create(dto); + } + + /** + * 更新角色 hasAuthority('SYSTEM:ROLE:UPDATE') + * + * @param dto 表单数据 + * @param id ID + * @return Role 更新后的角色 + */ + @PatchMapping("{id}") + @PreAuthorize("hasAuthority('system:role:update')") + public Role update(@RequestBody RoleDto dto, @PathVariable("id") Long id) { + return service.edit(dto, id); + } + + /** + * 切换角色"启动/禁用"状态 hasAuthority('SYSTEM:ROLE:UPDATE') + * + * @param id ID + * @return Role 更新后的角色 + */ + @PatchMapping("{id}/toggle") + @PreAuthorize("hasAuthority('system:role:update')") + public Boolean toggle(@PathVariable("id") Long id) { + return service.toggle(id); + } + + /** + * 查询角色详情 hasAuthority('SYSTEM:ROLE:QUERY') + * + * @param id ID + * @return Role 角色详情 + */ + @GetMapping("{id}") + @PreAuthorize("hasAuthority('system:role:query')") + public RoleVo detail(@PathVariable Long id) { + return service.detail(id); + } + + /** + * 删除角色 hasAuthority('SYSTEM:ROLE:DELETE') + * + * @param id ID + * @return Role 更新后的角色 + */ + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('system:role:delete')") + public Boolean delete(@PathVariable Long id) { + return service.delete(id); + } + +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/controller/UserController.java b/src/main/java/com/zsc/edu/dify/modules/system/controller/UserController.java new file mode 100644 index 0000000..779ef69 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/controller/UserController.java @@ -0,0 +1,227 @@ +package com.zsc.edu.dify.modules.system.controller; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.operationLog.entity.OperationLogAnnotation; +import com.zsc.edu.dify.modules.system.dto.UserCreateDto; +import com.zsc.edu.dify.modules.system.dto.UserSelfUpdateDto; +import com.zsc.edu.dify.modules.system.dto.UserSelfUpdatePasswordDto; +import com.zsc.edu.dify.modules.system.dto.UserUpdateDto; +import com.zsc.edu.dify.modules.system.entity.*; +import com.zsc.edu.dify.modules.system.query.UserQuery; +import com.zsc.edu.dify.modules.system.service.*; +import com.zsc.edu.dify.modules.system.utils.TreeUtil; +import com.zsc.edu.dify.modules.system.vo.UserDetail; +import lombok.AllArgsConstructor; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.web.csrf.CsrfToken; +import org.springframework.web.bind.annotation.*; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * 用户Controller + * + * @author harry yao + */ +@AllArgsConstructor +@RestController +@RequestMapping("api/rest/user") +public class UserController { + + private final UserService service; + + private final RoleService roleService; + private final DeptService deptService; + private final AuthorityService authorityService; + private final MenuService menuService; + + /** + * 登录前,获取csrfToken信息 + * 登录成功后,获取principal和csrfToken信息 + * + * @param principal 认证主体 + * @param csrfToken csrf令牌 + * @return 包含csrf令牌和登录用户的认证主体信息 + */ + @RequestMapping(value = "me", method = {RequestMethod.POST, RequestMethod.GET}) + public Map me(@AuthenticationPrincipal Object principal, CsrfToken csrfToken) { + Map map = new LinkedHashMap<>(); + map.put("user", principal); + map.put("csrf", csrfToken); + return map; + } + + /** + * 切换角色 + */ + @OperationLogAnnotation(operationType = "更新角色") + @PatchMapping("/switch/{roleId}") + public UserDetailsImpl switchRole(@PathVariable Long roleId) { + return service.switchRole(roleId); + } + + /** + * 用户获取自己的信息 + * + * @param userDetails 操作用户 + * @return 用户信息 + */ + @GetMapping("self") + public UserDetail selfDetail(@AuthenticationPrincipal UserDetailsImpl userDetails) { + UserDetail userDetail = new UserDetail(); + User user = service.selfDetail(userDetails); + user.dept = deptService.getById(user.deptId); + Role role= roleService.getOne(new QueryWrapper().eq("id",user.roleId)); + userDetail.setPermissions(role.getName()); + Set authorities = authorityService.selectAuthoritiesByRoleId(role.getId()); + userDetail.setAuthorities(authorities.stream().toList()); + userDetail.setUser(user); + return userDetail; + } + + /** + * 用户更新自己的信息 + * + * @param userDetails 操作用户 + * @param dto 表单数据 + * @return 更新后的用户信息 + */ + @OperationLogAnnotation(content = "信息", operationType = "更新") + @PatchMapping("self") + public Boolean selfUpdate( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @RequestBody UserSelfUpdateDto dto + ) { + return service.selfUpdate(userDetails, dto); + } + + /** + * 用户更新自己的密码 + * + * @param userDetails 操作用户 + * @param dto 表单数据 + */ + + @OperationLogAnnotation(content = "'密码'", operationType = "更新") + @PatchMapping("self/update-password") + public Boolean selfUpdatePassword( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @RequestBody UserSelfUpdatePasswordDto dto + ) { + return service.selfUpdatePassword(userDetails, dto); + } + + /** + * 分页查询用户信息 hasAuthority('SYSTEM:USER:QUERY') + * + * @param query 查询表单 + * @param page 分页 + * @return 分页用户信息 + */ + @GetMapping() + @PreAuthorize("hasAuthority('system:user:query')") + public Page query(Long deptId, UserQuery query, Page page) { + if (deptId != null) { + List deptList = TreeUtil.flat(deptService.listTree(deptId), Dept::getChildren, d -> d.setChildren(null)); + List deptIds = deptList.stream().map(Dept::getId).collect(Collectors.toList()); + query.setDeptIds(deptIds); + } + return service.page(page, query.wrapper()); + } + + + + /** + * 新建用户 hasAuthority('SYSTEM:USER:CREATE') + * + * @param dto 表单数据 + * @return 新建的用户信息 + */ + + @OperationLogAnnotation(operationType = "新建") + @PostMapping + @PreAuthorize("hasAuthority('system:user:create')") + public Boolean create(@RequestBody UserCreateDto dto) { + return service.create(dto); + } + + /** + * 更新用户 hasAuthority('SYSTEM:USER:UPDATE') + * + * @param dto 表单数据 + * @param id ID + * @return 更新后的用户 + */ + @OperationLogAnnotation(operationType = "更新") + @PatchMapping("{id}") + @PreAuthorize("hasAuthority('system:user:update')") + public Boolean update(@RequestBody UserUpdateDto dto, @PathVariable("id") Long id) { + return service.update(dto, id); + } + + /** + * 更新用户密码 hasAuthority('SYSTEM:USER:UPDATE') + * + * @param id ID + * @param password 新密码 + */ + @OperationLogAnnotation(content = "'密码'", operationType = "更新") + @PatchMapping("{id}/update-password") + @PreAuthorize("hasAuthority('system:user:update')") + public Boolean updatePassword(@PathVariable("id") Long id, @RequestParam String password) { + return service.updatePassword(password, id); + } + + /** + * 切换用户"启动/禁用"状态 hasAuthority('SYSTEM:USER:DELETE') + * + * @param id ID + * @return Dept 更新后的用户 + */ + @PatchMapping("{id}/toggle") + @PreAuthorize("hasAuthority('system:user:delete')") + public Boolean toggle(@PathVariable("id") Long id) { + return service.toggle(id); + } + /** + * 删除用户 hasAuthority('SYSTEM:USER:DELETE') + * */ + @OperationLogAnnotation(operationType = "删除") + @DeleteMapping("{id}") + @PreAuthorize("hasAuthority('system:user:delete')") + public Boolean delete(@PathVariable("id") Long id) { + return service.removeById(id); + } + /** + * 根据部门ID查询用户 + * */ + @GetMapping("dept/{id}") + public Collection listByDept(@PathVariable("id") Long id) { + return service.list(new LambdaQueryWrapper().eq(User::getDeptId, id)); + } + + /** + * 根据ID查询用户 + * */ + @GetMapping("{id}") + public User detail(@PathVariable("id") Long id) { + return service.getById(id); + } + + /** + * 发送邮件 + * */ + @GetMapping("send-email") + public String sendEmail(@RequestParam String email) { + return service.sendEmail(email); + } + +} + + + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/AuthorityCreateDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/AuthorityCreateDto.java new file mode 100644 index 0000000..7aaa356 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/AuthorityCreateDto.java @@ -0,0 +1,18 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthorityCreateDto { + /** + * 权限名 + */ + private String name; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/AuthorityDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/AuthorityDto.java new file mode 100644 index 0000000..4272a85 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/AuthorityDto.java @@ -0,0 +1,39 @@ +package com.zsc.edu.dify.modules.system.dto; + +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.zsc.edu.dify.modules.system.entity.Authority; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +/** + * 权限Dto + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthorityDto { + /** + * 权限名 + */ + @NotBlank(message = "名字不能为空") + public String name; + /** + * 启用状态 + */ + private Boolean enabled = true; + /** + * 备注 + */ + private String remark; + + public LambdaUpdateWrapper updateWrapper(Long id) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + return updateWrapper.eq(Authority::getId, id) + .set(Authority::getName, name) + .set(StringUtils.hasText(remark), Authority::getRemark, remark); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/DeptDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/DeptDto.java new file mode 100644 index 0000000..2fa5afa --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/DeptDto.java @@ -0,0 +1,54 @@ +package com.zsc.edu.dify.modules.system.dto; + +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +/** + * 部门Dto + * + * @author harry_yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeptDto { + + /** + * 编码 + */ +// @NotBlank(message = "编码不能为空") +// public String code; + + /** + * 名称 + */ + @NotBlank(message = "名字不能为空") + public String name; + + /** + * 备注 + */ + public String remark; + + /** + * 父部门ID + */ + @NotNull(message = "上级公司不能为空") + public Long pid; + + public LambdaUpdateWrapper updateWrapper(Long id) { + LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); + return updateWrapper.eq(Dept::getId, id) + .set(Dept::getName, name) + .set(StringUtils.hasText(remark), Dept::getRemark, remark); + } + +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/MenuDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/MenuDto.java new file mode 100644 index 0000000..28c9beb --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/MenuDto.java @@ -0,0 +1,58 @@ +package com.zsc.edu.dify.modules.system.dto; + +import com.zsc.edu.dify.modules.system.entity.Menu; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class MenuDto { + /** + * 父菜单ID + */ + private Long pid = null; + /** + * 菜单类型 + */ + private Menu.Type type; + /** + * 名称 + */ + @NotBlank(message = "名字不能为空") + private String name; + /** + * 路径 + */ + private String path; + /** + * 应用区域 + */ + private String locale; + /** + * 标注 + */ + private String icon; + /** + * 是否需要认证 + */ + private Boolean requiresAuth; + /** + * 是否隐藏 + */ + private Boolean hideInMenu; + /** + * 菜单排序 + */ + private Integer menuOrder; + /** + * 权限 + */ + @NotBlank(message = "权限不能为空") + private String permissions; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleAuthCreateDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleAuthCreateDto.java new file mode 100644 index 0000000..6aa6304 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleAuthCreateDto.java @@ -0,0 +1,22 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RoleAuthCreateDto { + /** + * 角色名 + */ + private String roleName; + /** + * 权限名 + */ + private String authName; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleAuthorityDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleAuthorityDto.java new file mode 100644 index 0000000..4924c5f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleAuthorityDto.java @@ -0,0 +1,22 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RoleAuthorityDto { + /** + * 权限ID + */ + private Long roleId; + /** + * 用户ID + */ + private Long authorityId; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleDto.java new file mode 100644 index 0000000..b3f3965 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/RoleDto.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; +import java.util.Set; + +/** + * 角色Dto + * + * @author harry yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RoleDto { + + /** + * 名称 + */ + @NotBlank(message = "名称不能为空") + public String name; + + /** + * 备注 + */ + public String remark; + + /** + * 权限列表 + */ + public Set menuIds; + + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/UserCreateDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserCreateDto.java new file mode 100644 index 0000000..04e72a0 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserCreateDto.java @@ -0,0 +1,88 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.*; + +import java.util.List; + +/** + * 用户新建Dto + * + * @author harry yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserCreateDto { + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + public String username; + + /** + * 密码 + */ + @NotBlank(message = "密码不能为空") + public String password; + + /** + * 手机号 + */ + @Pattern(regexp = "^1[34578]\\d{9}$", message = "手机号格式不对") + public String phone; + + /** + * 邮箱 + */ + @Email(message = "电子邮箱格式不对") + public String email; + + /** + * 启用状态 + */ + @NotNull(message = "启用状态不能为空") + public Boolean enable; + + /** + * 部门ID + */ + @NotNull(message = "部门不能为空") + public Long deptId; + + /** + * 用户当前身份 + */ + public Long roleId; + /** + * 昵称 + * */ + public String nickName; + /** + * 头像 + * */ + public String avatar; + /** + * 地址 + * */ + public String address; + + /** + * 备注说明 + */ + public String remark; + /** + * 验证码 + */ + public Integer code; + + /** + * 用户角色id集合 + */ + @NotEmpty(message = "角色集合不能为空") + public List roleIds; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/UserSelfUpdateDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserSelfUpdateDto.java new file mode 100644 index 0000000..3d78a46 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserSelfUpdateDto.java @@ -0,0 +1,45 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.Pattern; + +/** + * 用户更新自己信息Dto + * + * @author harry yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserSelfUpdateDto { + + /** + * 手机号 + */ + @Pattern(regexp = "^1[34578]\\d{9}$", message = "手机号格式不对") + public String phone; + + /** + * 邮箱 + */ + @Email(message = "电子邮箱格式不对") + public String email; + /** + * 昵称 + * */ + public String nickName; + /** + * 头像 + * */ + public String avatar; + /** + * 地址 + * */ + public String address; + + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/UserSelfUpdatePasswordDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserSelfUpdatePasswordDto.java new file mode 100644 index 0000000..ba90a76 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserSelfUpdatePasswordDto.java @@ -0,0 +1,30 @@ +package com.zsc.edu.dify.modules.system.dto; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.NotBlank; + +/** + * 用户更新自己密码Dto + * + * @author harry yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserSelfUpdatePasswordDto { + + /** + * 旧密码 + */ + @NotBlank(message = "旧密码不能为空") + public String oldPassword; + + /** + * 新密码 + */ + @NotBlank(message = "新密码不能为空") + public String password; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/dto/UserUpdateDto.java b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserUpdateDto.java new file mode 100644 index 0000000..e16d1ec --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/dto/UserUpdateDto.java @@ -0,0 +1,70 @@ +package com.zsc.edu.dify.modules.system.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; + +import java.util.List; + +/** + * 用户更新Dto + * + * @author harry yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserUpdateDto { + + /** + * 手机号 + */ + @Pattern(regexp = "^1[34578]\\d{9}$", message = "手机号格式不对") + public String phone; + + /** + * 邮箱 + */ + @Email(message = "电子邮箱格式不对") + public String email; + + /** + * 启用状态 + */ + @NotNull(message = "启用状态不能为空") + public Boolean enable; + + /** + * 部门ID + */ + @NotNull(message = "部门不能为空") + public Long deptId; + /** + * 昵称 + * */ + public String nickName; + /** + * 头像 + * */ + public String avatar; + /** + * 地址 + * */ + public String address; + + /** + * 用户身份 + */ + public Long roleId; + + public String remark; + + @NotEmpty(message = "角色集合不能为空") + public List roleIds; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/Authority.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/Authority.java new file mode 100644 index 0000000..57716a8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/Authority.java @@ -0,0 +1,33 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +/** + * 操作权限 + * + * @author harry yao + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@TableName("sys_authority") +public class Authority extends BaseEntity { + /** + * 权限名 + */ + public String name; + /** + * 启用状态 + */ + private Boolean enabled = true; + /** + * 部门ID(权限) + */ + public Long deptId; + + + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/BaseEntity.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/BaseEntity.java new file mode 100644 index 0000000..e14da6e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/BaseEntity.java @@ -0,0 +1,75 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * @author harry yao + */ +@Setter +@Getter +public class BaseEntity implements Serializable { + + /** + * 自增主键 + */ + @TableId(type = IdType.AUTO) + public Long id; + + /** + * 备注说明 + */ + public String remark; + + /** + * 创建者ID + */ + @TableField(value = "create_id", fill = FieldFill.INSERT) + public Long createId; + + /** + * 创建时间 + */ + @TableField(value = "create_time", fill = FieldFill.INSERT) + public LocalDateTime createTime; + /* + * 创建人 + * */ + @TableField(value = "create_by", fill = FieldFill.INSERT) + public String createBy; + + /** + * 更新时间 + */ + @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE) + public LocalDateTime updateTime; + /* + * 更新人 + * + * */ + @TableField(value = "update_by", fill = FieldFill.INSERT_UPDATE) + public String updateBy; + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BaseEntity that = (BaseEntity) o; + return id.equals(that.id); + } + + @Override + public int hashCode() { + return getClass().getName().hashCode() + id.hashCode(); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/Dept.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/Dept.java new file mode 100644 index 0000000..f2bb69c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/Dept.java @@ -0,0 +1,47 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * 部门 + * + * @author Yao + */ +@Getter +@Setter +@TableName("sys_dept") +public class Dept extends BaseEntity { + /** + * 上级部门 + */ + private Long pid; + + /** + * 子部门数目 + */ + private Integer subCount = 0; + + /** + * 名称 + */ + private String name; + + /** + * 排序 + */ + private Integer deptSort = 1; + + /** + * 状态 + */ + private Boolean enabled = true; + + @TableField(exist = false) + public List children = null; + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/Menu.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/Menu.java new file mode 100644 index 0000000..95c0c31 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/Menu.java @@ -0,0 +1,63 @@ +package com.zsc.edu.dify.modules.system.entity; + + +import com.baomidou.mybatisplus.annotation.IEnum; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zsc.edu.dify.common.enums.IState; +import lombok.*; + +/** + * 操作权限 + * + * @author harry yao + */ +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +@TableName("sys_menu") +public class Menu extends BaseEntity { + + private Long pid; + private Type type; + private String name; + private String path; + private String locale; + private String icon; + private Boolean requiresAuth; + private Boolean hideInMenu; + private Integer menuOrder; + private String permissions; + private String authority; + + public enum Type implements IEnum, IState { + /** + * 页面 + */ + PAGE(1, "页面"), + /** + * 操作 + */ + OPERATION(2, "操作"); + + private final Integer value; + private final String description; + + Type(int value, String description) { + this.value = value; + this.description = description; + } + + @Override + public Integer getValue() { + return this.value; + } + + @Override + public String toString() { + return this.description; + } + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/Role.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/Role.java new file mode 100644 index 0000000..b0598a8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/Role.java @@ -0,0 +1,50 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.zsc.edu.dify.framework.mybatisplus.DataScopeType; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; + +import java.util.Set; + +/** + * 角色 + * + * @author harry yao + */ +@Getter +@Setter +@EqualsAndHashCode(callSuper = false) +@TableName("sys_role") +public class Role extends BaseEntity { + + /** + * 名称,唯一 + */ + public String name; + /** + * 数据权限 + */ + public DataScopeType dataScope; + + /** + * 启用状态 + */ + private Boolean enabled = true; + + /** + * 部门ID(权限) + */ + public Long deptId; + + /** + * 权限集合 + */ + @TableField(exist = false) + public Set authorities; + + +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/RoleAuthority.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/RoleAuthority.java new file mode 100644 index 0000000..7062345 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/RoleAuthority.java @@ -0,0 +1,29 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.*; + +import java.io.Serializable; + +/** + * sys_role_authorities + * @author zhuang + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@TableName("sys_role_authorities") +public class RoleAuthority implements Serializable { + /** + * 角色ID + */ + private Long roleId; + /** + * 权限ID + */ + private Long authorityId; + + // @TableField(exist = false) +// private Set authorities; +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/RoleMenu.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/RoleMenu.java new file mode 100644 index 0000000..910edda --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/RoleMenu.java @@ -0,0 +1,30 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +import java.io.Serializable; + +/** + * sys_role_menu + * @author zhuang + */ +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@TableName("sys_role_menu") +public class RoleMenu implements Serializable { + /** + * 角色ID + */ + private Long roleId; + /** + * 权限ID + */ + private Long menuId; + +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/User.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/User.java new file mode 100644 index 0000000..35c2e37 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/User.java @@ -0,0 +1,98 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.*; + +import java.util.List; +import java.util.Set; + +/** + * 用户 + * + * @author harry yao + */ +@AllArgsConstructor +@NoArgsConstructor +@Setter +@Getter +@JsonIgnoreProperties({"password"}) +@EqualsAndHashCode(callSuper = false) +@TableName(value = "sys_user") +public class User extends BaseEntity { + + /** + * 用户名 + */ + public String username; + + /** + * 密码 + */ + public String password; + + /** + * 手机号码 + */ + public String phone; + + /** + * 电子邮件 + */ + public String email; + + /** + * 启用状态 + */ + public Boolean enableState = true; + /** + * + *昵称 + * */ + public String name; + + /** + * 所属部门ID + */ + public Long deptId; + + /** + * 所属部门 + */ + @TableField(exist = false) + public Dept dept; + /** + * 所属部门及子部门ID + */ + @TableField(exist = false) + public Set dataScopeDeptIds; + + /** + * 角色ID + */ + public Long roleId; + + /** + * 拥有的当前角色 + */ + @TableField(exist = false) + public Role role; + + /** + * 拥有的所有角色 + */ + @TableField(exist = false) + public List roles; + /** + * 头像 + */ + public String avatar; + /** + * 地址 + */ + public String address; + + +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java b/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java new file mode 100644 index 0000000..fdc0b5a --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/entity/UserRole.java @@ -0,0 +1,24 @@ +package com.zsc.edu.dify.modules.system.entity; + +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.io.Serializable; + +/** + * sys_users_roles + * @author zhuang + */ +@Data +@TableName("sys_users_roles") +public class UserRole implements Serializable { + /** + * 用户ID + */ + private Long userId; + + /** + * 角色ID + */ + private Long roleId; +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/mapper/AuthorityMapper.java b/src/main/java/com/zsc/edu/dify/modules/system/mapper/AuthorityMapper.java new file mode 100644 index 0000000..9bbd6d5 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/mapper/AuthorityMapper.java @@ -0,0 +1,14 @@ +package com.zsc.edu.dify.modules.system.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.system.dto.AuthorityDto; +import com.zsc.edu.dify.modules.system.entity.Authority; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author zhuang + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface AuthorityMapper extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/mapper/DeptMapper.java b/src/main/java/com/zsc/edu/dify/modules/system/mapper/DeptMapper.java new file mode 100644 index 0000000..5702f1f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/mapper/DeptMapper.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.modules.system.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.system.dto.DeptDto; +import com.zsc.edu.dify.modules.system.entity.Dept; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; + +/** + * @author Yao + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface DeptMapper extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/mapper/MenuMapper.java b/src/main/java/com/zsc/edu/dify/modules/system/mapper/MenuMapper.java new file mode 100644 index 0000000..1a53543 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/mapper/MenuMapper.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.modules.system.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.system.dto.MenuDto; +import com.zsc.edu.dify.modules.system.entity.Menu; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; + +/** + * @author zhuang + */ +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface MenuMapper extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/mapper/RoleAuthorityMapper.java b/src/main/java/com/zsc/edu/dify/modules/system/mapper/RoleAuthorityMapper.java new file mode 100644 index 0000000..bfd2a9e --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/mapper/RoleAuthorityMapper.java @@ -0,0 +1,14 @@ +package com.zsc.edu.dify.modules.system.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.system.dto.RoleAuthorityDto; +import com.zsc.edu.dify.modules.system.entity.RoleAuthority; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author zhuang + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface RoleAuthorityMapper extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/mapper/RoleMapper.java b/src/main/java/com/zsc/edu/dify/modules/system/mapper/RoleMapper.java new file mode 100644 index 0000000..b5a8942 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/mapper/RoleMapper.java @@ -0,0 +1,15 @@ +package com.zsc.edu.dify.modules.system.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.system.dto.RoleDto; +import com.zsc.edu.dify.modules.system.entity.Role; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Yao + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface RoleMapper extends BaseMapper { + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/mapper/UserMapper.java b/src/main/java/com/zsc/edu/dify/modules/system/mapper/UserMapper.java new file mode 100644 index 0000000..89f27e3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/mapper/UserMapper.java @@ -0,0 +1,14 @@ +package com.zsc.edu.dify.modules.system.mapper; + +import com.zsc.edu.dify.common.mapstruct.BaseMapper; +import com.zsc.edu.dify.modules.system.dto.UserCreateDto; +import com.zsc.edu.dify.modules.system.entity.User; +import org.mapstruct.Mapper; +import org.mapstruct.ReportingPolicy; + +/** + * @author Yao + */ +@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE) +public interface UserMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/query/AuthorityQuery.java b/src/main/java/com/zsc/edu/dify/modules/system/query/AuthorityQuery.java new file mode 100644 index 0000000..d28db3f --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/query/AuthorityQuery.java @@ -0,0 +1,32 @@ +package com.zsc.edu.dify.modules.system.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.modules.system.entity.Authority; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +/** + * @author zhuang + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class AuthorityQuery { + /** + * 编码,前缀匹配 + */ + public String code; + + /** + * 名称,模糊查询 + */ + public String name; + + public LambdaQueryWrapper wrapper() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(StringUtils.hasText(this.name), Authority::getName, this.name); + return queryWrapper; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/query/DeptQuery.java b/src/main/java/com/zsc/edu/dify/modules/system/query/DeptQuery.java new file mode 100644 index 0000000..6a6f22c --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/query/DeptQuery.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.modules.system.query; + +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + + +/** + * 部门Query + * + * @author Yao + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class DeptQuery { + + /** + * 编码,前缀匹配 + */ + public String code; + + /** + * 名称,模糊查询 + */ + public String name; + + public LambdaQueryWrapper wrapper() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.like(StringUtils.hasText(this.name), Dept::getName, this.name); + return queryWrapper; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/query/RoleQuery.java b/src/main/java/com/zsc/edu/dify/modules/system/query/RoleQuery.java new file mode 100644 index 0000000..d5c7d12 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/query/RoleQuery.java @@ -0,0 +1,35 @@ +package com.zsc.edu.dify.modules.system.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.modules.system.entity.Role; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author ftz + * 创建时间:2024/4/8 8:06 + * 描述: 角色查询参数 + */ +@Data +@AllArgsConstructor +@NoArgsConstructor +public class RoleQuery { + /** + * 名称,唯一 + */ + public String name; + + /** + * 启用状态 + */ + private Boolean enabled ; + + public LambdaQueryWrapper wrapper() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(this.enabled != null, Role::getEnabled, this.enabled); + queryWrapper.like(this.name != null, Role::getName, this.name); + return queryWrapper; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/query/UserQuery.java b/src/main/java/com/zsc/edu/dify/modules/system/query/UserQuery.java new file mode 100644 index 0000000..8f19d87 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/query/UserQuery.java @@ -0,0 +1,69 @@ +package com.zsc.edu.dify.modules.system.query; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.CollectionUtils; +import com.zsc.edu.dify.modules.system.entity.User; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import org.springframework.util.StringUtils; + +import java.util.List; +import java.util.Objects; + +/** + * 用户Query + * + * @author harry yao + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +public class UserQuery { + + /** + * 用户名 + */ + public String username; + + /** + * 手机号码 + */ + public String phone; + + /** + * 电子邮件 + */ + public String email; + + /** + * 启用状态 + */ + public Boolean enableState; + + /** + * 角色集合 + */ + public Long roleId; + + /** + * 部门集合 + */ + public List deptIds; + + + + + public LambdaQueryWrapper wrapper() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + queryWrapper.eq(StringUtils.hasText(this.username), User::getUsername, this.username); + queryWrapper.eq(StringUtils.hasText(this.phone), User::getPhone, this.phone); + queryWrapper.eq(StringUtils.hasText(this.email), User::getEmail, this.email); + queryWrapper.eq(Objects.nonNull(this.enableState), User::getEnableState, this.enableState); + queryWrapper.in(CollectionUtils.isNotEmpty(this.deptIds), User::getDeptId, this.deptIds); + queryWrapper.eq(Objects.nonNull(this.roleId),User::getRoleId,this.roleId); + return queryWrapper; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/AuthorityRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/AuthorityRepository.java new file mode 100644 index 0000000..e77ae64 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/AuthorityRepository.java @@ -0,0 +1,18 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.system.entity.Authority; +import org.apache.ibatis.annotations.Param; + +import java.util.Set; + +/** + * @author zhuang + */ +public interface AuthorityRepository extends BaseMapper { + + Set selectAuthoritiesByRoleId (@Param("roleId") Long roleId); + + Long getAuthorityIdByName(@Param("authName")String authName); +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/DeptRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/DeptRepository.java new file mode 100644 index 0000000..1e933bd --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/DeptRepository.java @@ -0,0 +1,18 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +import java.util.List; + +/** + *

+ * 部门 Mapper 接口 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface DeptRepository extends BaseMapper { + List selectDeptTree(); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/MenuRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/MenuRepository.java new file mode 100644 index 0000000..e005b83 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/MenuRepository.java @@ -0,0 +1,19 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.system.entity.Menu; + +import java.util.List; + +/** + * @author yao + */ +public interface MenuRepository extends BaseMapper { + + List selectByRoleId(Long roleId); + + List selectByUserId(Long userId, Menu.Type type); + + List selectAll(); +} + diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleAuthoritiesRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleAuthoritiesRepository.java new file mode 100644 index 0000000..23b3758 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleAuthoritiesRepository.java @@ -0,0 +1,18 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.zsc.edu.dify.modules.system.entity.RoleAuthority; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +import java.util.List; + +/** + * @author Yao + */ +public interface RoleAuthoritiesRepository extends BaseMapper { + + @Select("select * from sys_role_authorities where role_id=#{roleId}") + List selectByRoleId(Long roleId); + +// List selectAuthorityByRoleId(@Param("roleId") Long roleId); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleMenuRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleMenuRepository.java new file mode 100644 index 0000000..2ae1b2d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleMenuRepository.java @@ -0,0 +1,10 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.system.entity.RoleMenu; + +/** + * @author Yao + */ +public interface RoleMenuRepository extends BaseMapper { +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleRepository.java new file mode 100644 index 0000000..10d3444 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/RoleRepository.java @@ -0,0 +1,22 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.zsc.edu.dify.modules.system.entity.Role; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.zsc.edu.dify.modules.system.vo.RoleVo; +import org.apache.ibatis.annotations.Param; + + +/** + *

+ * 角色表 Mapper 接口 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface RoleRepository extends BaseMapper { + + RoleVo selectRoleById(@Param("roleId") Long roleId); + + Long getRoleIdByName(@Param("roleName") String roleName); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/UserRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/UserRepository.java new file mode 100644 index 0000000..d38fe21 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/UserRepository.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.zsc.edu.dify.modules.system.entity.User; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Select; + +/** + *

+ * Mapper 接口 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface UserRepository extends BaseMapper { + User selectByUsername(String username); + + @Select("select sys_user.name from sys_user where sys_user.id=#{id}") + static String selectNameById(Long id) { + return null; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/repo/UserRolesRepository.java b/src/main/java/com/zsc/edu/dify/modules/system/repo/UserRolesRepository.java new file mode 100644 index 0000000..4f836d4 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/repo/UserRolesRepository.java @@ -0,0 +1,16 @@ +package com.zsc.edu.dify.modules.system.repo; + +import com.zsc.edu.dify.modules.system.entity.UserRole; +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 + */ +public interface UserRolesRepository extends BaseMapper { + @Select("select role_id from sys_users_roles where user_id = #{userId}") + List selectByUserId(@Param("userId") Long userId); +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/AuthorityService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/AuthorityService.java new file mode 100644 index 0000000..f23d7fa --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/AuthorityService.java @@ -0,0 +1,22 @@ +package com.zsc.edu.dify.modules.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.modules.system.dto.AuthorityDto; +import com.zsc.edu.dify.modules.system.entity.Authority; + +import java.util.Set; + +/** + * @author zhuang + */ +public interface AuthorityService extends IService { + + Authority create(AuthorityDto authorityDto); + + Boolean update(AuthorityDto authorityDto,Long id); + + Boolean toggle(Long id); + + Set selectAuthoritiesByRoleId(Long roleId); + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java new file mode 100644 index 0000000..afc8704 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/DeptService.java @@ -0,0 +1,40 @@ +package com.zsc.edu.dify.modules.system.service; + +import com.zsc.edu.dify.modules.system.dto.DeptDto; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + *

+ * 部门 服务类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface DeptService extends IService { + + /** + * 创建部门 + * @param dto + */ + Dept create(DeptDto dto); + + /** + * 更新部门 + * @param dto + * @param id + */ + Boolean edit(DeptDto dto, Long id); + + Boolean toggle(Long id); + + /** + * 生成部门树结构 + * @param id + * @return + */ + List listTree(Long id); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/MenuService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/MenuService.java new file mode 100644 index 0000000..da03eb3 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/MenuService.java @@ -0,0 +1,29 @@ +package com.zsc.edu.dify.modules.system.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.dto.MenuDto; +import com.zsc.edu.dify.modules.system.entity.Menu; +import com.zsc.edu.dify.modules.system.vo.MenuVo; + +import java.util.List; + +/** + * @author zhuang + */ +public interface MenuService extends IService { + + List selectByRoleId(Long roleId); + + List selectByUserId(Long userId, Menu.Type type); + + Menu create(MenuDto dto); + + Menu update(MenuDto dto, Long id); + + Boolean delete(Long id); + + List getTree(UserDetailsImpl userDetails, String name); + + Menu detail(Long id); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/RoleAuthService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/RoleAuthService.java new file mode 100644 index 0000000..875945b --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/RoleAuthService.java @@ -0,0 +1,22 @@ +package com.zsc.edu.dify.modules.system.service; + +//import com.zsc.edu.dify.modules.system.dto.RoleAuthCreateDto; +//import com.zsc.edu.dify.modules.system.dto.RoleAuthorityDto; +import com.zsc.edu.dify.modules.system.entity.RoleAuthority; +import com.baomidou.mybatisplus.extension.service.IService; + + +/** + *

+ * 角色权限表 服务类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface RoleAuthService extends IService { + + boolean removeByRoleId(Long id); + +// boolean addRoleAuthority(RoleAuthCreateDto dto); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/RoleService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/RoleService.java new file mode 100644 index 0000000..eedb38b --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/RoleService.java @@ -0,0 +1,28 @@ +package com.zsc.edu.dify.modules.system.service; + +import com.zsc.edu.dify.modules.system.dto.RoleDto; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.baomidou.mybatisplus.extension.service.IService; +import com.zsc.edu.dify.modules.system.vo.RoleVo; + +/** + *

+ * 角色表 服务类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface RoleService extends IService { + + Role create(RoleDto roleDto); + + Role edit(RoleDto roleDto, Long id); + + RoleVo detail(Long id); + + Boolean toggle(Long id); + + Boolean delete(Long id); + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/UserService.java b/src/main/java/com/zsc/edu/dify/modules/system/service/UserService.java new file mode 100644 index 0000000..516f190 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/UserService.java @@ -0,0 +1,35 @@ +package com.zsc.edu.dify.modules.system.service; + +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.dto.*; +import com.zsc.edu.dify.modules.system.entity.User; +import com.baomidou.mybatisplus.extension.service.IService; + +/** + *

+ * 服务类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +public interface UserService extends IService { + + User selfDetail(UserDetailsImpl userDetails); + + Boolean selfUpdate(UserDetailsImpl userDetails, UserSelfUpdateDto dto); + + Boolean selfUpdatePassword(UserDetailsImpl userDetails, UserSelfUpdatePasswordDto dto); + + Boolean create(UserCreateDto dto); + + Boolean update(UserUpdateDto dto, Long id); + + Boolean updatePassword(String password, Long id); + + Boolean toggle(Long id); + + String sendEmail(String email); + + UserDetailsImpl switchRole(Long roleId); +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/AuthorityServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/AuthorityServiceImpl.java new file mode 100644 index 0000000..7de02b8 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/AuthorityServiceImpl.java @@ -0,0 +1,59 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.modules.system.dto.AuthorityDto; +//import com.zsc.edu.dify.modules.system.dto.RoleAuthorityDto; +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.mapper.AuthorityMapper; +import com.zsc.edu.dify.modules.system.repo.AuthorityRepository; +import com.zsc.edu.dify.modules.system.service.AuthorityService; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * @author zhuang + */ +@AllArgsConstructor +@Service +public class AuthorityServiceImpl extends ServiceImpl implements AuthorityService { + private AuthorityMapper mapper; + private AuthorityRepository repo; + + @Override + public Authority create(AuthorityDto authorityDto) { + boolean existsByName = count(new LambdaQueryWrapper().eq(Authority::getName, authorityDto.getName())) > 0; + if (existsByName) { + throw new ConstraintException("name", authorityDto.name, "权限已存在"); + } + Authority authority = mapper.toEntity(authorityDto); + save(authority); + return authority; + } + + @Override + public Boolean update(AuthorityDto authorityDto, Long id) { + boolean isExists = count(new LambdaQueryWrapper().ne(Authority::getId, id).eq(Authority::getName, authorityDto.getName())) > 0; + if (isExists) { + throw new ConstraintException("name", authorityDto.name, "同名权限已存在"); + } + return update(authorityDto.updateWrapper(id)); + } + + + @Override + public Boolean toggle(Long id) { + Authority authority = getById(id); + authority.setEnabled(!authority.getEnabled()); + return updateById(authority); + } + + @Override + public Set selectAuthoritiesByRoleId(Long roleId){ + return baseMapper.selectAuthoritiesByRoleId(roleId); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java new file mode 100644 index 0000000..cebc5bb --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/DeptServiceImpl.java @@ -0,0 +1,88 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.common.util.TreeUtil; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.modules.system.dto.DeptDto; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.mapper.DeptMapper; +import com.zsc.edu.dify.modules.system.repo.DeptRepository; +import com.zsc.edu.dify.modules.system.service.DeptService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + *

+ * 部门 服务实现类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +@RequiredArgsConstructor +@Service +public class DeptServiceImpl extends ServiceImpl implements DeptService { + + private final DeptMapper mapper; + + @Override + public Dept create(DeptDto dto) { + boolean existsByName = count(new LambdaQueryWrapper().eq(Dept::getName, dto.getName())) > 0; + if (existsByName) { + throw new ConstraintException("name", dto.name, "部门已存在"); + } + Dept dept = mapper.toEntity(dto); + save(dept); + return dept; + } + + @Override + public Boolean edit(DeptDto dto, Long id) { + boolean isExists = count(new LambdaQueryWrapper().ne(Dept::getId, id).eq(Dept::getName, dto.getName())) > 0; + if (isExists) { + throw new ConstraintException("name", dto.name, "同名部门已存在"); + } + return update(dto.updateWrapper(id)); + } + + @Override + public Boolean toggle(Long id) { + Dept dept = getById(id); + dept.setEnabled(!dept.getEnabled()); + return updateById(dept); + } + + @Override + public List listTree(Long deptId) { + List deptTrees = baseMapper.selectDeptTree(); + List deptTree = TreeUtil.makeTree( + deptTrees, + department -> department.getPid() == null || department.getPid() == -1L, + (parent, child) -> parent.getId().equals(child.getPid()), + Dept::setChildren + ); + + if (Objects.nonNull(deptId)) { + if (deptId == 0) { + // 返回全部部门树 + return deptTree; + } else { + // 返回指定部门及其子部门树 + List deptChildrenTree = new ArrayList<>(); + TreeUtil.forLevelOrder(deptTree, node -> { + if (node.getId().equals(deptId)) { + deptChildrenTree.add(node); + } + }, Dept::getChildren); + return deptChildrenTree; + } + } + return deptTree; + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImpl.java new file mode 100644 index 0000000..ac5c27d --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImpl.java @@ -0,0 +1,118 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.dto.MenuDto; +import com.zsc.edu.dify.modules.system.entity.Menu; +import com.zsc.edu.dify.modules.system.entity.RoleMenu; +import com.zsc.edu.dify.modules.system.mapper.MenuMapper; +import com.zsc.edu.dify.modules.system.repo.MenuRepository; +import com.zsc.edu.dify.modules.system.repo.RoleMenuRepository; +import com.zsc.edu.dify.modules.system.service.MenuService; +import com.zsc.edu.dify.modules.system.utils.TreeUtil; +import com.zsc.edu.dify.modules.system.vo.MenuVo; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author zhuang + */ +@AllArgsConstructor +@Service +public class MenuServiceImpl extends ServiceImpl implements MenuService { + private MenuMapper mapper; + private RoleMenuRepository roleMenuRepository; + @Override + public List selectByRoleId(Long roleId) { + return baseMapper.selectByRoleId(roleId); + } + + @Override + public List selectByUserId(Long userId, Menu.Type type) { + return baseMapper.selectByUserId(userId, type); + } + + @Override + @Transactional + public Menu create(MenuDto dto) { + if (baseMapper.exists(new LambdaQueryWrapper().eq(Menu::getName, dto.getName()))) { + throw new ConstraintException("该菜单名已存在!请检查输入表单是否出错!"); + } + if (baseMapper.exists(new LambdaQueryWrapper().eq(Menu::getPermissions, dto.getPermissions()))) { + throw new ConstraintException("该权限已存在!请检查输入表单是否出错!"); + } + if (baseMapper.selectOne(new LambdaQueryWrapper().eq(Menu::getId, dto.getPid())) == null && dto.getPid() != null) { + throw new ConstraintException("上级菜单不存在!请检查输入表单是否出错!"); + } + Menu menu = mapper.toEntity(dto); + baseMapper.insert(menu); + return menu; + } + + @Override + @Transactional + public Menu update(MenuDto dto, Long id) { + Menu menu = baseMapper.selectById(id); + if (menu == null) { + throw new ConstraintException("菜单不存在!请检查输入ID是否正确!"); + } + mapper.convert(dto, menu); + baseMapper.updateById(menu); + return menu; + } + + @Override + public Boolean delete(Long id) { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + roleMenuRepository.delete(queryWrapper.eq(RoleMenu::getMenuId, id)); + return removeById(id); + } + + @Override + public List getTree(UserDetailsImpl userDetails, String name) { + if (Objects.equals(userDetails.getUsername(), "admin")) { + List menus = baseMapper.selectAll().stream().map(MenuVo::new).toList(); + return createTree(menus, name); + } + List menuVos = selectByUserId(userDetails.getId(), Menu.Type.PAGE).stream().map(MenuVo::new).toList(); + return createTree(menuVos, name); + } + + public List createTree(List menuVos, String name) { + List menuTrees = TreeUtil.makeTree( + menuVos, + menuVo -> menuVo.getPid() == null || menuVo.getPid() == -1L, + (parent, child) -> parent.getId().equals(child.getPid()), + MenuVo::setChildren + ); + if (Objects.equals(name, "all")) { + return menuTrees; + } + if (Objects.nonNull(name)) { + Menu menu = baseMapper.selectOne(new LambdaQueryWrapper().eq(Menu::getName, name)); + if (menu == null) { + throw new ConstraintException("此名称不存在,请重新输入!"); + } + List menuTree = new ArrayList<>(); + TreeUtil.forLevelOrder(menuTrees, node -> { + if (node.getId().equals(menu.getId())) { + menuTree.add(node); + } + }, MenuVo::getChildren); + return menuTree; + } + return menuTrees; + } + + @Override + public Menu detail(Long id) { + return baseMapper.selectById(id); + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/RoleAuthServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/RoleAuthServiceImpl.java new file mode 100644 index 0000000..cbe9dff --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/RoleAuthServiceImpl.java @@ -0,0 +1,42 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +//import com.zsc.edu.dify.modules.system.dto.RoleAuthCreateDto; +//import com.zsc.edu.dify.modules.system.dto.RoleAuthorityDto; +import com.zsc.edu.dify.modules.system.entity.RoleAuthority; +//import com.zsc.edu.dify.modules.system.mapper.RoleAuthorityMapper; +import com.zsc.edu.dify.modules.system.repo.AuthorityRepository; +import com.zsc.edu.dify.modules.system.repo.RoleAuthoritiesRepository; +import com.zsc.edu.dify.modules.system.repo.RoleRepository; +import com.zsc.edu.dify.modules.system.service.RoleAuthService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + + +/** + * @author zhuang + */ +@AllArgsConstructor +@Service +public class RoleAuthServiceImpl extends ServiceImpl implements RoleAuthService { +// private RoleAuthorityMapper mapper; + private AuthorityRepository repo; + private RoleRepository roleRepository; + + @Override + public boolean removeByRoleId(Long roleId) { + return remove(new LambdaQueryWrapper().eq(RoleAuthority::getRoleId, roleId)); + } + +// @Override +// public boolean addRoleAuthority(RoleAuthCreateDto dto){ +// Long authId=repo.getAuthorityIdByName(dto.getAuthName()); +// Long roleId=roleRepository.getRoleIdByName(dto.getRoleName()); +// RoleAuthorityDto dto1=new RoleAuthorityDto(); +// dto1.setAuthorityId(authId); +// dto1.setRoleId(roleId); +// RoleAuthority roleAuthority = mapper.toEntity(dto1); +// return save(roleAuthority); +// } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/RoleServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/RoleServiceImpl.java new file mode 100644 index 0000000..9725eb1 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/RoleServiceImpl.java @@ -0,0 +1,102 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.modules.system.dto.RoleDto; +import com.zsc.edu.dify.modules.system.entity.*; +import com.zsc.edu.dify.modules.system.mapper.RoleMapper; +import com.zsc.edu.dify.modules.system.repo.RoleMenuRepository; +import com.zsc.edu.dify.modules.system.repo.RoleRepository; +import com.zsc.edu.dify.modules.system.repo.UserRolesRepository; +import com.zsc.edu.dify.modules.system.service.RoleService; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.modules.system.vo.RoleVo; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Objects; +import java.util.stream.Collectors; + +/** + *

+ * 角色表 服务实现类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +@AllArgsConstructor +@Service +public class RoleServiceImpl extends ServiceImpl implements RoleService { + + private final RoleMapper mapper; + private final UserRolesRepository urRepo; + + private final RoleMenuRepository roleMenuRepository; + private final RoleRepository roleRepository; + + @Override + public Role create(RoleDto dto) { + boolean existsByName = count(new LambdaQueryWrapper().eq(Role::getName, dto.getName())) > 0; + if (existsByName) { + throw new ConstraintException("name", dto.name, "角色已存在"); + } + Role role = new Role(); + role.setName(dto.getName()); + role.setRemark(dto.getRemark()); + save(role); + if (dto.getMenuIds() != null) { + roleMenuRepository.insert( + dto.getMenuIds().stream() + .map(menuId -> new RoleMenu(role.getId(), menuId)).collect(Collectors.toList())); + } + return role; + } + + @Override + public Boolean toggle(Long id) { + Role role = getById(id); + role.setEnabled(!role.getEnabled()); + return updateById(role); + } + + @Override + public Boolean delete(Long id) { + boolean hasUser = urRepo.selectCount(new LambdaQueryWrapper().eq(UserRole::getRoleId, id)) > 0; + if (hasUser) { + throw new ConstraintException("存在与本角色绑定的用户,请先删除用户"); + } + roleMenuRepository.delete(new LambdaQueryWrapper().eq(RoleMenu::getRoleId, id)); + return removeById(id); + } + + @Override + public Role edit(RoleDto dto, Long id) { + Role selectyRole = getById(id); + if (selectyRole == null) { + throw new ConstraintException("id", id.toString(), "角色不存在"); + } + if (!Objects.equals(selectyRole.getName(), dto.getName()) && + count(new LambdaQueryWrapper().eq(Role::getName, dto.getName())) > 0) { + throw new ConstraintException("name", dto.getName(), "同名角色已存在"); + } + Role role = new Role(); + role.setName(dto.getName()); + role.setRemark(dto.getRemark()); + updateById(role); + if (dto.getMenuIds() != null) { + roleMenuRepository.delete(new LambdaQueryWrapper().eq(RoleMenu::getRoleId, id)); + roleMenuRepository.insert( + dto.getMenuIds().stream() + .map(menuId -> new RoleMenu(id, menuId)).collect(Collectors.toList())); + } + return role; + } + + + @Override + public RoleVo detail(Long id) { + return roleRepository.selectRoleById(id); + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/service/impl/UserServiceImpl.java b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/UserServiceImpl.java new file mode 100644 index 0000000..030bcd7 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/service/impl/UserServiceImpl.java @@ -0,0 +1,172 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.framework.security.SecurityUtil; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.dto.UserCreateDto; +import com.zsc.edu.dify.modules.system.dto.UserSelfUpdateDto; +import com.zsc.edu.dify.modules.system.dto.UserSelfUpdatePasswordDto; +import com.zsc.edu.dify.modules.system.dto.UserUpdateDto; +import com.zsc.edu.dify.modules.system.entity.*; +import com.zsc.edu.dify.modules.system.mapper.UserMapper; +import com.zsc.edu.dify.modules.system.repo.*; +import com.zsc.edu.dify.modules.system.service.UserService; +import com.zsc.edu.dify.modules.system.utils.sendMail; +import lombok.AllArgsConstructor; +import org.springframework.beans.BeanUtils; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +/** + *

+ * 服务实现类 + *

+ * + * @author Yao + * @since 2023-04-06 + */ +@AllArgsConstructor +@Service +public class UserServiceImpl extends ServiceImpl implements UserService { + + private final PasswordEncoder passwordEncoder; + private final sendMail send; + private final UserMapper userMapper; + private final RoleRepository roleRepository; + private final MenuRepository menuRepository; + private final UserRolesRepository userRolesRepository; + + @Override + @Transactional + public Boolean create(UserCreateDto dto) { + User user = new User(); + dto.setRoleId(dto.getRoleIds().get(0)); + userMapper.convert(dto, user); + boolean existsByPhone = count(new LambdaQueryWrapper().eq(User::getPhone, dto.getPhone())) > 0; + boolean existsByEmail = count(new LambdaQueryWrapper().eq(User::getEmail, dto.getEmail())) > 0; + if (user.getPhone().equals(dto.getPhone()) && existsByPhone) { + throw new ConstraintException("phone", dto.phone, "手机号已存在"); + } + if (user.getEmail().equals(dto.getEmail()) && existsByEmail) { + throw new ConstraintException("email", dto.email, "邮箱地址已存在"); + } + boolean saveSuccess = save(user); + if (!saveSuccess) { + return false; + } + if (dto.getRoleIds() != null && !dto.getRoleIds().isEmpty()) { + addUserRole(dto.getRoleIds(), user.getId()); + } + return true; + } + @Override + @Transactional + public Boolean update(UserUpdateDto dto, Long id) { + User user = getById(id); + if (dto.getRoleIds() != null && !dto.getRoleIds().isEmpty()) { + userRolesRepository.delete(new LambdaQueryWrapper().eq(UserRole::getUserId, id)); + dto.setRoleId(dto.getRoleIds().get(0)); + addUserRole(dto.getRoleIds(), user.getId()); + } + BeanUtils.copyProperties(dto, user); + return updateById(user); + } + + @Override + @Transactional + public Boolean updatePassword(String password, Long id) { + User user = getById(id); + user.setPassword(passwordEncoder.encode(password)); + return updateById(user); + } + + @Override + @Transactional + public Boolean toggle(Long id) { + User user = getById(id); + user.setEnableState(!user.getEnableState()); + return updateById(user); + } + + @Override + public String sendEmail(String email) { + send.sendEmailMessage(email); + return "发送成功"; + } + + + @Override + public User selfDetail(UserDetailsImpl userDetails) { + return getById(userDetails.getId()); + } + + @Override + @Transactional + public Boolean selfUpdate(UserDetailsImpl userDetails, UserSelfUpdateDto dto) { + User user = getById(userDetails.getId()); + boolean existsByPhone = count(new LambdaQueryWrapper().ne(User::getId, userDetails.getId()).eq(User::getPhone, dto.getPhone())) > 0; + if (existsByPhone) { + throw new ConstraintException("phone", dto.phone, "手机号已存在"); + } + boolean existsByEmail = count(new LambdaQueryWrapper().ne(User::getId, userDetails.getId()).eq(User::getEmail, dto.getEmail())) > 0; + if (existsByEmail) { + throw new ConstraintException("email", dto.email, "邮箱地址已存在"); + } + BeanUtils.copyProperties(dto, user); + + return updateById(user); + } + + @Override + @Transactional + public Boolean selfUpdatePassword(UserDetailsImpl userDetails, UserSelfUpdatePasswordDto dto) { + User user = getById(userDetails.getId()); + if (!passwordEncoder.matches(dto.oldPassword, user.password)) { + throw new ConstraintException("旧密码不对"); + } + user.setPassword(passwordEncoder.encode(dto.password)); + return updateById(user); + } + + @Override + @Transactional + public UserDetailsImpl switchRole(Long roleId) { + Role role = roleRepository.selectById(roleId); + if (role == null) { + throw new ConstraintException("角色不存在"); + } + UserDetailsImpl userDetails = SecurityUtil.getUserInfo(); + boolean updated = lambdaUpdate().eq(User::getId, userDetails.getId()) + .set(User::getRoleId, roleId) + .update(); + userDetails.setRole(role); + if (updated) { + Set permissions = menuRepository.selectByRoleId(roleId).stream() + .map(Menu::getPermissions) + .collect(Collectors.toSet()); + userDetails.setPermissions(permissions); + SecurityUtil.setUserInfo(userDetails); + } + return userDetails; + } + + public Boolean addUserRole(List roleIds, Long userId) { + List 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; + } +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/utils/TreeUtil.java b/src/main/java/com/zsc/edu/dify/modules/system/utils/TreeUtil.java new file mode 100644 index 0000000..37257a9 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/utils/TreeUtil.java @@ -0,0 +1,148 @@ +package com.zsc.edu.dify.modules.system.utils; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collectors; + +/** + * @Description: 树操作方法工具类 + * @Copyright: Copyright (c) 赵侠客 + * @Date: 2024-07-22 10:42 + * @Version: 1.0 + */ +public class TreeUtil { + /** + * 将list合成树 + * + * @param list 需要合成树的List + * @param rootCheck 判断E中为根节点的条件,如:x->x.getPId()==-1L , x->x.getParentId()==null,x->x.getParentMenuId()==0 + * @param parentCheck 判断E中为父节点条件,如:(x,y)->x.getId().equals(y.getPId()) + * @param setSubChildren E中设置下级数据方法,如:Menu::setSubMenus + * @param 泛型实体对象 + * @return 合成好的树 + */ + public static List makeTree(List list, Predicate rootCheck, BiFunction parentCheck, BiConsumer> setSubChildren) { + return list.stream() + .filter(rootCheck) + .peek(x -> setSubChildren.accept(x, makeChildren(x, list, parentCheck, setSubChildren))) + .collect(Collectors.toList()); + } + /** + * 创建子树 + * + * @param parent 父节点 + * @param allData 需要合成树的原始List + * @param parentCheck 判断E中为父节点条件,如:(x,y)->x.getId().equals(y.getPId()) + * @param children E中设置下级数据方法,如:Menu::setSubMenus + * @param 泛型实体对象 + * @return 合成好的子树 + */ + private static List makeChildren(E parent, List allData, BiFunction parentCheck, BiConsumer> children) { + return allData.stream() + .filter(x -> parentCheck.apply(parent, x)) + .peek(x -> children.accept(x, makeChildren(x, allData, parentCheck, children))) + .collect(Collectors.toList()); + } + + /** + * 将树打平成tree + * @param tree 需要打平的树 + * @param getSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param setSubChildren 将下级数据置空方法,如:x->x.setSubMenus(null) + * @return 打平后的数据 + * @param 泛型实体对象 + */ + public static List flat(List tree, Function> getSubChildren, Consumer setSubChildren) { + List res = new ArrayList<>(); + forPostOrder( + tree, + item -> { + setSubChildren.accept(item); + res.add(item); + }, + getSubChildren + ); + return res; + } + + + /** + * 前序遍历 + * + * @param tree 需要遍历的树 + * @param consumer 遍历后对单个元素的处理方法,如:x-> System.out.println(x)、 System.out::println打印元素 + * @param setSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param 泛型实体对象 + */ + public static void forPreOrder(List tree, Consumer consumer, Function> setSubChildren) { + for (E l : tree) { + consumer.accept(l); + List es = setSubChildren.apply(l); + if (Objects.nonNull(es) && !es.isEmpty()) { + forPreOrder(es, consumer, setSubChildren); + } + } + } + + + /** + * 层序遍历 + * + * @param tree 需要遍历的树 + * @param consumer 遍历后对单个元素的处理方法,如:x-> System.out.println(x)、 System.out::println打印元素 + * @param setSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param 泛型实体对象 + */ + public static void forLevelOrder(List tree, Consumer consumer, Function> setSubChildren) { + Queue queue = new LinkedList<>(tree); + while (!queue.isEmpty()) { + E item = queue.poll(); + consumer.accept(item); + List childList = setSubChildren.apply(item); + if (Objects.nonNull(childList) && !childList.isEmpty()) { + queue.addAll(childList); + } + } + } + + + /** + * 后序遍历 + * + * @param tree 需要遍历的树 + * @param consumer 遍历后对单个元素的处理方法,如:x-> System.out.println(x)、 System.out::println打印元素 + * @param setSubChildren 设置下级数据方法,如:Menu::getSubMenus,x->x.setSubMenus(null) + * @param 泛型实体对象 + */ + public static void forPostOrder(List tree, Consumer consumer, Function> setSubChildren) { + for (E item : tree) { + List childList = setSubChildren.apply(item); + if (Objects.nonNull(childList) && !childList.isEmpty()) { + forPostOrder(childList, consumer, setSubChildren); + } + consumer.accept(item); + } + } + + /** + * 对树所有子节点按comparator排序 + * + * @param tree 需要排序的树 + * @param comparator 排序规则Comparator,如:Comparator.comparing(MenuVo::getRank)按Rank正序 ,(x,y)->y.getRank().compareTo(x.getRank()),按Rank倒序 + * @param getChildren 获取下级数据方法,如:MenuVo::getSubMenus + * @return 排序好的树 + * @param 泛型实体对象 + */ + public static List sort(List tree, Comparator comparator, Function> getChildren) { + for (E item : tree) { + List childList = getChildren.apply(item); + if (Objects.nonNull(childList) && !childList.isEmpty()) { + sort(childList,comparator,getChildren); + } + } + tree.sort(comparator); + return tree; + } + + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/utils/sendMail.java b/src/main/java/com/zsc/edu/dify/modules/system/utils/sendMail.java new file mode 100644 index 0000000..e7161a7 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/utils/sendMail.java @@ -0,0 +1,93 @@ +package com.zsc.edu.dify.modules.system.utils; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.mail.javamail.JavaMailSenderImpl; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.text.MessageFormat; + +/** + * @author ftz + * 创建时间:2024/4/5 17:24 + * 描述: 根据邮箱发送验证码 + */ +@Component +public class sendMail { + @jakarta.annotation.Resource + JavaMailSenderImpl mailSender; + @Getter + private int randomCode; + + @Value("${spring.mail.username}") + private String username; + + /** + * 读取邮件模板 + * 替换模板中的信息 + * + * @param title 内容 + * @return + */ + public String buildContent(String title) { + //加载邮件html模板 + Resource resource = new ClassPathResource("mailTemplate/mailtemplate.ftl"); + InputStream inputStream = null; + BufferedReader fileReader = null; + StringBuffer buffer = new StringBuffer(); + String line = ""; + try { + inputStream = resource.getInputStream(); + fileReader = new BufferedReader(new InputStreamReader(inputStream)); + while ((line = fileReader.readLine()) != null) { + buffer.append(line); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (fileReader != null) { + try { + fileReader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + //替换html模板中的参数 + return MessageFormat.format(buffer.toString(), title); + } + public void sendEmailMessage(String email) { + MimeMessage message = mailSender.createMimeMessage(); + try { + randomCode = (int) ((Math.random() * 9 + 1) * 100000); + // RandomStringUtils + //邮箱发送内容组成 + MimeMessageHelper helper = new MimeMessageHelper(message, true); + helper.setSubject("主题"); + + helper.setText(buildContent(randomCode + ""), true); + helper.setTo(email); + helper.setFrom("发件人名字" + '<' + username + '>'); + mailSender.send(message); + } catch (MessagingException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/vo/MenuVo.java b/src/main/java/com/zsc/edu/dify/modules/system/vo/MenuVo.java new file mode 100644 index 0000000..1e4b7d5 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/vo/MenuVo.java @@ -0,0 +1,82 @@ +package com.zsc.edu.dify.modules.system.vo; + +import com.zsc.edu.dify.modules.system.entity.Menu; +import lombok.Data; + +import java.util.List; + +/** + * 菜单 + * + * @author harry yao + */ +@Data +public class MenuVo { + /** + * 菜单id + */ + private Long id; + /** + * 父菜单id + */ + private Long pid; + /** + * 路由名称 + */ + private String name; + /** + * 菜单路径 + */ + private String path; + + private Meta meta; + + /** + * 子菜单 + */ + private List children = null; + + public MenuVo(Menu menu) { + this.id = menu.getId(); + this.pid = menu.getPid(); + this.name = menu.getName(); + this.path = menu.getPath(); + this.meta = new Meta(menu); + } +} +@Data +class Meta { + /** + * 菜单名称(语言包键名) + */ + private String locale; + /** + * 菜单icon + */ + private String icon; + /** + * 是否需要登录鉴权 + */ + private Boolean requiresAuth; + /** + * 是否隐藏菜单 + */ + private Boolean hideInMenu; + /** + * 排序 + */ + private Integer order; + /** + * 访问权限,使用","隔开的权限字符串 + */ + private String[] permissions; + + public Meta(Menu menu) { + this.locale = menu.getLocale(); + this.icon = menu.getIcon(); + this.requiresAuth = menu.getRequiresAuth(); + this.hideInMenu = menu.getHideInMenu(); + this.order = menu.getMenuOrder(); + this.permissions = menu.getPermissions().split(","); + } +} \ No newline at end of file diff --git a/src/main/java/com/zsc/edu/dify/modules/system/vo/RoleVo.java b/src/main/java/com/zsc/edu/dify/modules/system/vo/RoleVo.java new file mode 100644 index 0000000..c064a51 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/vo/RoleVo.java @@ -0,0 +1,65 @@ +package com.zsc.edu.dify.modules.system.vo; + +import com.zsc.edu.dify.modules.system.entity.Menu; +import lombok.Data; + +import java.time.LocalDateTime; +import java.util.Set; +/** + * @author zhuang + */ +@Data +public class RoleVo { + + /** + * 自增主键 + */ + public Long id; + + /** + * 名称,唯一 + */ + public String name; + + /** + * 启用状态 + */ + private Boolean enabled = true; + + /** + * 部门ID(权限) + */ + public Long deptId; + + + /** + * 备注说明 + */ + public String roleRemark; + + /** + * 创建时间 + */ + public LocalDateTime createTime; + /* + * 创建人 + * + */ + public String createBy; + + /** + * 更新时间 + */ + public LocalDateTime updateTime; + /* + * 更新人 + * + * */ + public String updateBy; + + + /** + * 权限集合 + */ + public Set menus; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/vo/UserDetail.java b/src/main/java/com/zsc/edu/dify/modules/system/vo/UserDetail.java new file mode 100644 index 0000000..795c6c1 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/vo/UserDetail.java @@ -0,0 +1,20 @@ +package com.zsc.edu.dify.modules.system.vo; + +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.entity.User; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; + +/** + * @author ftz + * 创建时间:16/2/2024 下午6:20 + */ +@Getter +@Setter +public class UserDetail { + private User user; + private List authorities; + private String permissions; +} diff --git a/src/main/java/com/zsc/edu/dify/modules/system/vo/UserVo.java b/src/main/java/com/zsc/edu/dify/modules/system/vo/UserVo.java new file mode 100644 index 0000000..4aaa541 --- /dev/null +++ b/src/main/java/com/zsc/edu/dify/modules/system/vo/UserVo.java @@ -0,0 +1,63 @@ +package com.zsc.edu.dify.modules.system.vo; + + +import lombok.Data; + +import java.time.LocalDateTime; + +/** + * @author zhuang + */ +@Data +public class UserVo { + /** + * 自增主键 + */ + public Long userId; + /** + * 用户名 + */ + public String username; + + /** + * 手机号码 + */ + public String phone; + + /** + * 电子邮件 + */ + public String email; + + /** + * 启用状态 + */ + public Boolean enabled; + /** + * + *昵称 + * */ + public String name; + + /** + * 所属部门ID + */ + public Long deptId; + + /** + * 角色ID + */ + public Long roleId; + /** + * 头像 + */ + public String avatar; + /** + * 地址 + */ + public String address; + + LocalDateTime createTime; + LocalDateTime updateTime; + +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..3aa7cca --- /dev/null +++ b/src/main/resources/application-dev.yml @@ -0,0 +1,59 @@ +server: + port: 8081 + +mybatis-plus: + type-aliases-package: com.zsc.edu.dify.modules.*.entity + mapper-locations: classpath*:mappers/*/*.xml + type-handlers-package: com.zsc.edu.dify.framework.mybatisplus + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler + map-underscore-to-camel-case: true + +spring: + datasource: + url: jdbc:postgresql://43.139.10.64:15432/dify?ssl=false&TimeZone=Asia/Shanghai + username: gitea + password: gitea + driver-class-name: org.postgresql.Driver + data: + redis: + host: 43.139.10.64 + port: 16379 + password: + servlet: + multipart: + max-file-size: 40MB + max-request-size: 40MB + jackson: + # 属性为空不序列化 + default-property-inclusion: non_null + mail: + # 配置 SMTP 服务器地址 + host: smtp.qq.com + # 发送者邮箱 + username: 2179732328@qq.com + # 配置密码,注意不是真正的密码,而是刚刚申请到的授权码 + password: mabpnbtqqezjdjde + # 端口号465或587 + port: 587 + # 默认的邮件编码为UTF-8 + default-encoding: UTF-8 + # 配置SSL 加密工厂 + properties: + mail: + smtp: + auth: true + starttls: + enable: true + socketFactoryClass: javax.net.ssl.SSLSocketFactory + #表示开启 DEBUG 模式,这样,邮件发送过程的日志会在控制台打印出来,方便排查错误 + debug: true + +storage: + attachment: ./storage/attachment + temp: ./storage/temp + +jwt: + secret: your_secret_key_here + expiration: 3600 diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml new file mode 100644 index 0000000..970d1a8 --- /dev/null +++ b/src/main/resources/application-prod.yml @@ -0,0 +1,42 @@ +server: + port: 8081 + +mybatis-plus: + type-aliases-package: com.zsc.edu.dify.modules.*.entity + mapper-locations: classpath:mappers/*/*.xml + type-handlers-package: com.zsc.edu.dify.framework.mybatisplus + configuration: + log-impl: org.apache.ibatis.logging.stdout.StdOutImpl + +spring: + datasource: + url: jdbc:postgresql://localhost:5432/gateway?ssl=false&TimeZone=Asia/Shanghai + username: postgres + password: 123456 + driver-class-name: org.postgresql.Driver + servlet: + multipart: + max-file-size: 40MB + max-request-size: 40MB + mail: + # 配置 SMTP 服务器地址 + host: smtp.qq.com + # 发送者邮箱 + username: 2179732328@qq.com + # 配置密码,注意不是真正的密码,而是刚刚申请到的授权码 + password: mabpnbtqqezjdjde + # 端口号465或587 + port: 587 + # 默认的邮件编码为UTF-8 + default-encoding: UTF-8 + # 配置SSL 加密工厂 + properties: + mail: + smtp: + socketFactoryClass: javax.net.ssl.SSLSocketFactory + #表示开启 DEBUG 模式,这样,邮件发送过程的日志会在控制台打印出来,方便排查错误 + debug: true +storage: + attachment: ./storage/attachment + temp: ./storage/temp + diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..6f638a7 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,6 @@ +spring: + profiles: + active: dev + docker: + compose: + enabled: false \ No newline at end of file diff --git a/src/main/resources/mailTemplate/mailtemplate.ftl b/src/main/resources/mailTemplate/mailtemplate.ftl new file mode 100644 index 0000000..12307d9 --- /dev/null +++ b/src/main/resources/mailTemplate/mailtemplate.ftl @@ -0,0 +1,61 @@ + + + + + + + + + + +
+ + + + + + + + + +
+ 验证码 +
+
+ +

+ + + 尊敬的用户: + +

+ +

您好!感谢您使用票据管理系统,您的账号正在进行邮箱验证,验证码为:{0},有效期1分钟,请尽快填写验证码完成验证!


+ +

+ + + Dear user: + +

+

Hello! Thanks for using Ticket system, your account is being authenticated by email, the + verification code is:{0}, valid for 1 minutes. Please fill in the verification code as soon as + possible!

+
+
+ +
+

此为系统邮件,请勿回复
+ Please do not reply to this system email +

+ +
+
+
+
+
+ + + diff --git a/src/main/resources/mappers/system/AuthorityMapper.xml b/src/main/resources/mappers/system/AuthorityMapper.xml new file mode 100644 index 0000000..1505c27 --- /dev/null +++ b/src/main/resources/mappers/system/AuthorityMapper.xml @@ -0,0 +1,17 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/mappers/system/DeptMapper.xml b/src/main/resources/mappers/system/DeptMapper.xml new file mode 100644 index 0000000..5ab65e3 --- /dev/null +++ b/src/main/resources/mappers/system/DeptMapper.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/src/main/resources/mappers/system/MenuMapper.xml b/src/main/resources/mappers/system/MenuMapper.xml new file mode 100644 index 0000000..75fff63 --- /dev/null +++ b/src/main/resources/mappers/system/MenuMapper.xml @@ -0,0 +1,16 @@ + + + + + + + + diff --git a/src/main/resources/mappers/system/RoleAuthoritiesReposity.xml b/src/main/resources/mappers/system/RoleAuthoritiesReposity.xml new file mode 100644 index 0000000..3cee7f8 --- /dev/null +++ b/src/main/resources/mappers/system/RoleAuthoritiesReposity.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/mappers/system/RoleMapper.xml b/src/main/resources/mappers/system/RoleMapper.xml new file mode 100644 index 0000000..af0b7ed --- /dev/null +++ b/src/main/resources/mappers/system/RoleMapper.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mappers/system/UserMapper.xml b/src/main/resources/mappers/system/UserMapper.xml new file mode 100644 index 0000000..5966f89 --- /dev/null +++ b/src/main/resources/mappers/system/UserMapper.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + id, username, password, email, phone, create_time + + + + + + + + diff --git a/src/test/java/com/zsc/edu/dify/BaseServiceTest.java b/src/test/java/com/zsc/edu/dify/BaseServiceTest.java new file mode 100644 index 0000000..d06af71 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/BaseServiceTest.java @@ -0,0 +1,94 @@ +package com.zsc.edu.dify; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.zsc.edu.dify.domain.system.DeptBuilder; +import com.zsc.edu.dify.domain.system.RoleBuilder; +import com.zsc.edu.dify.domain.system.UserBuilder; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.User; +import com.zsc.edu.dify.modules.system.repo.DeptRepository; +import com.zsc.edu.dify.modules.system.repo.RoleRepository; +import com.zsc.edu.dify.modules.system.repo.UserRepository; +import com.zsc.edu.dify.modules.system.service.RoleService; +import com.zsc.edu.dify.modules.system.service.UserService; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.util.Set; + +/** + * @author pengzheng + */ +//@ActiveProfiles("test") +@SpringBootTest +abstract public class BaseServiceTest { + + protected static UserDetailsImpl userDetails; + protected static User user1; + protected static User user2; + private static boolean dataInit; + private static UserRepository userRepoStatic; + private static DeptRepository deptRepoStatic; + private static RoleRepository roleRepoStatic; + private static RoleService roleServiceStatic; + @Autowired + private UserService service; + @Autowired + private UserRepository userRepo; + @Autowired + private DeptRepository deptRepo; + @Autowired + private RoleRepository roleRepo; + @Autowired + private RoleService roleService; + @Autowired + private PasswordEncoder passwordEncoder; + + @AfterAll + static void afterAll() { + userRepoStatic.delete(new QueryWrapper<>()); + roleRepoStatic.delete(new QueryWrapper<>()); + deptRepoStatic.delete(new QueryWrapper<>()); + dataInit = false; + } + + @BeforeEach + public void baseSetUp() { + if (!dataInit) { + Dept dept1 = DeptBuilder.aDept().name("神湾分局").build(); + deptRepo.insert(dept1); + Role role1 = RoleBuilder.aRole().name("超级管理员").build(); + roleRepo.insert(role1); + + user1 = UserBuilder.anUser() + .username("admin") + .email("123@qq.com") + .phone("13412334452") + .dept(dept1) + .role(role1) + .password(passwordEncoder.encode("admin")) + .build(); + userRepo.insert(user1); + + user2 = UserBuilder.anUser() + .username("13412334452") + .email("13412334452@zsc.edu.cn") + .phone("13412334452") + .password(passwordEncoder.encode("user1")) + .build(); + userRepo.insert(user2); + userDetails = UserDetailsImpl.from(user1, Set.of()); + + dataInit = true; + deptRepoStatic = deptRepo; + roleRepoStatic = roleRepo; + userRepoStatic = userRepo; + roleServiceStatic = roleService; + } + } +} diff --git a/src/test/java/com/zsc/edu/dify/DifyBackendApplicationTests.java b/src/test/java/com/zsc/edu/dify/DifyBackendApplicationTests.java new file mode 100644 index 0000000..3b88e1d --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/DifyBackendApplicationTests.java @@ -0,0 +1,23 @@ +package com.zsc.edu.dify; + +import com.zsc.edu.dify.modules.message.repo.BulletinRepository; +import com.zsc.edu.dify.modules.system.repo.UserRepository; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DifyBackendApplicationTests { + + @Resource + private UserRepository userRepository; + @Resource + private BulletinRepository bulletinRepository; + @Test + void contextLoads() { +// bulletinRepository.selectAll(); + } + + + +} diff --git a/src/test/java/com/zsc/edu/dify/MockMvcConfigBase.java b/src/test/java/com/zsc/edu/dify/MockMvcConfigBase.java new file mode 100644 index 0000000..334f641 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/MockMvcConfigBase.java @@ -0,0 +1,66 @@ +package com.zsc.edu.dify; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.zsc.edu.dify.domain.system.DeptBuilder; +import com.zsc.edu.dify.domain.system.RoleBuilder; +import com.zsc.edu.dify.domain.system.UserBuilder; +import com.zsc.edu.dify.framework.security.CustomAccessDeniedHandler; +import com.zsc.edu.dify.framework.security.CustomAuthenticationFailureHandler; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.User; +import com.zsc.edu.dify.modules.system.mapper.RoleMapper; +import com.zsc.edu.dify.modules.system.mapper.UserMapper; +import com.zsc.edu.dify.modules.system.service.UserService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.security.core.session.SessionRegistry; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; + +import javax.sql.DataSource; +import java.util.HashSet; +import java.util.Set; + +/** + * @author pengzheng + */ +@ExtendWith(SpringExtension.class) +//@AutoConfigureRestDocs +//@ActiveProfiles("test") +abstract public class MockMvcConfigBase { + + protected static UserDetailsImpl userDetails; + protected static User user; + @MockBean + protected DataSource dataSource; + @MockBean + protected SessionRegistry sessionRegistry; + @MockBean + protected UserMapper userMapper; + @MockBean + protected RoleMapper roleMapper; + + @MockBean + private UserService userService; + @MockBean + protected CustomAuthenticationFailureHandler customAuthenticationFailureHandler; + @MockBean + protected CustomAccessDeniedHandler customAccessDeniedHandler; + @Resource + protected MockMvc mockMvc; + @Resource + protected ObjectMapper objectMapper; + + @BeforeAll + public static void setup() { + Dept dept = DeptBuilder.aDept().name("Platform").build(); + Role role = RoleBuilder.aRole().authorities(new HashSet<>()).build(); +// Role role = RoleBuilder.aRole().authorities(new HashSet<>(Arrays.asList(Authority))).build(); + user = UserBuilder.anUser().username("admin").dept(dept).role(role).build(); + userDetails = UserDetailsImpl.from(user, Set.of()); + } +} diff --git a/src/test/java/com/zsc/edu/dify/domain/message/BulletinBuilder.java b/src/test/java/com/zsc/edu/dify/domain/message/BulletinBuilder.java new file mode 100644 index 0000000..3eb09f2 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/message/BulletinBuilder.java @@ -0,0 +1,36 @@ +package com.zsc.edu.dify.domain.message; + +import com.zsc.edu.dify.domain.system.BaseEntityBuilder; +import com.zsc.edu.dify.modules.message.entity.Bulletin; + +public class BulletinBuilder extends BaseEntityBuilder { + private String title; + private String content; + private Boolean top; + + public static BulletinBuilder bBulletin(){return new BulletinBuilder();} + + public BulletinBuilder title(String title){ + this.title = title; + return this; + } + + public BulletinBuilder content(String content){ + this.content = content; + return this; + } + + + public BulletinBuilder top(Boolean top){ + this.top = top; + return this; + } + + public Bulletin build(){ + Bulletin bulletin = new Bulletin(); + bulletin.setTitle(title); + bulletin.setContent(content); + bulletin.setTop(top); + return bulletin; + } +} diff --git a/src/test/java/com/zsc/edu/dify/domain/message/NoticeBuilder.java b/src/test/java/com/zsc/edu/dify/domain/message/NoticeBuilder.java new file mode 100644 index 0000000..34cfcce --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/message/NoticeBuilder.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.domain.message; + +import com.zsc.edu.dify.modules.message.entity.Notice; +import com.zsc.edu.dify.modules.message.entity.NoticeType; + +public class NoticeBuilder { + public NoticeType type; + public String title; + public String content; + + public static NoticeBuilder bMessage() { + return new NoticeBuilder(); + } + + public NoticeBuilder type(NoticeType type) { + this.type = type; + return this; + } + + public NoticeBuilder title(String title) { + this.title = title; + return this; + } + + public NoticeBuilder content(String content) { + this.content = content; + return this; + } + + public Notice build() { + Notice notice = new Notice(); + notice.setTitle(title); + notice.setContent(content); + notice.type = NoticeType.MESSAGE; + return notice; + } +} diff --git a/src/test/java/com/zsc/edu/dify/domain/message/UserNoticeBuilder.java b/src/test/java/com/zsc/edu/dify/domain/message/UserNoticeBuilder.java new file mode 100644 index 0000000..26dc785 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/message/UserNoticeBuilder.java @@ -0,0 +1,37 @@ +package com.zsc.edu.dify.domain.message; + + +import com.zsc.edu.dify.modules.message.entity.UserNotice; + +public class UserNoticeBuilder { + public Long userId; + public Long noticeId; + public Boolean isRead; + + public static UserNoticeBuilder builder() { + return new UserNoticeBuilder(); + } + + public UserNoticeBuilder setUserId(Long userId) { + this.userId = userId; + return this; + } + + public UserNoticeBuilder setNoticeId(Long noticeId) { + this.noticeId = noticeId; + return this; + } + + public UserNoticeBuilder setIsRead(Boolean isRead) { + this.isRead = isRead; + return this; + } + + public UserNotice build() { + UserNotice userNotice = new UserNotice(); + userNotice.setUserId(userId); + userNotice.setNoticeId(noticeId); + userNotice.setIsRead(isRead); + return userNotice; + } +} diff --git a/src/test/java/com/zsc/edu/dify/domain/system/AuthorityBuilder.java b/src/test/java/com/zsc/edu/dify/domain/system/AuthorityBuilder.java new file mode 100644 index 0000000..a930c91 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/system/AuthorityBuilder.java @@ -0,0 +1,45 @@ +package com.zsc.edu.dify.domain.system; + +import com.zsc.edu.dify.modules.system.entity.Authority; + +import java.util.Set; + +public class AuthorityBuilder extends BaseEntityBuilder{ + + private String name; + private Boolean enabled; + private String remark; + private Set aut; + + public static AuthorityBuilder aAuthority(){ + return new AuthorityBuilder(); + } + + public AuthorityBuilder name(String name){ + this.name = name; + return this; + } + + public AuthorityBuilder enabled(Boolean enabled){ + this.enabled = enabled; + return this; + } + + public AuthorityBuilder remark(String remark){ + this.remark = remark; + return this; + } + + public AuthorityBuilder authorities(Set authorities){ + this.aut = authorities; + return this; + } + public Authority build(){ + Authority authority = new Authority(); + authority.setName(name); + authority.setEnabled(enabled); + authority.setRemark(remark); + return authority; + } + +} diff --git a/src/test/java/com/zsc/edu/dify/domain/system/BaseEntityBuilder.java b/src/test/java/com/zsc/edu/dify/domain/system/BaseEntityBuilder.java new file mode 100644 index 0000000..a6c47ab --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/system/BaseEntityBuilder.java @@ -0,0 +1,24 @@ +package com.zsc.edu.dify.domain.system; + +import java.time.LocalDateTime; + +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; + +/** + * @author pengzheng + */ +public class BaseEntityBuilder { + public Long id = -1L; + public String remark; + public LocalDateTime createAt; + public LocalDateTime updateAt; + public Long createId; + + public BaseEntityBuilder() { + remark = randomAlphabetic(5); + createAt = LocalDateTime.now(); + updateAt = LocalDateTime.now(); + createId = 1L; + } + +} diff --git a/src/test/java/com/zsc/edu/dify/domain/system/DeptBuilder.java b/src/test/java/com/zsc/edu/dify/domain/system/DeptBuilder.java new file mode 100644 index 0000000..2000a04 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/system/DeptBuilder.java @@ -0,0 +1,51 @@ +package com.zsc.edu.dify.domain.system; + +import com.zsc.edu.dify.modules.system.entity.Dept; + +import java.util.List; + +public class DeptBuilder extends BaseEntityBuilder { + + public String name; + public Dept parent; + public Long pid; + + public List children; + + + public static DeptBuilder aDept(){ + return new DeptBuilder(); + } + + public DeptBuilder name(String name){ + this.name = name; + return this; + } + + + public DeptBuilder parent(Dept parent) { + this.parent = parent; + return this; + } + + public DeptBuilder pid(Long pid) { + this.pid = pid; + return this; + } + + public DeptBuilder children(List children) { + this.children = children; + return this; + } + + + public Dept build(){ + Dept dept = new Dept(); + dept.setName(name); + dept.setPid(pid); + dept.setChildren(children); + return dept; + } + + +} diff --git a/src/test/java/com/zsc/edu/dify/domain/system/RoleBuilder.java b/src/test/java/com/zsc/edu/dify/domain/system/RoleBuilder.java new file mode 100644 index 0000000..bdbeb75 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/system/RoleBuilder.java @@ -0,0 +1,54 @@ +package com.zsc.edu.dify.domain.system; + +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.entity.Role; + +import java.util.HashSet; +import java.util.Set; + +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; + +/** + * @author pengzheng + */ +public final class RoleBuilder extends BaseEntityBuilder { + public String name; + public boolean enable; + public Set authorities; + + private RoleBuilder() { + this.name = randomAlphabetic(5); + this.enable = true; + this.authorities = new HashSet<>(); + } + + public static RoleBuilder aRole() { + return new RoleBuilder(); + } + + public RoleBuilder name(String name) { + this.name = name; + return this; + } + + public RoleBuilder enable(boolean enable) { + this.enable = enable; + return this; + } + + public RoleBuilder authorities(Set authorities) { + this.authorities = authorities; + return this; + } + + public Role build() { + Role role = new Role(); + role.setRemark(remark); + role.setCreateTime(createAt); + role.setUpdateTime(updateAt); + role.setName(name); + role.setEnabled(enable); + role.setAuthorities(authorities); + return role; + } +} diff --git a/src/test/java/com/zsc/edu/dify/domain/system/UserBuilder.java b/src/test/java/com/zsc/edu/dify/domain/system/UserBuilder.java new file mode 100644 index 0000000..d5f6641 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/domain/system/UserBuilder.java @@ -0,0 +1,83 @@ +package com.zsc.edu.dify.domain.system; + +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.User; + +import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; +import static org.apache.commons.lang3.RandomStringUtils.randomAlphanumeric; + +/** + * @author pengzheng + */ +public final class UserBuilder extends BaseEntityBuilder { + public String username; + public String password; + public String phone; + public String email; + public boolean enable; + public Dept dept; + public Role role; + + private UserBuilder() { + this.username = randomAlphabetic(5); + this.password = randomAlphabetic(5); + this.phone = "139" + randomAlphanumeric(8); + this.email = randomAlphanumeric(8) + "@" + randomAlphanumeric(6) + ".com"; + this.enable = true; + } + + public static UserBuilder anUser() { + return new UserBuilder(); + } + + public UserBuilder username(String username) { + this.username = username; + return this; + } + + public UserBuilder password(String password) { + this.password = password; + return this; + } + + public UserBuilder phone(String phone) { + this.phone = phone; + return this; + } + + public UserBuilder email(String email) { + this.email = email; + return this; + } + + public UserBuilder enable(boolean enable) { + this.enable = enable; + return this; + } + + public UserBuilder dept(Dept dept) { + this.dept = dept; + return this; + } + + public UserBuilder role(Role role) { + this.role = role; + return this; + } + + public User build() { + User user = new User(); + user.setRemark(remark); + user.setCreateTime(createAt); + user.setUpdateTime(updateAt); + user.setUsername(username); + user.setPassword(password); + user.setPhone(phone); + user.setEmail(email); + user.setEnableState(enable); + user.setDept(dept); + user.setRole(role); + return user; + } +} diff --git a/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java b/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java new file mode 100644 index 0000000..50946cd --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/modules/system/service/impl/MenuServiceImplTest.java @@ -0,0 +1,130 @@ +package com.zsc.edu.dify.modules.system.service.impl; + +import com.zsc.edu.dify.modules.system.entity.Menu; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.entity.RoleMenu; +import com.zsc.edu.dify.modules.system.repo.RoleMenuRepository; +import com.zsc.edu.dify.modules.system.service.MenuService; +import com.zsc.edu.dify.modules.system.service.RoleService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class MenuServiceImplTest { + + @Autowired + private MenuService menuService; + + @Autowired + private RoleService roleService; + + @Autowired + private RoleMenuRepository roleMenuRepository; + + @Test + public void insertRootMenu() { + Menu dashboard = new Menu(null, Menu.Type.PAGE, "Dashboard", "/dashboard", "dashboard", "icon-dashboard", true, false, 1, "dashboard", ""); + Menu system = new Menu(null, Menu.Type.PAGE, "System", "/system", "menu.system", "icon-computer", true, false, 2, "system", ""); + Menu user = new Menu(null, Menu.Type.PAGE, "User", "/user", "menu.user", "icon-user", true, false, 3, "user", ""); + Menu message = new Menu(null, Menu.Type.PAGE, "Message", "/message", "消息管理", "icon-message", true, false, 4, "message", ""); + Menu iot = new Menu(null, Menu.Type.PAGE, "Iot", "/iot", "物联网管理", "icon-iot", true, false, 5, "iot", ""); + Menu operationLog = new Menu(null, Menu.Type.PAGE, "OperationLog", "/operationLog", "操作日志", "icon-log", true, false, 6, "operationLog", ""); + menuService.saveBatch(List.of(dashboard, system, user, message, iot, operationLog)); + Menu workplace = new Menu(dashboard.getId(), Menu.Type.PAGE, "Workplace", "workplace", "工作台", "icon-dashboard", true, false, 1, "dashboard:workplace", ""); + Menu role = new Menu(system.getId(), Menu.Type.PAGE, "Role", "role", "角色管理", null, true, false, 1, "system:role", ""); + Menu dept = new Menu(system.getId(), Menu.Type.PAGE, "Dept", "dept", "部门管理", null, true, false, 2, "system:dept", ""); + Menu users = new Menu(system.getId(), Menu.Type.PAGE, "User", "user", "用户管理", null, true, false, 3, "system:user", ""); + Menu authority = new Menu(system.getId(), Menu.Type.PAGE, "Authority", "authority", "权限管理", null, true, false, 4, "system:authority", ""); + Menu menu = new Menu(system.getId(), Menu.Type.PAGE, "Menu", "menu", "菜单管理", null, true, false, 5, "system:menu", ""); + Menu notice = new Menu(message.getId(), Menu.Type.PAGE, "Notice", "notice", "通知管理", null, true, false, 1, "message:notice", ""); + Menu bulletin = new Menu(message.getId(), Menu.Type.PAGE, "Bulletin", "bulletin", "公告管理", null, true, false, 2, "message:bulletin", ""); + Menu device = new Menu(iot.getId(), Menu.Type.PAGE, "Device", "device", "设备管理", null, true, false, 1, "iot:device", ""); + Menu product = new Menu(iot.getId(), Menu.Type.PAGE, "Product", "product", "产品管理", null, true, false, 2, "iot:product", ""); + Menu tsl = new Menu(product.getId(), Menu.Type.PAGE, "Tsl", "tsl", "物模型管理", null, true, true, 3, "iot:tsl", ""); + Menu log = new Menu(operationLog.getId(), Menu.Type.PAGE, "OperationLog", "operationLog", "操作日志", null, true, false, 1, "operationLog", ""); + menuService.saveBatch(List.of(workplace, dept, users, authority, menu, notice, bulletin, device, product, tsl, role, log)); + Menu roleCreate = new Menu(role.getId(), Menu.Type.OPERATION, "roleCreate", null, "角色新增", null, true, false, 1, "system:role:create", ""); + Menu roleDelete = new Menu(role.getId(), Menu.Type.OPERATION, "roleDelete", null, "角色删除", null, true, false, 1, "system:role:delete", ""); + Menu roleUpdate = new Menu(role.getId(), Menu.Type.OPERATION, "roleUpdate", null, "角色修改", null, true, false, 1, "system:role:update", ""); + Menu roleQuery = new Menu(role.getId(), Menu.Type.OPERATION, "roleQuery", null, "角色查询", null, true, false, 1, "system:role:query", ""); + Menu deptSave = new Menu(dept.getId(), Menu.Type.OPERATION, "deptCreate", null, "部门新增", null, true, false, 1, "system:dept:create", ""); + Menu deptUpdate = new Menu(dept.getId(), Menu.Type.OPERATION, "deptUpdate", null, "部门修改", null, true, false, 1, "system:dept:update", ""); + Menu deptQuery = new Menu(dept.getId(), Menu.Type.OPERATION, "deptQuery", null, "部门查询", null, true, false, 1, "system:dept:query", ""); + Menu deptDelete = new Menu(dept.getId(), Menu.Type.OPERATION, "deptDelete", null, "部门删除", null, true, false, 1, "system:dept:delete", ""); + Menu userSave = new Menu(dept.getId(), Menu.Type.OPERATION, "userCreate", null, "用户新增", null, true, false, 1, "system:user:create", ""); + Menu userUpdate = new Menu(dept.getId(), Menu.Type.OPERATION, "userUpdate", null, "用户修改", null, true, false, 1, "system:user:update", ""); + Menu userQuery = new Menu(dept.getId(), Menu.Type.OPERATION, "userQuery", null, "用户查询", null, true, false, 1, "system:user:query", ""); + Menu userDelete = new Menu(dept.getId(), Menu.Type.OPERATION, "userDelete", null, "用户删除", null, true, false, 1, "system:user:delete", ""); + Menu menuSave = new Menu(menu.getId(), Menu.Type.OPERATION, "menuCreate", null, "菜单新增", null, true, false, 1, "system:menu:create", ""); + Menu menuUpdate = new Menu(menu.getId(), Menu.Type.OPERATION, "menuUpdate", null, "菜单修改", null, true, false, 1, "system:menu:update", ""); + Menu menuQuery = new Menu(menu.getId(), Menu.Type.OPERATION, "menuQuery", null, "菜单查询", null, true, false, 1, "system:menu:query", ""); + Menu menuDelete = new Menu(menu.getId(), Menu.Type.OPERATION, "menuDelete", null, "菜单删除", null, true, false, 1, "system:menu:delete", ""); + Menu noticeCreate = new Menu(notice.getId(), Menu.Type.OPERATION, "noticeCreate", null, "通知新增", null, true, false, 1, "message:notice:create", ""); + Menu noticeUpdate = new Menu(notice.getId(), Menu.Type.OPERATION, "noticeUpdate", null, "通知修改", null, true, false, 1, "message:notice:update", ""); + Menu noticeQuery = new Menu(notice.getId(), Menu.Type.OPERATION, "noticeQuery", null, "通知查询", null, true, false, 1, "message:notice:query", ""); + Menu noticeDelete = new Menu(notice.getId(), Menu.Type.OPERATION, "noticeDelete", null, "通知删除", null, true, false, 1, "message:notice:delete", ""); + Menu bulletinCreate = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinCreate", null, "公告新增", null, true, false, 1, "message:bulletin:create", ""); + Menu bulletinUpdate = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinUpdate", null, "公告修改", null, true, false, 1, "message:bulletin:update", ""); + Menu bulletinQuery = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinQuery", null, "公告查询", null, true, false, 1, "message:bulletin:query", ""); + Menu bulletinDelete = new Menu(bulletin.getId(), Menu.Type.OPERATION, "bulletinDelete", null, "公告删除", null, true, false, 1, "message:bulletin:delete", ""); + Menu deviceQuery = new Menu(device.getId(), Menu.Type.OPERATION, "deviceQuery", null, "设备查询", null, true, false, 1, "iot:device:query", ""); + Menu deviceCreate = new Menu(device.getId(), Menu.Type.OPERATION, "deviceCreate", null, "设备新增", null, true, false, 1, "iot:device:create", ""); + Menu deviceUpdate = new Menu(device.getId(), Menu.Type.OPERATION, "deviceUpdate", null, "设备修改", null, true, false, 1, "iot:device:update", ""); + Menu deviceDelete = new Menu(device.getId(), Menu.Type.OPERATION, "deviceDelete", null, "设备删除", null, true, false, 1, "iot:device:delete", ""); + Menu productQuery = new Menu(product.getId(), Menu.Type.OPERATION, "productQuery", null, "产品查询", null, true, false, 1, "iot:product:query", ""); + Menu productCreate = new Menu(product.getId(), Menu.Type.OPERATION, "productCreate", null, "产品新增", null, true, false, 1, "iot:product:create", ""); + Menu productUpdate = new Menu(product.getId(), Menu.Type.OPERATION, "productUpdate", null, "产品修改", null, true, false, 1, "iot:product:update", ""); + Menu productDelete = new Menu(product.getId(), Menu.Type.OPERATION, "productDelete", null, "产品删除", null, true, false, 1, "iot:product:delete", ""); + Menu eventQuery = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventQuery", null, "事件查询", null, true, false, 1, "iot:event:query", ""); + Menu eventCreate = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventCreate", null, "事件新增", null, true, false, 1, "iot:event:create", ""); + Menu eventUpdate = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventUpdate", null, "事件修改", null, true, false, 1, "iot:event:update", ""); + Menu eventDelete = new Menu(tsl.getId(), Menu.Type.OPERATION, "eventDelete", null, "事件删除", null, true, false, 1, "iot:event:delete", ""); + Menu propertyQuery = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyQuery", null, "属性查询", null, true, false, 1, "iot:property:query", ""); + Menu propertyCreate = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyCreate", null, "属性新增", null, true, false, 1, "iot:property:create", ""); + Menu propertyUpdate = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyUpdate", null, "属性修改", null, true, false, 1, "iot:property:update", ""); + Menu propertyDelete = new Menu(tsl.getId(), Menu.Type.OPERATION, "propertyDelete", null, "属性删除", null, true, false, 1, "iot:property:delete", ""); + Menu serverQuery = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverQuery", null, "服务查询", null, true, false, 1, "iot:server:query", ""); + Menu serverCreate = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverCreate", null, "服务新增", null, true, false, 1, "iot:server:create", ""); + Menu serverUpdate = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverUpdate", null, "服务修改", null, true, false, 1, "iot:server:update", ""); + Menu serverDelete = new Menu(tsl.getId(), Menu.Type.OPERATION, "serverDelete", null, "服务删除", null, true, false, 1, "iot:server:delete", ""); + Menu operationLogQuery = new Menu(operationLog.getId(), Menu.Type.OPERATION, "operationLogQuery", null, "操作日志查询", null, true, false, 1, "operationLog:query", ""); + Menu operationLogDelete = new Menu(operationLog.getId(), Menu.Type.OPERATION, "operationLogDelete", null, "操作日志删除", null, true, false, 1, "operationLog:delete", ""); + menuService.saveBatch(List.of(roleCreate, roleDelete, roleUpdate, roleQuery, + deptSave, deptUpdate, deptQuery, deptDelete, + userSave, userUpdate, userQuery, userDelete, + menuSave, menuUpdate, menuQuery, menuDelete, + noticeCreate, noticeUpdate, noticeQuery, noticeDelete, + bulletinCreate, bulletinUpdate, bulletinQuery, bulletinDelete, + deviceQuery, deviceCreate, deviceUpdate, deviceDelete, + productQuery, productCreate, productUpdate, productDelete, + eventQuery, eventCreate, eventUpdate, eventDelete, + propertyQuery, propertyCreate, propertyUpdate, propertyDelete, + serverQuery, serverCreate, serverUpdate, serverDelete, + operationLogQuery, operationLogDelete + )); + } + + @Test + public void test() { + Role admin = roleService.lambdaQuery().eq(Role::getName, "admin").one(); + menuService.list().forEach(menu -> roleMenuRepository.insert(new RoleMenu(admin.getId(), menu.getId()))); + } + + @Test + public void testRole() { + Role admin = roleService.lambdaQuery().eq(Role::getName, "admin").one(); + List menus = menuService.selectByRoleId(admin.getId()); + assertEquals(27, menus.size()); + } + + @Test + public void test2() { + Menu menu = menuService.getById(7L); + System.out.println(menu.toString()); + } + +} \ No newline at end of file diff --git a/src/test/java/com/zsc/edu/dify/rest/message/BulletinControllerTest.java b/src/test/java/com/zsc/edu/dify/rest/message/BulletinControllerTest.java new file mode 100644 index 0000000..a15fb9d --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/rest/message/BulletinControllerTest.java @@ -0,0 +1,93 @@ +package com.zsc.edu.dify.rest.message; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.MockMvcConfigBase; +import com.zsc.edu.dify.domain.message.BulletinBuilder; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.controller.BulletinController; +import com.zsc.edu.dify.modules.message.dto.BulletinDto; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import com.zsc.edu.dify.modules.message.service.BulletinService; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(BulletinController.class) +public class BulletinControllerTest extends MockMvcConfigBase { + @Spy + private static Bulletin bulletin1; + private static Bulletin bulletin2; + @MockBean + private BulletinService service; + + @BeforeAll + static void beforeAll() { + bulletin1 = BulletinBuilder.bBulletin().title("title1").content("content1").top(true).build(); + bulletin1.setId(1L); + bulletin2 = BulletinBuilder.bBulletin().title("title2").content("content2").top(false).build(); + } + + @Test + void create() throws Exception { + BulletinDto dto = new BulletinDto(); + dto.setTitle(bulletin1.getTitle()); + dto.setContent(bulletin1.getContent()); + dto.setTop(bulletin1.getTop()); + when(service.create(any(UserDetailsImpl.class), any(BulletinDto.class))).thenReturn(bulletin1); + mockMvc.perform(post("/api/rest/bulletin") + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ) + .andExpect(status().isOk()) + .andDo(print()); + verify(service).create(any(UserDetailsImpl.class), any(BulletinDto.class)); + } + + @Test + void list() throws Exception { + List bulletins = Lists.newArrayList(bulletin1, bulletin2); + when(service.list()).thenReturn(bulletins); + mockMvc.perform(get("/api/rest/bulletin").with(user(userDetails)) + ).andExpect(status().isOk()).andDo(print()); + verify(service).page(any(Page.class), any(LambdaQueryWrapper.class)); + } + + @Test + void update() throws Exception { + BulletinDto dto = new BulletinDto(); + dto.setTitle(bulletin1.getTitle()); + dto.setContent(bulletin1.getContent()); + dto.setTop(bulletin1.getTop()); + when(service.update(any(UserDetailsImpl.class), any(BulletinDto.class), anyLong())).thenReturn(true); + mockMvc.perform(patch("/api/rest/bulletin/{id}", bulletin1.getId()) + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ) + .andExpect(status().isOk()) + .andDo(print()); + verify(service).update(any(UserDetailsImpl.class), any(BulletinDto.class), anyLong()); + } + + +} diff --git a/src/test/java/com/zsc/edu/dify/rest/message/UserNoticeControllerTest.java b/src/test/java/com/zsc/edu/dify/rest/message/UserNoticeControllerTest.java new file mode 100644 index 0000000..69c45de --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/rest/message/UserNoticeControllerTest.java @@ -0,0 +1,78 @@ +package com.zsc.edu.dify.rest.message; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.MockMvcConfigBase; +import com.zsc.edu.dify.domain.message.UserNoticeBuilder; +import com.zsc.edu.dify.modules.message.controller.UserNoticeController; +import com.zsc.edu.dify.modules.message.dto.UserNoticeDto; +import com.zsc.edu.dify.modules.message.entity.NoticeType; +import com.zsc.edu.dify.modules.message.entity.UserNotice; +import com.zsc.edu.dify.modules.message.query.AdminNoticeQuery; +import com.zsc.edu.dify.modules.message.service.UserNoticeService; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(UserNoticeController.class) +public class UserNoticeControllerTest extends MockMvcConfigBase { + @Spy + private static UserNotice userNotice1; + private static UserNotice userNotice2; + @MockBean + private UserNoticeService service; + + @BeforeAll + static void beforeAll() { + userNotice1 = UserNoticeBuilder.builder().setNoticeId(1L).setUserId(1L).setIsRead(true).build(); + userNotice2 = UserNoticeBuilder.builder().setNoticeId(1L).setUserId(1L).setIsRead(false).build(); + } + + @Test + void list() throws Exception { + List userNotices = Lists.newArrayList(userNotice1, userNotice2); + Page pageResult = new Page<>(); + pageResult.setRecords(userNotices); + pageResult.setTotal((long) userNotices.size()); + when(service.getAdminNoticePage(any(Page.class), any(AdminNoticeQuery.class))).thenReturn(pageResult); + mockMvc.perform(get("/api/rest/message") + .with(user(userDetails)) + ) + .andExpect(status().isOk()) + .andDo(print()); + verify(service).getAdminNoticePage(any(Page.class), any(AdminNoticeQuery.class)); + } + + @Test + void create() throws Exception { + UserNoticeDto dto = new UserNoticeDto(); + dto.setTitle("测试创建消息"); + dto.setContent("测试创建消息"); + dto.setType(NoticeType.MESSAGE); + when(service.createByAdmin(any(UserNoticeDto.class))).thenReturn(true); + mockMvc.perform(post("/api/rest/message") + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ) + .andExpect(status().isOk()) + .andDo(print()); + verify(service).createByAdmin(any(UserNoticeDto.class)); + } +} diff --git a/src/test/java/com/zsc/edu/dify/rest/system/AuthorityControllerTest.java b/src/test/java/com/zsc/edu/dify/rest/system/AuthorityControllerTest.java new file mode 100644 index 0000000..6e64f2d --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/rest/system/AuthorityControllerTest.java @@ -0,0 +1,85 @@ +package com.zsc.edu.dify.rest.system; + +import com.zsc.edu.dify.MockMvcConfigBase; +import com.zsc.edu.dify.domain.system.AuthorityBuilder; +import com.zsc.edu.dify.modules.system.controller.AuthorityController; +import com.zsc.edu.dify.modules.system.dto.AuthorityDto; +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.service.AuthorityService; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(AuthorityController.class) +public class AuthorityControllerTest extends MockMvcConfigBase { + + @Spy + private static Authority aut1; + + private static Authority aut2; + + @MockBean + private AuthorityService service; + + + @BeforeAll + static void beforeAll() { + aut1 = AuthorityBuilder.aAuthority().name("AUTHORITY_ONE").build(); + aut1.setId(1L); + aut2 = AuthorityBuilder.aAuthority().name("AUTHORITY_TWO").build(); + } + + @Test + void create() throws Exception { + AuthorityDto dto = new AuthorityDto(); + dto.name=aut1.getName(); + when(service.create(any())).thenReturn(aut1); + mockMvc.perform(post("/api/rest/auth") + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isOk()).andDo(print()); + verify(service).create(any()); + } + + @Test + void list() throws Exception { + List authorities = Lists.newArrayList(aut1, aut2); + when(service.list()).thenReturn(authorities); + mockMvc.perform(get("/api/rest/auth").with(user(userDetails)) + ).andExpect(status().isOk()).andDo(print()); + verify(service).list(); + } + + + @Test + void update() throws Exception { + AuthorityDto dto = new AuthorityDto(); + dto.name = aut1.getName(); + when(service.update((AuthorityDto) any(), any())).thenReturn(true); + mockMvc.perform(patch("/api/rest/auth/{id}", aut1.id) + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ) + .andExpect(status().isOk()).andDo(print()); + verify(service).update((AuthorityDto) any(), any()); + } +} diff --git a/src/test/java/com/zsc/edu/dify/rest/system/DeptControllerTest.java b/src/test/java/com/zsc/edu/dify/rest/system/DeptControllerTest.java new file mode 100644 index 0000000..8cd65de --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/rest/system/DeptControllerTest.java @@ -0,0 +1,94 @@ +package com.zsc.edu.dify.rest.system; + +//import com.zsc.edu.dify.MockMvcConfigBase; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.MockMvcConfigBase; +import com.zsc.edu.dify.domain.system.DeptBuilder; +import com.zsc.edu.dify.modules.system.controller.DeptController; +import com.zsc.edu.dify.modules.system.dto.DeptDto; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.service.DeptService; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(DeptController.class) +public class DeptControllerTest extends MockMvcConfigBase { + + @Spy + private static Dept dept1; + + private static Dept dept2; + + @MockBean + private DeptService service; + + + + @BeforeAll + static void beforeAll() { + dept1 = DeptBuilder.aDept().name("测试部门1").build(); + dept1.setId(1L); + dept2 = DeptBuilder.aDept().name("测试部门2").pid(dept1.id).build(); + } + + @Test + void create() throws Exception{ + DeptDto dto = new DeptDto(); + dto.name = dept1.getName(); + when(service.create(any())).thenReturn(dept1); + mockMvc.perform(post("/api/rest/dept") + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isOk()).andDo(print()); + verify(service).create(any()); + } + + @Test + void list() throws Exception { + List depts = Lists.newArrayList(dept1, dept2); + Page pageResult = new Page<>(); + pageResult.setRecords(depts); + pageResult.setTotal(depts.size()); + + when(service.page(any(Page.class), any())).thenReturn(pageResult); + mockMvc.perform(get("/api/rest/dept") + .with(user(userDetails)) + ).andExpect(status().isOk()) + .andDo(print()); + verify(service).page(any(Page.class), any()); + } + + @Test + void update() throws Exception { + DeptDto dto = new DeptDto(); + dto.name = dept1.getName(); + when(service.edit(any(), any())).thenReturn(true); + mockMvc.perform(patch("/api/rest/dept/1") + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ) + .andExpect(status().isOk()).andDo(print()); + verify(service).edit(any(), any()); + } +} diff --git a/src/test/java/com/zsc/edu/dify/rest/system/RoleControllerTest.java b/src/test/java/com/zsc/edu/dify/rest/system/RoleControllerTest.java new file mode 100644 index 0000000..d681e11 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/rest/system/RoleControllerTest.java @@ -0,0 +1,92 @@ +package com.zsc.edu.dify.rest.system; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.zsc.edu.dify.MockMvcConfigBase; +import com.zsc.edu.dify.domain.system.RoleBuilder; +import com.zsc.edu.dify.modules.system.controller.RoleController; +import com.zsc.edu.dify.modules.system.dto.RoleDto; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.service.RoleAuthService; +import com.zsc.edu.dify.modules.system.service.RoleService; +import org.assertj.core.util.Lists; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.mockito.Spy; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; + +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@WebMvcTest(RoleController.class) +public class RoleControllerTest extends MockMvcConfigBase { + @Spy + private static Role role1; + + private static Role role2; + + @MockBean + private RoleService service; + @MockBean + private RoleAuthService authService; + + @BeforeAll + static void beforeAll() { + role1 = RoleBuilder.aRole().name("管理员").build(); + role1.setId(1L); + role2 = RoleBuilder.aRole().name("普通用户").build(); + role2.setId(2L); + } + + @Test + void create() throws Exception { + RoleDto dto = new RoleDto(); + dto.setName(role1.getName()); + when(service.create(any())).thenReturn(role1); + mockMvc.perform(post("/api/rest/role") + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isOk()).andDo(print()); + verify(service).create(any()); + } + + @Test + void list() throws Exception { + List roles = Lists.newArrayList(role1, role2); + Page pageResult = new Page<>(); + pageResult.setRecords(roles); + pageResult.setTotal((long) roles.size()); + + when(service.page(any(Page.class), any())).thenReturn(pageResult); + mockMvc.perform(get("/api/rest/role") + .with(user(userDetails)) + ).andExpect(status().isOk()) + .andDo(print()); + verify(service).page(any(Page.class), any()); + } + + @Test + void update() throws Exception { + RoleDto dto = new RoleDto(); + dto.setName(role1.getName()); +// when(service.edit(any(), any())).thenReturn(true); + mockMvc.perform(patch("/api/rest/role/{id}", role1.getId()) + .with(csrf().asHeader()) + .with(user(userDetails)) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(dto)) + ).andExpect(status().isOk()).andDo(print()); + verify(service).edit(any(), any()); + } +} diff --git a/src/test/java/com/zsc/edu/dify/service/notice/BulletinServiceTest.java b/src/test/java/com/zsc/edu/dify/service/notice/BulletinServiceTest.java new file mode 100644 index 0000000..24bc9f0 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/service/notice/BulletinServiceTest.java @@ -0,0 +1,93 @@ +package com.zsc.edu.dify.service.notice; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.zsc.edu.dify.domain.message.BulletinBuilder; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.BulletinDto; +import com.zsc.edu.dify.modules.message.entity.Bulletin; +import com.zsc.edu.dify.modules.message.repo.BulletinRepository; +import com.zsc.edu.dify.modules.message.service.BulletinService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Arrays; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +public class BulletinServiceTest { + + @Resource + private BulletinService service; + @Autowired + private BulletinRepository repo; + + private Bulletin bulletin1; + private Bulletin bulletin2; + private UserDetailsImpl userDetails; + + @BeforeEach + void setUp() { + userDetails = new UserDetailsImpl(); + userDetails.setUsername("admin"); + bulletin1 = createAndInsertBulletin("测试1"); + bulletin2 = createAndInsertBulletin("测试2"); + } + + @AfterEach + void tearDown() { + // 清理所有测试公告 + List titlesToDelete = Arrays.asList("测试1", "测试2", "测试3", "测试"); + repo.delete(new QueryWrapper().in("title", titlesToDelete)); + } + + private Bulletin createAndInsertBulletin(String title) { + Bulletin bulletin = BulletinBuilder.bBulletin().top(false).title(title).build(); + repo.insert(bulletin); + return bulletin; + } + + @Test + void list() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + assertEquals(2, service.list(queryWrapper.like(Bulletin::getTitle, "测试")).size()); + assertEquals(1, service.list(queryWrapper.eq(Bulletin::getTitle, bulletin1.getTitle())).size()); +// assertEquals(2, service.list().size()); + } + + @Test + void createBulletin() { + BulletinDto dto = createBulletinDto("测试", true, "测试测试", "测试公告增加"); + Bulletin bulletin = service.create(userDetails, dto); + assertNotNull(bulletin.getId()); + assertThrows(ConstraintException.class, () -> service.create(userDetails, createBulletinDto(bulletin2.getTitle(), false, "", ""))); + } + + @Test + void updateBulletin() { + BulletinDto dto = createBulletinDto("测试3", true, "测试更新", "测试公告更新"); + assertTrue(service.update(userDetails, dto, bulletin2.getId())); + Bulletin updatedBulletin = service.getOne(new LambdaQueryWrapper() + .eq(Bulletin::getTitle, dto.getTitle())); + assertNotNull(updatedBulletin); + assertEquals(dto.getTitle(), updatedBulletin.getTitle()); + assertEquals(bulletin2.getId(), updatedBulletin.getId()); + } + + + private BulletinDto createBulletinDto(String title, boolean top, String content, String remark) { + BulletinDto dto = new BulletinDto(); + dto.setTitle(title); + dto.setTop(top); + dto.setContent(content); + dto.setRemark(remark); + return dto; + } +} diff --git a/src/test/java/com/zsc/edu/dify/service/notice/NoticeServiceTest.java b/src/test/java/com/zsc/edu/dify/service/notice/NoticeServiceTest.java new file mode 100644 index 0000000..44db155 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/service/notice/NoticeServiceTest.java @@ -0,0 +1,67 @@ +package com.zsc.edu.dify.service.notice; + + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.zsc.edu.dify.domain.message.NoticeBuilder; +import com.zsc.edu.dify.framework.security.UserDetailsImpl; +import com.zsc.edu.dify.modules.message.dto.UserNoticeDto; +import com.zsc.edu.dify.modules.message.entity.Notice; +import com.zsc.edu.dify.modules.message.entity.NoticeType; +import com.zsc.edu.dify.modules.message.repo.NoticeRepository; +import com.zsc.edu.dify.modules.message.repo.UserNoticeRepository; +import com.zsc.edu.dify.modules.message.service.UserNoticeService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +public class NoticeServiceTest { + @Autowired + public NoticeRepository noticeRepository; + @Autowired + public UserNoticeRepository userNoticeRepository; + @Autowired + public UserNoticeService service; + + private Notice notice1; + + @BeforeEach + void setup() { + UserDetailsImpl userDetails = new UserDetailsImpl(); + userDetails.setUsername("admin"); + notice1 = NoticeBuilder.bMessage().type(NoticeType.MESSAGE).title("A测试消息1").build(); + noticeRepository.insert(notice1); + + } + + @AfterEach + void tearDown() { + List titlesToDelete = Arrays.asList("A测试消息1", "A测试消息3"); + noticeRepository.delete(new QueryWrapper().in("title", titlesToDelete)); + } + + @Test + void list() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + assertEquals(1, noticeRepository.selectList(queryWrapper.like(Notice::getTitle, "A测试")).size()); + assertEquals(1, noticeRepository.selectList(queryWrapper.eq(Notice::getTitle, notice1.getTitle())).size()); +// assertEquals(2, messageRepository.selectList(null).size()); + } + + @Test + void createMessage() { + UserNoticeDto dto = new UserNoticeDto(new HashSet<>(Arrays.asList(1L, 2L)), NoticeType.MESSAGE, false, false, false, "A测试消息3", "测试测试"); + service.createByAdmin(dto); + Notice notice = noticeRepository.selectOne(new LambdaQueryWrapper().eq(Notice::getTitle, dto.getTitle())); + assertNotNull(notice.getId()); + } +} diff --git a/src/test/java/com/zsc/edu/dify/service/system/AuthorityServiceTest.java b/src/test/java/com/zsc/edu/dify/service/system/AuthorityServiceTest.java new file mode 100644 index 0000000..9f0ba1e --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/service/system/AuthorityServiceTest.java @@ -0,0 +1,88 @@ +package com.zsc.edu.dify.service.system; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.domain.system.AuthorityBuilder; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.modules.system.dto.AuthorityDto; +import com.zsc.edu.dify.modules.system.entity.Authority; +import com.zsc.edu.dify.modules.system.repo.AuthorityRepository; +import com.zsc.edu.dify.modules.system.service.AuthorityService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +public class AuthorityServiceTest { + @Resource + private AuthorityRepository repo; + @Autowired + private AuthorityService service; + + + private Authority aut1; + private Authority aut2; + + @BeforeEach + void setUp() { + aut1 = AuthorityBuilder.aAuthority().name("TEST_AUTHORITY_ONE").build(); + repo.insert(aut1); + aut2 = AuthorityBuilder.aAuthority().name("TEST_AUTHORITY_TWO").build(); + repo.insert(aut2); + } + + @Test + void list() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + assertEquals(2, service.list(queryWrapper.like(Authority::getName, "TEST_AUTHORITY")).size()); + assertEquals(1, service.list(queryWrapper.eq(Authority::getName, aut1.getName())).size()); + assertEquals(2, service.list().size()); + } + + @Test + void createAuthority() { + AuthorityDto dto = new AuthorityDto(); + dto.setName("TEST_AUTHORITY"); + dto.setEnabled(true); + dto.setRemark("测试权限增加"); + AuthorityDto dto2 = new AuthorityDto(); + dto2.setName(aut2.getName()); + dto2.setEnabled(aut2.getEnabled()); + dto2.setRemark(aut2.getRemark()); + Authority authority=service.create(dto); + assertNotNull(authority.getId()); + List list = service.list(); + assertEquals(3, list.size()); + // 不能创建其他已存在的同名同代码部门 + assertThrows(ConstraintException.class, () -> service.create(dto2)); + } + + @Test + void updateAuthority() { + AuthorityDto dto = new AuthorityDto(); + dto.setName("TEST_AUTHORITY_BBS"); + dto.setRemark("测试权限增加..."); + assertTrue(service.update(dto, aut2.id)); + Authority authority = service.getOne(new LambdaQueryWrapper().eq(Authority::getName, dto.getName())); + assertEquals(authority.getName(), dto.getName()); + assertEquals(authority.getId(), aut2.id); + // 不能改为其他已存在的同名同代码部门 + assertThrows(ConstraintException.class, + () -> service.update(new AuthorityDto(aut1.getName(), true,null), aut2.id)); + } + +// @AfterEach +// void tearDown() { +// repo.delete(new QueryWrapper<>()); +// } + @Test + void get() { + service.getById(1); + } + +} diff --git a/src/test/java/com/zsc/edu/dify/service/system/DeptServiceTest.java b/src/test/java/com/zsc/edu/dify/service/system/DeptServiceTest.java new file mode 100644 index 0000000..d3c70f4 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/service/system/DeptServiceTest.java @@ -0,0 +1,115 @@ +package com.zsc.edu.dify.service.system; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.zsc.edu.dify.domain.system.DeptBuilder; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.modules.system.dto.DeptDto; +import com.zsc.edu.dify.modules.system.entity.Dept; +import com.zsc.edu.dify.modules.system.repo.DeptRepository; +import com.zsc.edu.dify.modules.system.service.DeptService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.dao.DuplicateKeyException; + +import jakarta.annotation.Resource; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author pengzheng + */ +//@ActiveProfiles("test") +@SpringBootTest +class DeptServiceTest { + + @Autowired + private DeptService service; + + @Resource + private DeptRepository repo; + + private Dept dept1; + private Dept dept2; + + private Dept dept3; + private Dept dept4; + + @BeforeEach + void setUp() { + dept1 = DeptBuilder.aDept().name("A测试部门1").build(); + dept1.setId(121L); + repo.insert(dept1); + dept2 = DeptBuilder.aDept().name("A测试部门2").pid(dept1.id).build(); + dept2.setId(122L); + repo.insert(dept2); + dept3 = DeptBuilder.aDept().name("A测试部门3").pid(dept1.id).build(); + dept3.setId(123L); + repo.insert(dept3); + dept4 = DeptBuilder.aDept().name("A测试部门4").pid(dept3.id).build(); + dept4.setId(124L); + repo.insert(dept4); + } + + @AfterEach + void tearDown() { + repo.delete(new LambdaQueryWrapper().in(Dept::getName, "A测试部门1", "A测试部门2", "A测试部门3", "A测试部门4", "A东菱经销商3", "A东菱经销商5")); + } + + @Test + void list() { + LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>(); + assertEquals(4, service.list(queryWrapper.like(Dept::getName, "A测试部门")).size()); + assertEquals(1, service.list(queryWrapper.eq(Dept::getName, dept1.getName())).size()); +// assertEquals(4, service.list().size()); + } + + + @Test + void createAdmin() { + DeptDto dto = new DeptDto(); + dto.setName("A东菱经销商3"); + dto.setRemark("remark..."); + dto.setPid(dept1.id); + service.create(dto); + assertEquals(5, service.list(new LambdaQueryWrapper().like(Dept::getName, "A")).size()); + // 不能创建其他已存在的同名同代码部门 + assertThrows(DuplicateKeyException.class, () -> service.save(dept1)); + } + + + @Test + void updateAdmin() { + DeptDto dto = new DeptDto(); + dto.setName("A东菱经销商5"); + dto.setRemark("remark..."); + assertTrue(service.edit(dto, dept2.id)); + Dept tmp = service.getOne(new LambdaQueryWrapper().eq(Dept::getName, dto.getName())); + assertEquals(tmp.getName(), dto.getName()); + assertEquals(tmp.getId(), dept2.id); + // 不能改为其他已存在的同名同代码部门 + assertThrows(ConstraintException.class, + () -> service.edit(new DeptDto(dept3.getName(), "remark",null), dept2.id)); + } + + + @Test + void tree() { + List results = service.listTree(dept3.id); + System.out.println(results); + assertEquals(1, results.size()); + } + + @Test + void findTreeChild() { + List childNode = service.listTree(dept3.id); + Dept dept = childNode.get(0); + System.out.println(dept.id); + assertEquals(dept3.id, dept.id); + } + +} diff --git a/src/test/java/com/zsc/edu/dify/service/system/RoleServiceTest.java b/src/test/java/com/zsc/edu/dify/service/system/RoleServiceTest.java new file mode 100644 index 0000000..696681f --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/service/system/RoleServiceTest.java @@ -0,0 +1,78 @@ +package com.zsc.edu.dify.service.system; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.zsc.edu.dify.domain.system.RoleBuilder; +import com.zsc.edu.dify.exception.ConstraintException; +import com.zsc.edu.dify.modules.system.dto.RoleDto; +import com.zsc.edu.dify.modules.system.entity.Role; +import com.zsc.edu.dify.modules.system.repo.RoleRepository; +import com.zsc.edu.dify.modules.system.service.RoleService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import jakarta.annotation.Resource; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author harry yao + */ +//@ActiveProfiles("test") +@SpringBootTest +class RoleServiceTest { + + @Autowired + private RoleService service; + + @Resource + private RoleRepository repo; + + private Role role1; + private Role role2; + + @BeforeEach + void setUp() { + role1 = RoleBuilder.aRole().name("A超级管理员").build(); + repo.insert(role1); + role2 = RoleBuilder.aRole().name("A普通用户").build(); + repo.insert(role2); + } + + @AfterEach + void tearDown() { + repo.delete(new LambdaQueryWrapper().in(Role::getName, "A超级管理员", "A普通用户", "A公司CEO", "A项目经理", "A超级管理员2")); + } + + @Test + void list() { + assertEquals(2, service.list(new LambdaQueryWrapper().like(Role::getName, "A")).size()); + assertEquals(1, service.list(new LambdaQueryWrapper().eq(Role::getName, role1.getName())).size()); +// assertEquals(1, service.list().size()); + } + + @Test + void create() { + RoleDto dto = new RoleDto(); + dto.setName("A公司CEO"); + dto.setRemark("remark..."); +// dto.setAuthorities(null); + Role Role = service.create(dto); + assertNotNull(Role.getId()); + assertEquals(3, service.list(new QueryWrapper().like("name", "A")).size()); + // 不能创建其他已存在的同名同代码部门 + assertThrows(ConstraintException.class, () -> service.create(new RoleDto(role1.getName(), "remark...", null))); + } + + @Test + void update() { + RoleDto dto = new RoleDto(); + dto.setName("A超级管理员2"); + dto.setRemark("remark..."); +// dto.setAuthorities(null); +// assertTrue(service.edit(dto, role2.id)); + } +} diff --git a/src/test/java/com/zsc/edu/dify/service/system/UserServiceTest.java b/src/test/java/com/zsc/edu/dify/service/system/UserServiceTest.java new file mode 100644 index 0000000..ed84ef3 --- /dev/null +++ b/src/test/java/com/zsc/edu/dify/service/system/UserServiceTest.java @@ -0,0 +1,108 @@ +package com.zsc.edu.dify.service.system; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.zsc.edu.dify.modules.system.dto.UserCreateDto; +import com.zsc.edu.dify.modules.system.dto.UserUpdateDto; +import com.zsc.edu.dify.modules.system.entity.User; +import com.zsc.edu.dify.modules.system.repo.AuthorityRepository; +import com.zsc.edu.dify.modules.system.repo.RoleAuthoritiesRepository; +import com.zsc.edu.dify.modules.system.repo.UserRepository; +import com.zsc.edu.dify.modules.system.service.UserService; +import jakarta.annotation.Resource; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author ftz + * 创建时间:29/12/2023 上午11:21 + */ +@SpringBootTest +public class UserServiceTest { + @Resource + private RoleAuthoritiesRepository roleAuthoritiesRepository; + @Resource + private UserService service; + @Resource + private UserRepository repo; + @Resource + private AuthorityRepository authorityRepository; + + @Resource + private PasswordEncoder passwordEncoder; + + private User user1; + private User user2; + private User user3; + +// @BeforeEach +// void setUp() { +// user1 = UserBuilder.anUser().username("A张三").password("123456").enable(true).build(); +// repo.insert(user1); +// user2 = UserBuilder.anUser().username("A李四").password("123456").enable(true).build(); +// repo.insert(user2); +// user3 = UserBuilder.anUser().username("A王五").password("123456").enable(true).build(); +// repo.insert(user3); +// } +// +// @AfterEach +// void tearDown() { +// repo.delete(new LambdaQueryWrapper().in(User::getId, user1.getId(), user2.getId(), user3.getId())); +// } + + @Test + void list() { + assertEquals(3, service.list(new LambdaQueryWrapper().like(User::getUsername, "A")).size()); + assertEquals(1, service.list(new LambdaQueryWrapper().eq(User::getUsername, user1.getUsername())).size()); +// assertEquals(1, service.list().size()); + } + + @Test + void create() { + UserCreateDto dto = new UserCreateDto(); + dto.setUsername("A赵六"); + dto.setRemark("remark..."); + dto.setPassword("654321"); + dto.setDeptId(1L); + dto.setEnable(true); + dto.setRoleId(1L); + dto.setEmail("@123.com"); + dto.setPhone("14315367689"); + assertTrue(service.create(dto)); + assertEquals(4, service.list(new QueryWrapper().like("username", "A")).size()); + } + + @Test + void update() { + UserUpdateDto dto = new UserUpdateDto(); + dto.setEnable(false); + dto.setRemark("remark..."); + dto.setPhone("16786899221"); + dto.setEmail("@abc.com"); + assertTrue(service.update(dto, user2.id)); +// UserUpdateDto dto2 = new UserUpdateDto("16786899221", "@141.com", true, 1L, +// "admin", "admin", "admin", 1L, "remark..."); +// UserUpdateDto dto3 = new UserUpdateDto("16783399221", "@abc.com", true, 1L, +// "admin", "admin", "admin", 1L, "remark..."); + // 不能创建其他已存在的电话和邮箱 +// assertThrows(ConstraintException.class, () -> service.update(dto2, user2.getId())); +// assertThrows(ConstraintException.class, () -> service.update(dto3, user2.getId())); + } + + @Test + void updatePassword() { + assertTrue(service.updatePassword("777777", user3.id)); + } + + @Test + void getOne() { + User byId = repo.selectByUsername("admin"); + assertNotNull(byId.role.dataScope); + } + + +}