1、结构化调整;

This commit is contained in:
2026-03-31 19:58:48 +08:00
parent ebdbdbeb41
commit e78565ea5a
369 changed files with 3790 additions and 1195 deletions

View File

@@ -0,0 +1,21 @@
package com.njcn.msgpush.module.system.api.config;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 参数配置")
public interface ConfigApi {
String PREFIX = ApiConstants.PREFIX + "/config";
@GetMapping(PREFIX + "/get-value-by-key")
@Operation(summary = "根据参数键查询参数值")
CommonResult<String> getConfigValueByKey(@RequestParam("key") String key);
}

View File

@@ -1,12 +1,12 @@
package com.njcn.msgpush.module.system.api.user;
import cn.hutool.core.convert.Convert;
import com.fhs.core.trans.anno.AutoTrans;
import com.fhs.trans.service.AutoTransable;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.collection.CollectionUtils;
import com.njcn.msgpush.module.system.api.user.dto.AdminUserRespDTO;
import com.njcn.msgpush.module.system.enums.ApiConstants;
import com.fhs.core.trans.anno.AutoTrans;
import com.fhs.trans.service.AutoTransable;
import feign.FeignIgnore;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -22,7 +22,7 @@ import java.util.Map;
import static com.njcn.msgpush.module.system.api.user.AdminUserApi.PREFIX;
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - 管理员用户")
@AutoTrans(namespace = PREFIX, fields = {"nickname"})
public interface AdminUserApi extends AutoTransable<AdminUserRespDTO> {
@@ -34,11 +34,6 @@ public interface AdminUserApi extends AutoTransable<AdminUserRespDTO> {
@Parameter(name = "id", description = "用户编号", example = "1", required = true)
CommonResult<AdminUserRespDTO> getUser(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/list-by-subordinate")
@Operation(summary = "通过用户 ID 查询用户下属")
@Parameter(name = "id", description = "用户编号", example = "1", required = true)
CommonResult<List<AdminUserRespDTO>> getUserListBySubordinate(@RequestParam("id") Long id);
@GetMapping(PREFIX + "/list")
@Operation(summary = "通过用户 ID 查询用户们")
@Parameter(name = "ids", description = "部门编号数组", example = "1,2", required = true)
@@ -66,9 +61,7 @@ public interface AdminUserApi extends AutoTransable<AdminUserRespDTO> {
}
/**
* 校验用户是否有效。如下情况,视为无效:
* 1. 用户编号不存在
* 2. 用户被禁用
* 校验用户是否有效
*
* @param id 用户编号
*/

View File

@@ -0,0 +1,42 @@
package com.njcn.msgpush.module.system.api.websocket;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.json.JsonUtils;
import com.njcn.msgpush.module.system.api.websocket.dto.WebSocketSendToUsersReqDTO;
import com.njcn.msgpush.module.system.enums.ApiConstants;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.Collection;
@FeignClient(name = ApiConstants.NAME)
@Tag(name = "RPC 服务 - WebSocket 发送器的") // 对 WebSocketMessageSender 进行封装,提供给其它模块使用
public interface WebSocketSenderApi {
String PREFIX = ApiConstants.PREFIX + "/websocket";
@PostMapping(PREFIX + "/send-to-users")
@Operation(summary = "按用户编号集合发送 WebSocket 消息")
CommonResult<Boolean> sendToUsers(@Valid @RequestBody WebSocketSendToUsersReqDTO message);
/**
* 发送消息给指定用户集合
*
* @param userIds 用户编号集合
* @param messageType 消息类型
* @param messageContent 消息内容JSON 格式
*/
default void send(Collection<Long> userIds, String messageType, String messageContent) {
sendToUsers(new WebSocketSendToUsersReqDTO().setUserIds(userIds)
.setMessageType(messageType).setMessageContent(messageContent)).checkError();
}
default void sendObject(Collection<Long> userIds, String messageType, Object messageContent) {
send(userIds, messageType, JsonUtils.toJsonString(messageContent));
}
}

View File

@@ -0,0 +1,27 @@
package com.njcn.msgpush.module.system.api.websocket.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.Collection;
@Schema(description = "RPC 服务 - 按用户编号集合发送 WebSocket 消息 Request DTO")
@Data
@Accessors(chain = true)
public class WebSocketSendToUsersReqDTO {
@Schema(description = "用户编号集合", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")
@NotEmpty(message = "用户编号集合不能为空")
private Collection<Long> userIds;
@Schema(description = "消息类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "notice-push")
@NotEmpty(message = "消息类型不能为空")
private String messageType;
@Schema(description = "消息内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"name\":\"李四\"}")
@NotEmpty(message = "消息内容不能为空")
private String messageContent;
}

View File

@@ -0,0 +1,21 @@
package com.njcn.msgpush.module.system.enums.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum ConfigTypeEnum {
/**
* 系统配置
*/
SYSTEM(1),
/**
* 自定义配置
*/
CUSTOM(2);
private final Integer type;
}

View File

@@ -0,0 +1,33 @@
package com.njcn.msgpush.module.system.enums.dept;
import java.util.Arrays;
/**
* 组织节点类型枚举
*/
public enum DeptOrgTypeEnum {
COMPANY("company"),
DEPT("dept"),
DIRECTION("direction"),
TEAM("team");
private final String type;
DeptOrgTypeEnum(String type) {
this.type = type;
}
public String getType() {
return type;
}
public static boolean isValid(String type) {
return Arrays.stream(values()).anyMatch(item -> item.type.equals(type));
}
public static String defaultType() {
return DEPT.type;
}
}

View File

@@ -0,0 +1,28 @@
package com.njcn.msgpush.module.system.enums.dept;
import java.util.Arrays;
/**
* 岗位类型枚举
*/
public enum PostTypeEnum {
MANAGEMENT("management"),
TECHNICAL("technical"),
BUSINESS("business");
private final String type;
PostTypeEnum(String type) {
this.type = type;
}
public String getType() {
return type;
}
public static boolean isValid(String type) {
return Arrays.stream(values()).anyMatch(item -> item.type.equals(type));
}
}

View File

@@ -0,0 +1,28 @@
package com.njcn.msgpush.module.system.enums.logger;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* API 异常数据的处理状态
*
* @author hongawen
*/
@AllArgsConstructor
@Getter
public enum ApiErrorLogProcessStatusEnum {
INIT(0, "未处理"),
DONE(1, "已处理"),
IGNORE(2, "已忽略");
/**
* 状态
*/
private final Integer status;
/**
* 资源类型名
*/
private final String name;
}

View File

@@ -0,0 +1,38 @@
package com.njcn.msgpush.module.system.enums.permission;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 菜单路由类型枚举
*/
@Getter
@AllArgsConstructor
public enum MenuRouteKindEnum {
DIR("dir"), // 目录路由
VIEW("view"), // 普通页面
SINGLE("single"), // 顶级单页
IFRAME("iframe"), // iframe 页面
EXTERNAL("external"), // 外链页面
REDIRECT("redirect"); // 重定向路由
/**
* 路由类型值
*/
private final String kind;
public static MenuRouteKindEnum valueOfKind(String kind) {
if (StrUtil.isBlank(kind)) {
return null;
}
for (MenuRouteKindEnum value : values()) {
if (StrUtil.equalsIgnoreCase(value.getKind(), StrUtil.trim(kind))) {
return value;
}
}
return null;
}
}

View File

@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.njcn</groupId>
<artifactId>msgpush-module-system</artifactId>
<version>${revision}</version>
</parent>
<artifactId>msgpush-module-system-boot</artifactId>
<description>系统模块功能服务模块</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Cloud 基础 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-env</artifactId>
</dependency>
<!-- 依赖服务 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-system-api</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-biz-ip</artifactId>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-redis</artifactId>
</dependency>
<!-- RPC 远程调用相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-rpc</artifactId>
</dependency>
<!-- Registry 注册中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Config 配置中心相关 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- websocket 配置中心相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 消息队列相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-mq</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>com.njcn</groupId>
<artifactId>msgpush-spring-boot-starter-excel</artifactId>
</dependency>
<!-- 三方云服务相关 -->
<dependency>
<groupId>com.anji-plus</groupId>
<artifactId>captcha-spring-boot-starter</artifactId> <!-- 验证码,一般用于登录使用 -->
</dependency>
<dependency>
<groupId>org.apache.tika</groupId>
<artifactId>tika-core</artifactId> <!-- 文件客户端:文件类型的识别 -->
</dependency>
<!-- 三方云服务相关 -->
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId> <!-- 文件客户端:解决 ftp 连接 -->
</dependency>
<dependency>
<groupId>com.github.mwiede</groupId>
<artifactId>jsch</artifactId> <!-- 文件客户端:解决 sftp 连接 -->
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId> <!-- 文件客户端解决阿里云、腾讯云、minio 等 S3 连接 -->
<artifactId>s3</artifactId>
</dependency>
</dependencies>
<build>
<!-- 设置构建的 jar 包名 -->
<finalName>${project.artifactId}</finalName>
<plugins>
<!-- 打包 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -2,7 +2,6 @@ package com.njcn.msgpush.module.system;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 项目的启动类

View File

@@ -0,0 +1,27 @@
package com.njcn.msgpush.module.system.api.config;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.module.system.dal.dataobject.config.ConfigDO;
import com.njcn.msgpush.module.system.service.config.ConfigService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
@Hidden
public class ConfigApiImpl implements ConfigApi {
@Resource
private ConfigService configService;
@Override
public CommonResult<String> getConfigValueByKey(String key) {
ConfigDO config = configService.getConfigByKey(key);
return success(config != null ? config.getValue() : null);
}
}

View File

@@ -5,6 +5,7 @@ import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.api.dept.dto.DeptRespDTO;
import com.njcn.msgpush.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.msgpush.module.system.service.dept.DeptService;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.context.annotation.Bean;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@@ -17,6 +18,7 @@ import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@Hidden
public class DeptApiImpl implements DeptApi {
@Resource

View File

@@ -5,6 +5,7 @@ import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.api.dept.dto.PostRespDTO;
import com.njcn.msgpush.module.system.dal.dataobject.dept.PostDO;
import com.njcn.msgpush.module.system.service.dept.PostService;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@@ -16,6 +17,7 @@ import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@Hidden
public class PostApiImpl implements PostApi {
@Resource

View File

@@ -5,6 +5,7 @@ import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.common.biz.system.dict.dto.DictDataRespDTO;
import com.njcn.msgpush.module.system.dal.dataobject.dict.DictDataDO;
import com.njcn.msgpush.module.system.service.dict.DictDataService;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@@ -18,6 +19,7 @@ import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@Primary // 由于 DictDataCommonApi 的存在必须声明为 @Primary Bean
@Hidden
public class DictDataApiImpl implements DictDataApi {
@Resource

View File

@@ -0,0 +1,28 @@
package com.njcn.msgpush.module.system.api.logger;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiAccessLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.dto.ApiAccessLogCreateReqDTO;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.module.system.service.logger.ApiAccessLogService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
@Hidden
public class ApiAccessLogApiImpl implements ApiAccessLogCommonApi {
@Resource
private ApiAccessLogService apiAccessLogService;
@Override
public CommonResult<Boolean> createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {
apiAccessLogService.createApiAccessLog(createDTO);
return success(true);
}
}

View File

@@ -0,0 +1,28 @@
package com.njcn.msgpush.module.system.api.logger;
import com.njcn.msgpush.framework.common.biz.system.logger.ApiErrorLogCommonApi;
import com.njcn.msgpush.framework.common.biz.system.logger.dto.ApiErrorLogCreateReqDTO;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.module.system.service.logger.ApiErrorLogService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
@Hidden
public class ApiErrorLogApiImpl implements ApiErrorLogCommonApi {
@Resource
private ApiErrorLogService apiErrorLogService;
@Override
public CommonResult<Boolean> createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {
apiErrorLogService.createApiErrorLog(createDTO);
return success(true);
}
}

View File

@@ -8,6 +8,7 @@ import com.njcn.msgpush.module.system.api.logger.dto.OperateLogPageReqDTO;
import com.njcn.msgpush.module.system.api.logger.dto.OperateLogRespDTO;
import com.njcn.msgpush.module.system.dal.dataobject.logger.OperateLogDO;
import com.njcn.msgpush.module.system.service.logger.OperateLogService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
@@ -18,6 +19,7 @@ import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@Primary // 由于 OperateLogCommonApi 的存在必须声明为 @Primary Bean
@Hidden
public class OperateLogApiImpl implements OperateLogApi {
@Resource

View File

@@ -1,21 +1,23 @@
package com.njcn.msgpush.module.system.api.oauth2;
import com.njcn.msgpush.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCreateReqDTO;
import com.njcn.msgpush.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenRespDTO;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.njcn.msgpush.module.system.service.oauth2.OAuth2TokenService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口 Feign 调用
@RestController
@Validated
@Hidden
public class OAuth2TokenApiImpl implements OAuth2TokenCommonApi {
@Resource

View File

@@ -1,8 +1,8 @@
package com.njcn.msgpush.module.system.api.permission;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.biz.system.permission.dto.DeptDataPermissionRespDTO;
import com.njcn.msgpush.module.system.service.permission.PermissionService;
import io.swagger.v3.oas.annotations.Hidden;
import org.springframework.context.annotation.Primary;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
@@ -16,6 +16,7 @@ import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@Primary // 由于 PermissionCommonApi 的存在必须声明为 @Primary Bean
@Hidden
public class PermissionApiImpl implements PermissionApi {
@Resource
@@ -36,9 +37,5 @@ public class PermissionApiImpl implements PermissionApi {
return success(permissionService.hasAnyRoles(userId, roles));
}
@Override
public CommonResult<DeptDataPermissionRespDTO> getDeptDataPermission(Long userId) {
return success(permissionService.getDeptDataPermission(userId));
}
}

View File

@@ -1,34 +1,27 @@
package com.njcn.msgpush.module.system.api.user;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.api.user.dto.AdminUserRespDTO;
import com.njcn.msgpush.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.msgpush.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.msgpush.module.system.service.dept.DeptService;
import com.njcn.msgpush.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
import static com.njcn.msgpush.framework.common.util.collection.CollectionUtils.convertSet;
@RestController // 提供 RESTful API 接口 Feign 调用
@Validated
@Hidden
public class AdminUserApiImpl implements AdminUserApi {
@Resource
private AdminUserService userService;
@Resource
private DeptService deptService;
@Override
public CommonResult<AdminUserRespDTO> getUser(Long id) {
@@ -36,34 +29,6 @@ public class AdminUserApiImpl implements AdminUserApi {
return success(BeanUtils.toBean(user, AdminUserRespDTO.class));
}
@Override
public CommonResult<List<AdminUserRespDTO>> getUserListBySubordinate(Long id) {
// 1.1 获取用户负责的部门
AdminUserDO user = userService.getUser(id);
if (user == null) {
return success(Collections.emptyList());
}
ArrayList<Long> deptIds = new ArrayList<>();
DeptDO dept = deptService.getDept(user.getDeptId());
if (dept == null) {
return success(Collections.emptyList());
}
if (ObjUtil.notEqual(dept.getLeaderUserId(), id)) { // 校验为负责人
return success(Collections.emptyList());
}
deptIds.add(dept.getId());
// 1.2 获取所有子部门
List<DeptDO> childDeptList = deptService.getChildDeptList(dept.getId());
if (CollUtil.isNotEmpty(childDeptList)) {
deptIds.addAll(convertSet(childDeptList, DeptDO::getId));
}
// 2. 获取部门对应的用户信息
List<AdminUserDO> users = userService.getUserListByDeptIds(deptIds);
users.removeIf(item -> ObjUtil.equal(item.getId(), id)); // 排除自己
return success(BeanUtils.toBean(users, AdminUserRespDTO.class));
}
@Override
public CommonResult<List<AdminUserRespDTO>> getUserList(Collection<Long> ids) {
List<AdminUserDO> users = userService.getUserList(ids);

View File

@@ -0,0 +1,35 @@
package com.njcn.msgpush.module.system.api.websocket;
import cn.hutool.core.collection.CollUtil;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.websocket.core.sender.WebSocketMessageSender;
import com.njcn.msgpush.module.system.api.websocket.dto.WebSocketSendToUsersReqDTO;
import io.swagger.v3.oas.annotations.Hidden;
import jakarta.annotation.Resource;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RestController;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@RestController // 提供 RESTful API 接口,给 Feign 调用
@Validated
@Hidden
public class WebSocketSenderApiImpl implements WebSocketSenderApi {
private static final Integer SYSTEM_USER_TYPE = UserTypeEnum.ADMIN.getValue();
@Resource
private WebSocketMessageSender webSocketMessageSender;
@Override
public CommonResult<Boolean> sendToUsers(WebSocketSendToUsersReqDTO message) {
if (CollUtil.isNotEmpty(message.getUserIds())) {
message.getUserIds().stream().distinct().forEach(userId ->
webSocketMessageSender.send(SYSTEM_USER_TYPE, userId,
message.getMessageType(), message.getMessageContent()));
}
return success(true);
}
}

View File

@@ -4,9 +4,15 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.encrypt.core.annotation.ApiEncrypt;
import com.njcn.msgpush.framework.security.config.SecurityProperties;
import com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.*;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.AuthLoginReqVO;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.AuthLoginRespVO;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.AuthPermissionInfoRespVO;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.AuthUserRouteRespVO;
import com.njcn.msgpush.module.system.controller.admin.auth.vo.AuthUserInfoRespVO;
import com.njcn.msgpush.module.system.convert.auth.AuthConvert;
import com.njcn.msgpush.module.system.dal.dataobject.permission.MenuDO;
import com.njcn.msgpush.module.system.dal.dataobject.permission.RoleDO;
@@ -26,7 +32,12 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
@@ -53,13 +64,13 @@ public class AuthController {
private MenuService menuService;
@Resource
private PermissionService permissionService;
@Resource
private SecurityProperties securityProperties;
@PostMapping("/login")
@PermitAll
@Operation(summary = "使用账号密码登录")
@ApiEncrypt(response = false, requestFields = {"password"})
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
return success(authService.login(reqVO));
}
@@ -84,38 +95,78 @@ public class AuthController {
return success(authService.refreshToken(refreshToken));
}
@GetMapping("/get-permission-info")
@Operation(summary = "获取登录用户的权限信息")
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
@GetMapping("/get-user-info")
@Operation(summary = "获取登录用户信息")
public CommonResult<AuthUserInfoRespVO> getUserInfo() {
// 1.1 获得用户信息
AdminUserDO user = userService.getUser(getLoginUserId());
if (user == null) {
return success(null);
}
// 1.2 获得角色列表
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
if (CollUtil.isEmpty(roleIds)) {
// 1.2 获得角色和按钮权限
List<RoleDO> roles = getCurrentUserRoles();
List<MenuDO> menuList = getCurrentUserMenus(roles);
return success(AuthConvert.INSTANCE.convertUserInfo(user, roles, menuList));
}
@GetMapping("/get-user-routes")
@Operation(summary = "获取登录用户路由信息")
public CommonResult<AuthUserRouteRespVO> getUserRoutes() {
AdminUserDO user = userService.getUser(getLoginUserId());
if (user == null) {
return success(null);
}
List<RoleDO> roles = getCurrentUserRoles();
List<MenuDO> menuList = getCurrentUserMenus(roles);
return success(AuthConvert.INSTANCE.convertUserRoutes(menuList));
}
@GetMapping("/get-permission-info")
@Operation(summary = "获取登录用户的权限信息")
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
AdminUserDO user = userService.getUser(getLoginUserId());
if (user == null) {
return success(null);
}
List<RoleDO> roles = getCurrentUserRoles();
if (CollUtil.isEmpty(roles)) {
return success(AuthConvert.INSTANCE.convert(user, Collections.emptyList(), Collections.emptyList()));
}
List<RoleDO> roles = roleService.getRoleList(roleIds);
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
// 1.3 获得菜单列表
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
List<MenuDO> menuList = menuService.getMenuList(menuIds);
menuList = menuService.filterDisableMenus(menuList);
// 2. 拼接结果返回
List<MenuDO> menuList = getCurrentUserMenus(roles);
return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
}
@PostMapping("/register")
@PermitAll
@Operation(summary = "注册用户")
@ApiEncrypt(response = false, requestFields = {"password"})
public CommonResult<AuthLoginRespVO> register(@RequestBody @Valid AuthRegisterReqVO registerReqVO) {
return success(authService.register(registerReqVO));
}
private List<RoleDO> getCurrentUserRoles() {
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
if (CollUtil.isEmpty(roleIds)) {
return Collections.emptyList();
}
List<RoleDO> roles = roleService.getRoleList(roleIds);
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
return roles;
}
private List<MenuDO> getCurrentUserMenus(List<RoleDO> roles) {
if (CollUtil.isEmpty(roles)) {
return Collections.emptyList();
}
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
List<MenuDO> menuList = menuService.getMenuList(menuIds);
return menuService.filterDisableMenus(menuList);
}
}

View File

@@ -1,18 +1,16 @@
package com.njcn.msgpush.module.system.controller.admin.auth.vo;
import cn.hutool.core.util.StrUtil;
import com.njcn.msgpush.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.validator.constraints.Length;
@Schema(description = "管理后台 - 账号密码登录 Request VO如果登录并绑定社交用户需要传递 social 开头的参数")
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Pattern;
@Schema(description = "管理后台 - 账号密码登录 Request VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@@ -21,8 +19,8 @@ public class AuthLoginReqVO extends CaptchaVerificationReqVO {
@Schema(description = "账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpushyuanma")
@NotEmpty(message = "登录账号不能为空")
@Length(min = 4, max = 16, message = "账号长度为 4-16")
@Pattern(regexp = "^[A-Za-z0-9]+$", message = "账号格式为数字以及字母")
@Length(min = 4, max = 30, message = "账号长度为 4-30")
@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "账号格式为数字以及字母")
private String username;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "buzhidao")
@@ -30,12 +28,4 @@ public class AuthLoginReqVO extends CaptchaVerificationReqVO {
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
// ========== 绑定社交登录时需要传递如下参数 ==========
@Schema(description = "授权码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private String socialCode;
@Schema(description = "state", requiredMode = Schema.RequiredMode.REQUIRED, example = "9b2ffbc1-7425-4155-9894-9d5c08541d62")
private String socialState;
}
}

View File

@@ -1,31 +1,39 @@
package com.njcn.msgpush.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.hibernate.validator.constraints.Length;
@Schema(description = "管理后台 - Register Request VO")
@Schema(description = "管理后台 - 注册 Request VO")
@Data
public class AuthRegisterReqVO extends CaptchaVerificationReqVO {
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush")
@NotBlank(message = "用户账号不能为空")
@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成")
@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由数字、字母组成")
@Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符")
private String username;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen")
@NotBlank(message = "用户昵称不能为空")
@Size(max = 30, message = "用户昵称长度不能超过 30 个字符")
private String nickname;
@Schema(description = "所属部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "所属部门不能为空")
private Long deptId;
@Schema(description = "主岗位编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "主岗位不能为空")
private Long positionId;
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
@NotEmpty(message = "密码不能为空")
@Length(min = 4, max = 16, message = "密码长度为 4-16 位")
private String password;
}
}

View File

@@ -0,0 +1,46 @@
package com.njcn.msgpush.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 用户路由 Meta Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthRouteMetaRespVO {
@Schema(description = "菜单或页面标题", requiredMode = Schema.RequiredMode.REQUIRED)
private String title;
@Schema(description = "国际化 key")
private String i18nKey;
@Schema(description = "图标名")
private String icon;
@Schema(description = "本地图标名")
private String localIcon;
@Schema(description = "排序值")
private Integer order;
@Schema(description = "是否缓存")
private Boolean keepAlive;
@Schema(description = "是否在菜单中隐藏")
private Boolean hideInMenu;
@Schema(description = "当前页面高亮的菜单路由名")
private String activeMenu;
@Schema(description = "是否支持多标签页")
private Boolean multiTab;
@Schema(description = "标签页固定位置")
private Integer fixedIndexInTab;
}

View File

@@ -0,0 +1,42 @@
package com.njcn.msgpush.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 用户路由节点 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthRouteNodeRespVO {
@Schema(description = "路由节点 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1000")
private String id;
@Schema(description = "路由名", requiredMode = Schema.RequiredMode.REQUIRED, example = "system_user")
private String name;
@Schema(description = "完整路由路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "/system/user")
private String path;
@Schema(description = "前端组件白名单 key", example = "view.system_user")
private String component;
@Schema(description = "重定向路径")
private String redirect;
@Schema(description = "路由 props")
private Object props;
@Schema(description = "路由 meta", requiredMode = Schema.RequiredMode.REQUIRED)
private AuthRouteMetaRespVO meta;
@Schema(description = "子路由列表")
private List<AuthRouteNodeRespVO> children;
}

View File

@@ -0,0 +1,31 @@
package com.njcn.msgpush.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 登录用户信息 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthUserInfoRespVO {
@Schema(description = "用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private String userId;
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
private String userName;
@Schema(description = "角色编码列表", example = "[\"SUPER_ADMIN\"]")
private List<String> roles;
@Schema(description = "按钮权限码列表", requiredMode = Schema.RequiredMode.REQUIRED,
example = "[\"system:user:add\", \"system:user:update\"]")
private List<String> buttons;
}

View File

@@ -0,0 +1,24 @@
package com.njcn.msgpush.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Schema(description = "管理后台 - 用户路由 Response VO")
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class AuthUserRouteRespVO {
@Schema(description = "用户可访问路由树", requiredMode = Schema.RequiredMode.REQUIRED)
private List<AuthRouteNodeRespVO> routes;
@Schema(description = "默认首页路由名", requiredMode = Schema.RequiredMode.REQUIRED, example = "system_user")
private String home;
}

View File

@@ -27,6 +27,7 @@ public class CaptchaController {
@PostMapping({"/get"})
@Operation(summary = "获得验证码")
@PermitAll
public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {
assert request.getRemoteHost() != null;
data.setBrowserInfo(getRemoteId(request));
@@ -36,6 +37,7 @@ public class CaptchaController {
@PostMapping("/check")
@Operation(summary = "校验验证码")
@PermitAll
public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {
data.setBrowserInfo(getRemoteId(request));
return captchaService.check(data);

View File

@@ -0,0 +1,118 @@
package com.njcn.msgpush.module.system.controller.admin.config;
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
import com.njcn.msgpush.module.system.controller.admin.config.vo.ConfigPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.config.vo.ConfigRespVO;
import com.njcn.msgpush.module.system.controller.admin.config.vo.ConfigSaveReqVO;
import com.njcn.msgpush.module.system.convert.config.ConfigConvert;
import com.njcn.msgpush.module.system.dal.dataobject.config.ConfigDO;
import com.njcn.msgpush.module.system.enums.ErrorCodeConstants;
import com.njcn.msgpush.module.system.service.config.ConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.msgpush.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 参数配置")
@RestController
@RequestMapping("/system/config")
@Validated
public class ConfigController {
@Resource
private ConfigService configService;
@PostMapping("/create")
@Operation(summary = "创建参数配置")
@PreAuthorize("@ss.hasPermission('system:config:create')")
public CommonResult<Long> createConfig(@Valid @RequestBody ConfigSaveReqVO createReqVO) {
return success(configService.createConfig(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改参数配置")
@PreAuthorize("@ss.hasPermission('system:config:update')")
public CommonResult<Boolean> updateConfig(@Valid @RequestBody ConfigSaveReqVO updateReqVO) {
configService.updateConfig(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除参数配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:config:delete')")
public CommonResult<Boolean> deleteConfig(@RequestParam("id") Long id) {
configService.deleteConfig(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除参数配置")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('system:config:delete')")
public CommonResult<Boolean> deleteConfigList(@RequestParam("ids") List<Long> ids) {
configService.deleteConfigList(ids);
return success(true);
}
@GetMapping(value = "/get")
@Operation(summary = "获得参数配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:config:query')")
public CommonResult<ConfigRespVO> getConfig(@RequestParam("id") Long id) {
return success(ConfigConvert.INSTANCE.convert(configService.getConfig(id)));
}
@GetMapping(value = "/get-value-by-key")
@Operation(summary = "根据参数键名查询参数值", description = "不可见的配置,不允许返回给前端")
@Parameter(name = "key", description = "参数键", required = true, example = "yunai.biz.username")
public CommonResult<String> getConfigKey(@RequestParam("key") String key) {
ConfigDO config = configService.getConfigByKey(key);
if (config == null) {
return success(null);
}
if (!config.getVisible()) {
throw exception(ErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_VISIBLE);
}
return success(config.getValue());
}
@GetMapping("/page")
@Operation(summary = "获取参数配置分页")
@PreAuthorize("@ss.hasPermission('system:config:query')")
public CommonResult<PageResult<ConfigRespVO>> getConfigPage(@Valid ConfigPageReqVO pageReqVO) {
PageResult<ConfigDO> page = configService.getConfigPage(pageReqVO);
return success(ConfigConvert.INSTANCE.convertPage(page));
}
@GetMapping("/export-excel")
@Operation(summary = "导出参数配置")
@PreAuthorize("@ss.hasPermission('system:config:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportConfig(ConfigPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ConfigDO> list = configService.getConfigPage(exportReqVO).getList();
// 输出
ExcelUtils.write(response, "参数配置.xls", "数据", ConfigRespVO.class,
ConfigConvert.INSTANCE.convertList(list));
}
}

View File

@@ -0,0 +1,29 @@
package com.njcn.msgpush.module.system.controller.admin.config.vo;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 参数配置分页 Request VO")
@Data
public class ConfigPageReqVO extends PageParam {
@Schema(description = "数据源名称,模糊匹配", example = "名称")
private String name;
@Schema(description = "参数键名,模糊匹配", example = "yunai.db.username")
private String key;
@Schema(description = "参数类型,参见 SysConfigTypeEnum 枚举", example = "1")
private Integer type;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00,2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,56 @@
package com.njcn.msgpush.module.system.controller.admin.config.vo;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
import com.njcn.msgpush.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 参数配置信息 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ConfigRespVO {
@Schema(description = "参数配置序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("参数配置序号")
private Long id;
@Schema(description = "参数分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "biz")
@ExcelProperty("参数分类")
private String category;
@Schema(description = "参数名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "数据库名")
@ExcelProperty("参数名称")
private String name;
@Schema(description = "参数键名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yunai.db.username")
@ExcelProperty("参数键名")
private String key;
@Schema(description = "参数键值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("参数键值")
private String value;
@Schema(description = "参数类型,参见 SysConfigTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "参数类型", converter = DictConvert.class)
@DictFormat(DictTypeConstants.CONFIG_TYPE)
private Integer type;
@Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@ExcelProperty(value = "是否可见", converter = DictConvert.class)
@DictFormat(DictTypeConstants.BOOLEAN_STRING)
private Boolean visible;
@Schema(description = "备注", example = "备注一下很帅气!")
@ExcelProperty("备注")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,44 @@
package com.njcn.msgpush.module.system.controller.admin.config.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - 参数配置创建/修改 Request VO")
@Data
public class ConfigSaveReqVO {
@Schema(description = "参数配置序号", example = "1024")
private Long id;
@Schema(description = "参数分组", requiredMode = Schema.RequiredMode.REQUIRED, example = "biz")
@NotEmpty(message = "参数分组不能为空")
@Size(max = 50, message = "参数名称不能超过 50 个字符")
private String category;
@Schema(description = "参数名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "数据库名")
@NotBlank(message = "参数名称不能为空")
@Size(max = 100, message = "参数名称不能超过 100 个字符")
private String name;
@Schema(description = "参数键名", requiredMode = Schema.RequiredMode.REQUIRED, example = "yunai.db.username")
@NotBlank(message = "参数键名长度不能为空")
@Size(max = 100, message = "参数键名长度不能超过 100 个字符")
private String key;
@Schema(description = "参数键值", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotBlank(message = "参数键值不能为空")
@Size(max = 500, message = "参数键值长度不能超过 500 个字符")
private String value;
@Schema(description = "是否可见", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否可见不能为空")
private Boolean visible;
@Schema(description = "备注", example = "备注一下很帅气!")
private String remark;
}

View File

@@ -0,0 +1,101 @@
package com.njcn.msgpush.module.system.controller.admin.dept;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.controller.admin.dept.vo.orgleader.OrgLeaderRelationRespVO;
import com.njcn.msgpush.module.system.controller.admin.dept.vo.orgleader.OrgLeaderRelationSaveReqVO;
import com.njcn.msgpush.module.system.controller.admin.user.vo.user.UserSimpleRespVO;
import com.njcn.msgpush.module.system.convert.user.UserConvert;
import com.njcn.msgpush.module.system.dal.dataobject.dept.DeptDO;
import com.njcn.msgpush.module.system.dal.dataobject.dept.OrgLeaderRelationDO;
import com.njcn.msgpush.module.system.dal.dataobject.user.AdminUserDO;
import com.njcn.msgpush.module.system.service.dept.DeptService;
import com.njcn.msgpush.module.system.service.dept.OrgLeaderRelationService;
import com.njcn.msgpush.module.system.service.user.AdminUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
import static com.njcn.msgpush.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - 组织负责人关系")
@RestController
@RequestMapping("/system/org-leader")
@Validated
public class OrgLeaderRelationController {
@Resource
private OrgLeaderRelationService orgLeaderRelationService;
@Resource
private DeptService deptService;
@Resource
private AdminUserService adminUserService;
@PostMapping("/create")
@Operation(summary = "创建组织负责人关系")
@PreAuthorize("@ss.hasPermission('system:org-leader:create')")
public CommonResult<Long> createOrgLeaderRelation(@Valid @RequestBody OrgLeaderRelationSaveReqVO createReqVO) {
return success(orgLeaderRelationService.createOrgLeaderRelation(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改组织负责人关系")
@PreAuthorize("@ss.hasPermission('system:org-leader:update')")
public CommonResult<Boolean> updateOrgLeaderRelation(@Valid @RequestBody OrgLeaderRelationSaveReqVO updateReqVO) {
orgLeaderRelationService.updateOrgLeaderRelation(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除组织负责人关系")
@Parameter(name = "id", description = "负责人关系编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:org-leader:delete')")
public CommonResult<Boolean> deleteOrgLeaderRelation(@RequestParam("id") Long id) {
orgLeaderRelationService.deleteOrgLeaderRelation(id);
return success(true);
}
@GetMapping("/list-by-dept")
@Operation(summary = "查询组织下的负责人关系列表")
@Parameter(name = "deptId", description = "组织节点 ID", required = true, example = "100")
@PreAuthorize("@ss.hasPermission('system:org-leader:query')")
public CommonResult<List<OrgLeaderRelationRespVO>> getOrgLeaderRelationListByDept(@RequestParam("deptId") Long deptId) {
List<OrgLeaderRelationDO> relations = orgLeaderRelationService.getOrgLeaderRelationListByDeptId(deptId);
List<OrgLeaderRelationRespVO> respList = BeanUtils.toBean(relations, OrgLeaderRelationRespVO.class);
Map<Long, AdminUserDO> userMap = adminUserService.getUserMap(convertList(relations, OrgLeaderRelationDO::getUserId));
respList.forEach(respVO -> {
AdminUserDO user = userMap.get(respVO.getUserId());
if (user != null) {
respVO.setUserNickname(user.getNickname());
}
});
return success(respList);
}
@GetMapping("/candidate-users")
@Operation(summary = "查询组织负责人候选用户列表")
@Parameter(name = "deptId", description = "组织节点 ID", required = true, example = "100")
@PreAuthorize("@ss.hasPermission('system:org-leader:query')")
public CommonResult<List<UserSimpleRespVO>> getCandidateUsers(@RequestParam("deptId") Long deptId) {
List<AdminUserDO> users = orgLeaderRelationService.getCandidateUsersByDeptId(deptId);
Map<Long, DeptDO> deptMap = deptService.getDeptMap(convertList(users, AdminUserDO::getDeptId));
return success(UserConvert.INSTANCE.convertSimpleList(users, deptMap));
}
}

View File

@@ -10,6 +10,9 @@ public class DeptListReqVO {
@Schema(description = "部门名称,模糊匹配", example = "灿能")
private String name;
@Schema(description = "组织节点类型", example = "dept")
private String orgType;
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
private Integer status;

View File

@@ -12,28 +12,31 @@ public class DeptRespVO {
@Schema(description = "部门编号", example = "1024")
private Long id;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能")
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发中心")
private String name;
@Schema(description = "父部门 ID", example = "1024")
private Long parentId;
@Schema(description = "组织节点类型", example = "dept")
private String orgType;
@Schema(description = "组织物化路径", example = "/100/101/103/")
private String path;
@Schema(description = "组织层级", example = "3")
private Integer level;
@Schema(description = "组织编码", example = "RD_CENTER")
private String code;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Integer sort;
@Schema(description = "负责人的用户编号", example = "2048")
private Long leaderUserId;
@Schema(description = "联系电话", example = "15601691000")
private String phone;
@Schema(description = "邮箱", example = "msgpush@iocoder.cn")
private String email;
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "状态,参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED, example = "时间戳格式")
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -3,7 +3,6 @@ package com.njcn.msgpush.module.system.controller.admin.dept.vo.dept;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@@ -16,7 +15,7 @@ public class DeptSaveReqVO {
@Schema(description = "部门编号", example = "1024")
private Long id;
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能")
@Schema(description = "部门名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "研发中心")
@NotBlank(message = "部门名称不能为空")
@Size(max = 30, message = "部门名称长度不能超过 30 个字符")
private String name;
@@ -24,23 +23,19 @@ public class DeptSaveReqVO {
@Schema(description = "父部门 ID", example = "1024")
private Long parentId;
@Schema(description = "组织节点类型", example = "dept")
@Size(max = 20, message = "组织节点类型长度不能超过 20 个字符")
private String orgType;
@Schema(description = "组织编码", example = "RD_CENTER")
@Size(max = 64, message = "组织编码长度不能超过 64 个字符")
private String code;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "显示顺序不能为空")
private Integer sort;
@Schema(description = "负责人的用户编号", example = "2048")
private Long leaderUserId;
@Schema(description = "联系电话", example = "15601691000")
@Size(max = 11, message = "联系电话长度不能超过11个字符")
private String phone;
@Schema(description = "邮箱", example = "msgpush@iocoder.cn")
@Email(message = "邮箱格式不正确")
@Size(max = 50, message = "邮箱长度不能超过 50 个字符")
private String email;
@Schema(description = "状态,见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@Schema(description = "状态,参见 CommonStatusEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "状态不能为空")
@InEnum(value = CommonStatusEnum.class, message = "修改状态必须是 {value}")
private Integer status;

View File

@@ -20,4 +20,7 @@ public class DeptSimpleRespVO {
@Schema(description = "父部门 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long parentId;
@Schema(description = "组织节点类型", example = "dept")
private String orgType;
}

View File

@@ -0,0 +1,39 @@
package com.njcn.msgpush.module.system.controller.admin.dept.vo.orgleader;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 组织负责人关系 Response VO")
@Data
public class OrgLeaderRelationRespVO {
@Schema(description = "负责人关系编号", example = "1024")
private Long id;
@Schema(description = "组织节点 ID", example = "100")
private Long deptId;
@Schema(description = "负责人用户 ID", example = "1")
private Long userId;
@Schema(description = "负责人用户昵称", example = "管理员")
private String userNickname;
@Schema(description = "生效开始时间")
private LocalDateTime effectiveFrom;
@Schema(description = "生效结束时间")
private LocalDateTime effectiveUntil;
@Schema(description = "备注", example = "部门负责人")
private String remark;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "更新时间")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.msgpush.module.system.controller.admin.dept.vo.orgleader;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 组织负责人关系创建/修改 Request VO")
@Data
public class OrgLeaderRelationSaveReqVO {
@Schema(description = "负责人关系编号", example = "1024")
private Long id;
@Schema(description = "组织节点 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@NotNull(message = "组织节点不能为空")
private Long deptId;
@Schema(description = "负责人用户 ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "负责人用户不能为空")
private Long userId;
@Schema(description = "生效开始时间")
private LocalDateTime effectiveFrom;
@Schema(description = "生效结束时间")
private LocalDateTime effectiveUntil;
@Schema(description = "备注", example = "部门负责人")
@Size(max = 500, message = "备注长度不能超过 500 个字符")
private String remark;
}

View File

@@ -16,6 +16,12 @@ public class PostPageReqVO extends PageParam {
@Schema(description = "岗位名称,模糊匹配", example = "灿能")
private String name;
@Schema(description = "岗位类型", example = "technical")
private String postType;
@Schema(description = "岗位级别", example = "8")
private Integer levelRank;
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
private Integer status;

View File

@@ -1,10 +1,10 @@
package com.njcn.msgpush.module.system.controller.admin.dept.vo.post;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
import com.njcn.msgpush.module.system.enums.DictTypeConstants;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@@ -19,15 +19,23 @@ public class PostRespVO {
@ExcelProperty("岗位序号")
private Long id;
@Schema(description = "岗位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小土豆")
@Schema(description = "岗位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "后端开发工程师")
@ExcelProperty("岗位名称")
private String name;
@Schema(description = "岗位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush")
@Schema(description = "岗位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "backend")
@ExcelProperty("岗位编码")
private String code;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "岗位类型", example = "technical")
@ExcelProperty("岗位类型")
private String postType;
@Schema(description = "岗位级别", example = "8")
@ExcelProperty("岗位级别")
private Integer levelRank;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("岗位排序")
private Integer sort;
@@ -36,7 +44,7 @@ public class PostRespVO {
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;
@Schema(description = "备注", example = "快乐的备注")
@Schema(description = "备注", example = "技术序列岗位")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)

View File

@@ -3,6 +3,7 @@ package com.njcn.msgpush.module.system.controller.admin.dept.vo.post;
import com.njcn.msgpush.framework.common.enums.CommonStatusEnum;
import com.njcn.msgpush.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@@ -15,17 +16,25 @@ public class PostSaveReqVO {
@Schema(description = "岗位编号", example = "1024")
private Long id;
@Schema(description = "岗位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小土豆")
@Schema(description = "岗位名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "后端开发工程师")
@NotBlank(message = "岗位名称不能为空")
@Size(max = 50, message = "岗位名称长度不能超过 50 个字符")
private String name;
@Schema(description = "岗位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush")
@Schema(description = "岗位编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "backend")
@NotBlank(message = "岗位编码不能为空")
@Size(max = 64, message = "岗位编码长度不能超过64个字符")
@Size(max = 64, message = "岗位编码长度不能超过 64 个字符")
private String code;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Schema(description = "岗位类型", example = "technical")
@Size(max = 20, message = "岗位类型长度不能超过 20 个字符")
private String postType;
@Schema(description = "岗位级别", example = "8")
@Min(value = 0, message = "岗位级别不能小于 0")
private Integer levelRank;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "显示顺序不能为空")
private Integer sort;
@@ -33,7 +42,7 @@ public class PostSaveReqVO {
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "备注", example = "快乐的备注")
@Schema(description = "备注", example = "技术序列岗位")
private String remark;
}
}

View File

@@ -16,4 +16,16 @@ public class PostSimpleRespVO {
@ExcelProperty("岗位名称")
private String name;
@Schema(description = "岗位编码", example = "backend")
private String code;
@Schema(description = "岗位类型", example = "technical")
private String postType;
@Schema(description = "岗位级别", example = "8")
private Integer levelRank;
@Schema(description = "岗位排序", example = "1")
private Integer sort;
}

View File

@@ -75,6 +75,7 @@ public class DictTypeController {
@Operation(summary = "获得字典类型的分页列表")
@PreAuthorize("@ss.hasPermission('system:dict:query')")
public CommonResult<PageResult<DictTypeRespVO>> pageDictTypes(@Valid DictTypePageReqVO pageReqVO) {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
PageResult<DictTypeDO> pageResult = dictTypeService.getDictTypePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, DictTypeRespVO.class));
}

View File

@@ -5,6 +5,7 @@ import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.util.StringUtils;
import jakarta.validation.constraints.Size;
import java.time.LocalDateTime;
@@ -23,6 +24,10 @@ public class DictTypePageReqVO extends PageParam {
@Size(max = 100, message = "字典类型类型长度不能超过100个字符")
private String type;
@Schema(description = "字典类型编码,兼容前端 code 查询参数", example = "sys_common_sex")
@Size(max = 100, message = "字典类型编码长度不能超过100个字符")
private String code;
@Schema(description = "展示状态,参见 CommonStatusEnum 枚举类", example = "1")
private Integer status;
@@ -30,4 +35,8 @@ public class DictTypePageReqVO extends PageParam {
@Schema(description = "创建时间")
private LocalDateTime[] createTime;
public String getTypeKeyword() {
return StringUtils.hasText(code) ? code : type;
}
}

View File

@@ -0,0 +1,99 @@
package com.njcn.msgpush.module.system.controller.admin.file;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.controller.admin.file.vo.config.FileConfigPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.file.vo.config.FileConfigRespVO;
import com.njcn.msgpush.module.system.controller.admin.file.vo.config.FileConfigSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.file.FileConfigDO;
import com.njcn.msgpush.module.system.service.file.FileConfigService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 文件配置")
@RestController
@RequestMapping("/system/file-config")
@Validated
public class FileConfigController {
@Resource
private FileConfigService fileConfigService;
@PostMapping("/create")
@Operation(summary = "创建文件配置")
@PreAuthorize("@ss.hasPermission('system:file-config:create')")
public CommonResult<Long> createFileConfig(@Valid @RequestBody FileConfigSaveReqVO createReqVO) {
return success(fileConfigService.createFileConfig(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新文件配置")
@PreAuthorize("@ss.hasPermission('system:file-config:update')")
public CommonResult<Boolean> updateFileConfig(@Valid @RequestBody FileConfigSaveReqVO updateReqVO) {
fileConfigService.updateFileConfig(updateReqVO);
return success(true);
}
@PutMapping("/update-master")
@Operation(summary = "更新文件配置为 Master")
@PreAuthorize("@ss.hasPermission('system:file-config:update')")
public CommonResult<Boolean> updateFileConfigMaster(@RequestParam("id") Long id) {
fileConfigService.updateFileConfigMaster(id);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除文件配置")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:file-config:delete')")
public CommonResult<Boolean> deleteFileConfig(@RequestParam("id") Long id) {
fileConfigService.deleteFileConfig(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除文件配置")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('system:file-config:delete')")
public CommonResult<Boolean> deleteFileConfigList(@RequestParam("ids") List<Long> ids) {
fileConfigService.deleteFileConfigList(ids);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得文件配置")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:file-config:query')")
public CommonResult<FileConfigRespVO> getFileConfig(@RequestParam("id") Long id) {
FileConfigDO config = fileConfigService.getFileConfig(id);
return success(BeanUtils.toBean(config, FileConfigRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得文件配置分页")
@PreAuthorize("@ss.hasPermission('system:file-config:query')")
public CommonResult<PageResult<FileConfigRespVO>> getFileConfigPage(@Valid FileConfigPageReqVO pageVO) {
PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(pageVO);
return success(BeanUtils.toBean(pageResult, FileConfigRespVO.class));
}
@GetMapping("/test")
@Operation(summary = "测试文件配置是否正确")
@PreAuthorize("@ss.hasPermission('system:file-config:query')")
public CommonResult<String> testFileConfig(@RequestParam("id") Long id) throws Exception {
String url = fileConfigService.testFileConfig(id);
return success(url);
}
}

View File

@@ -0,0 +1,137 @@
package com.njcn.msgpush.module.system.controller.admin.file;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.system.controller.admin.file.vo.file.*;
import com.njcn.msgpush.module.system.dal.dataobject.file.FileDO;
import com.njcn.msgpush.module.system.service.file.FileService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
import static com.njcn.msgpush.module.system.framework.file.core.utils.FileTypeUtils.writeAttachment;
@Tag(name = "管理后台 - 文件存储")
@RestController
@RequestMapping("/system/file")
@Validated
@Slf4j
public class FileController {
@Resource
private FileService fileService;
@PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
@Parameter(name = "file", description = "文件附件", required = true,
schema = @Schema(type = "string", format = "binary"))
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
MultipartFile file = uploadReqVO.getFile();
byte[] content = IoUtil.readBytes(file.getInputStream());
return success(fileService.createFile(content, file.getOriginalFilename(),
uploadReqVO.getDirectory(), file.getContentType()));
}
@GetMapping("/presigned-url")
@Operation(summary = "获取文件预签名地址(上传)", description = "模式二:前端上传文件:用于前端直接上传七牛、阿里云 OSS 等文件存储器")
@Parameters({
@Parameter(name = "name", description = "文件名称", required = true),
@Parameter(name = "directory", description = "文件目录")
})
public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(
@RequestParam("name") String name,
@RequestParam(value = "directory", required = false) String directory) {
return success(fileService.presignPutUrl(name, directory));
}
@PostMapping("/create")
@Operation(summary = "创建文件", description = "模式二:前端上传文件:配合 presigned-url 接口,记录上传了上传的文件")
public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {
return success(fileService.createFile(createReqVO));
}
@GetMapping("/get")
@Operation(summary = "获得文件")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:file:query')")
public CommonResult<FileRespVO> getFile(@RequestParam("id") Long id) {
return success(BeanUtils.toBean(fileService.getFile(id), FileRespVO.class));
}
@DeleteMapping("/delete")
@Operation(summary = "删除文件")
@Parameter(name = "id", description = "编号", required = true)
@PreAuthorize("@ss.hasPermission('system:file:delete')")
public CommonResult<Boolean> deleteFile(@RequestParam("id") Long id) throws Exception {
fileService.deleteFile(id);
return success(true);
}
@DeleteMapping("/delete-list")
@Operation(summary = "批量删除文件")
@Parameter(name = "ids", description = "编号列表", required = true)
@PreAuthorize("@ss.hasPermission('system:file:delete')")
public CommonResult<Boolean> deleteFileList(@RequestParam("ids") List<Long> ids) throws Exception {
fileService.deleteFileList(ids);
return success(true);
}
@GetMapping("/{configId}/get/**")
@PermitAll
@Operation(summary = "下载文件")
@Parameter(name = "configId", description = "配置编号", required = true)
public void getFileContent(HttpServletRequest request,
HttpServletResponse response,
@PathVariable("configId") Long configId) throws Exception {
// 获取请求的路径
String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);
if (StrUtil.isEmpty(path)) {
throw new IllegalArgumentException("结尾的 path 路径必须传递");
}
// 解码,解决中文路径的问题
// https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/807/
// https://gitee.com/zhijiantianya/ruoyi-vue-pro/pulls/1432/
path = URLUtil.decode(path, StandardCharsets.UTF_8, false);
// 读取内容
byte[] content = fileService.getFileContent(configId, path);
if (content == null) {
log.warn("[getFileContent][configId({}) path({}) 文件不存在]", configId, path);
response.setStatus(HttpStatus.NOT_FOUND.value());
return;
}
writeAttachment(response, path, content);
}
@GetMapping("/page")
@Operation(summary = "获得文件分页")
@PreAuthorize("@ss.hasPermission('system:file:query')")
public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) {
PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);
return success(BeanUtils.toBean(pageResult, FileRespVO.class));
}
}

View File

@@ -0,0 +1,26 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.config;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 文件配置分页 Request VO")
@Data
public class FileConfigPageReqVO extends PageParam {
@Schema(description = "配置名", example = "S3 - 阿里云")
private String name;
@Schema(description = "存储器", example = "1")
private Integer storage;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,34 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.config;
import com.njcn.msgpush.module.system.framework.file.core.client.FileClientConfig;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 文件配置 Response VO")
@Data
public class FileConfigRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "配置名", requiredMode = Schema.RequiredMode.REQUIRED, example = "S3 - 阿里云")
private String name;
@Schema(description = "存储器,参见 FileStorageEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer storage;
@Schema(description = "是否为主配置", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean master;
@Schema(description = "存储配置", requiredMode = Schema.RequiredMode.REQUIRED)
private FileClientConfig config;
@Schema(description = "备注", example = "我是备注")
private String remark;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,31 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.config;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.Map;
@Schema(description = "管理后台 - 文件配置创建/修改 Request VO")
@Data
public class FileConfigSaveReqVO {
@Schema(description = "编号", example = "1")
private Long id;
@Schema(description = "配置名", requiredMode = Schema.RequiredMode.REQUIRED, example = "S3 - 阿里云")
@NotNull(message = "配置名不能为空")
private String name;
@Schema(description = "存储器,参见 FileStorageEnum 枚举类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "存储器不能为空")
private Integer storage;
@Schema(description = "存储配置,配置是动态参数,所以使用 Map 接收", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "存储配置不能为空")
private Map<String, Object> config;
@Schema(description = "备注", example = "我是备注")
private String remark;
}

View File

@@ -0,0 +1,33 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.file;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - 文件创建 Request VO")
@Data
public class FileCreateReqVO {
@NotNull(message = "文件配置编号不能为空")
@Schema(description = "文件配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
private Long configId;
@NotNull(message = "文件路径不能为空")
@Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush.jpg")
private String path;
@NotNull(message = "原文件名不能为空")
@Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush.jpg")
private String name;
@NotNull(message = "文件 URL不能为空")
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/msgpush.jpg")
private String url;
@Schema(description = "文件 MIME 类型", example = "application/octet-stream")
private String type;
@Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
private Long size;
}

View File

@@ -0,0 +1,26 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.file;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 文件分页 Request VO")
@Data
public class FilePageReqVO extends PageParam {
@Schema(description = "文件路径,模糊匹配", example = "msgpush")
private String path;
@Schema(description = "文件类型,模糊匹配", example = "jpg")
private String type;
@Schema(description = "创建时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] createTime;
}

View File

@@ -0,0 +1,38 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.file;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "管理后台 - 文件预签名地址 Response VO")
@Data
public class FilePresignedUrlRespVO {
@Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
private Long configId;
@Schema(description = "文件上传 URL", requiredMode = Schema.RequiredMode.REQUIRED,
example = "https://s3.cn-south-1.qiniucs.com/ruoyi-vue-pro/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS%2F20240217%2Fcn-south-1%2Fs3%2Faws4_request&X-Amz-Date=20240217T123222Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=a29f33770ab79bf523ccd4034d0752ac545f3c2a3b17baa1eb4e280cfdccfda5")
private String uploadUrl;
/**
* 为什么要返回 url 字段?
*
* 前端上传完文件后,需要使用该 URL 进行访问
*/
@Schema(description = "文件访问 URL", requiredMode = Schema.RequiredMode.REQUIRED,
example = "https://test.msgpush.iocoder.cn/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png")
private String url;
/**
* 为什么要返回 path 字段?
*
* 前端上传完文件后,需要调用 createFile 记录下 path 路径
*/
@Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "xxx.png")
private String path;
}

View File

@@ -0,0 +1,36 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.file;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 文件 Response VO,不返回 content 字段,太大")
@Data
public class FileRespVO {
@Schema(description = "文件编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "配置编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "11")
private Long configId;
@Schema(description = "文件路径", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush.jpg")
private String path;
@Schema(description = "原文件名", requiredMode = Schema.RequiredMode.REQUIRED, example = "msgpush.jpg")
private String name;
@Schema(description = "文件 URL", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/msgpush.jpg")
private String url;
@Schema(description = "文件MIME类型", example = "application/octet-stream")
private String type;
@Schema(description = "文件大小", example = "2048", requiredMode = Schema.RequiredMode.REQUIRED)
private Long size;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.msgpush.module.system.controller.admin.file.vo.file;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Schema(description = "管理后台 - 上传文件 Request VO")
@Data
public class FileUploadReqVO {
@Schema(description = "文件附件", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "文件附件不能为空")
private MultipartFile file;
@Schema(description = "文件目录", example = "XXX/YYY")
private String directory;
@AssertTrue(message = "文件目录不正确")
@JsonIgnore
public boolean isDirectoryValid() {
return isDirectoryValid(directory);
}
public static boolean isDirectoryValid(String directory) {
// 1. 不能包含 .. 防止目录穿越
// 2. 不能以 / 或 \ 开头,防止上传到根目录
return !StrUtil.contains(directory, "..")
&& !StrUtil.startWithAny(directory, "/", "\\");
}
}

View File

@@ -0,0 +1,72 @@
package com.njcn.msgpush.module.system.controller.admin.logger;
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
import com.njcn.msgpush.module.system.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.logger.vo.apiaccesslog.ApiAccessLogRespVO;
import com.njcn.msgpush.module.system.dal.dataobject.logger.ApiAccessLogDO;
import com.njcn.msgpush.module.system.service.logger.ApiAccessLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.List;
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - API 访问日志")
@RestController
@RequestMapping("/system/api-access-log")
@Validated
public class ApiAccessLogController {
@Resource
private ApiAccessLogService apiAccessLogService;
@GetMapping("/get")
@Operation(summary = "获得 API 访问日志")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:api-access-log:query')")
public CommonResult<ApiAccessLogRespVO> getApiAccessLog(@RequestParam("id") Long id) {
ApiAccessLogDO apiAccessLog = apiAccessLogService.getApiAccessLog(id);
return success(BeanUtils.toBean(apiAccessLog, ApiAccessLogRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得API 访问日志分页")
@PreAuthorize("@ss.hasPermission('system:api-access-log:query')")
public CommonResult<PageResult<ApiAccessLogRespVO>> getApiAccessLogPage(@Valid ApiAccessLogPageReqVO pageReqVO) {
PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getApiAccessLogPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ApiAccessLogRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出API 访问日志 Excel")
@PreAuthorize("@ss.hasPermission('system:api-access-log:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportApiAccessLogExcel(@Valid ApiAccessLogPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ApiAccessLogDO> list = apiAccessLogService.getApiAccessLogPage(exportReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "API 访问日志.xls", "数据", ApiAccessLogRespVO.class,
BeanUtils.toBean(list, ApiAccessLogRespVO.class));
}
}

View File

@@ -0,0 +1,84 @@
package com.njcn.msgpush.module.system.controller.admin.logger;
import com.njcn.msgpush.framework.apilog.core.annotation.ApiAccessLog;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.framework.excel.core.util.ExcelUtils;
import com.njcn.msgpush.module.system.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;
import com.njcn.msgpush.module.system.controller.admin.logger.vo.apierrorlog.ApiErrorLogRespVO;
import com.njcn.msgpush.module.system.dal.dataobject.logger.ApiErrorLogDO;
import com.njcn.msgpush.module.system.service.logger.ApiErrorLogService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.io.IOException;
import java.util.List;
import static com.njcn.msgpush.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
import static com.njcn.msgpush.framework.common.pojo.CommonResult.success;
import static com.njcn.msgpush.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - API 错误日志")
@RestController
@RequestMapping("/system/api-error-log")
@Validated
public class ApiErrorLogController {
@Resource
private ApiErrorLogService apiErrorLogService;
@PutMapping("/update-status")
@Operation(summary = "更新 API 错误日志的状态")
@Parameters({
@Parameter(name = "id", description = "编号", required = true, example = "1024"),
@Parameter(name = "processStatus", description = "处理状态", required = true, example = "1")
})
@PreAuthorize("@ss.hasPermission('system:api-error-log:update-status')")
public CommonResult<Boolean> updateApiErrorLogProcess(@RequestParam("id") Long id,
@RequestParam("processStatus") Integer processStatus) {
apiErrorLogService.updateApiErrorLogProcess(id, processStatus, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得 API 错误日志")
@Parameter(name = "id", description = "编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:api-error-log:query')")
public CommonResult<ApiErrorLogRespVO> getApiErrorLog(@RequestParam("id") Long id) {
ApiErrorLogDO apiErrorLog = apiErrorLogService.getApiErrorLog(id);
return success(BeanUtils.toBean(apiErrorLog, ApiErrorLogRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得 API 错误日志分页")
@PreAuthorize("@ss.hasPermission('system:api-error-log:query')")
public CommonResult<PageResult<ApiErrorLogRespVO>> getApiErrorLogPage(@Valid ApiErrorLogPageReqVO pageReqVO) {
PageResult<ApiErrorLogDO> pageResult = apiErrorLogService.getApiErrorLogPage(pageReqVO);
return success(BeanUtils.toBean(pageResult, ApiErrorLogRespVO.class));
}
@GetMapping("/export-excel")
@Operation(summary = "导出 API 错误日志 Excel")
@PreAuthorize("@ss.hasPermission('system:api-error-log:export')")
@ApiAccessLog(operateType = EXPORT)
public void exportApiErrorLogExcel(@Valid ApiErrorLogPageReqVO exportReqVO,
HttpServletResponse response) throws IOException {
exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<ApiErrorLogDO> list = apiErrorLogService.getApiErrorLogPage(exportReqVO).getList();
// 导出 Excel
ExcelUtils.write(response, "API 错误日志.xls", "数据", ApiErrorLogRespVO.class,
BeanUtils.toBean(list, ApiErrorLogRespVO.class));
}
}

View File

@@ -0,0 +1,38 @@
package com.njcn.msgpush.module.system.controller.admin.logger.vo.apiaccesslog;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - API 访问日志分页 Request VO")
@Data
public class ApiAccessLogPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "666")
private Long userId;
@Schema(description = "用户类型", example = "2")
private Integer userType;
@Schema(description = "应用名", example = "dashboard")
private String applicationName;
@Schema(description = "请求地址,模糊匹配", example = "/xxx/yyy")
private String requestUrl;
@Schema(description = "开始时间", example = "[2022-07-01 00:00:00, 2022-07-01 23:59:59]")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] beginTime;
@Schema(description = "执行时长,大于等于,单位:毫秒", example = "100")
private Integer duration;
@Schema(description = "结果码", example = "0")
private Integer resultCode;
}

View File

@@ -0,0 +1,99 @@
package com.njcn.msgpush.module.system.controller.admin.logger.vo.apiaccesslog;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
import com.njcn.msgpush.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - API 访问日志 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ApiAccessLogRespVO {
@Schema(description = "日志主键", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("日志主键")
private Long id;
@Schema(description = "链路追踪编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "66600cb6-7852-11eb-9439-0242ac130002")
@ExcelProperty("链路追踪编号")
private String traceId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
@ExcelProperty("用户编号")
private Long userId;
@Schema(description = "用户类型,参见 UserTypeEnum 枚举", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@ExcelProperty(value = "用户类型", converter = DictConvert.class)
@DictFormat(DictTypeConstants.USER_TYPE)
private Integer userType;
@Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "dashboard")
@ExcelProperty("应用名")
private String applicationName;
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
@ExcelProperty("请求方法名")
private String requestMethod;
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/xxx/yyy")
@ExcelProperty("请求地址")
private String requestUrl;
@Schema(description = "请求参数")
@ExcelProperty("请求参数")
private String requestParams;
@Schema(description = "响应结果")
@ExcelProperty("响应结果")
private String responseBody;
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
@ExcelProperty("用户 IP")
private String userIp;
@Schema(description = "浏览器 UA", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
@ExcelProperty("浏览器 UA")
private String userAgent;
@Schema(description = "操作模块", requiredMode = Schema.RequiredMode.REQUIRED, example = "商品模块")
@ExcelProperty("操作模块")
private String operateModule;
@Schema(description = "操作名", requiredMode = Schema.RequiredMode.REQUIRED, example = "创建商品")
@ExcelProperty("操作名")
private String operateName;
@Schema(description = "操作分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "操作分类", converter = DictConvert.class)
@DictFormat(DictTypeConstants.OPERATE_TYPE)
private Integer operateType;
@Schema(description = "开始请求时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("开始请求时间")
private LocalDateTime beginTime;
@Schema(description = "结束请求时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("结束请求时间")
private LocalDateTime endTime;
@Schema(description = "执行时长", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
@ExcelProperty("执行时长")
private Integer duration;
@Schema(description = "结果码", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@ExcelProperty("结果码")
private Integer resultCode;
@Schema(description = "结果提示", example = "灿能,牛逼!")
@ExcelProperty("结果提示")
private String resultMsg;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}

View File

@@ -0,0 +1,35 @@
package com.njcn.msgpush.module.system.controller.admin.logger.vo.apierrorlog;
import com.njcn.msgpush.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static com.njcn.msgpush.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - API 错误日志分页 Request VO")
@Data
public class ApiErrorLogPageReqVO extends PageParam {
@Schema(description = "用户编号", example = "666")
private Long userId;
@Schema(description = "用户类型", example = "1")
private Integer userType;
@Schema(description = "应用名", example = "dashboard")
private String applicationName;
@Schema(description = "请求地址", example = "/xx/yy")
private String requestUrl;
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@Schema(description = "异常发生时间")
private LocalDateTime[] exceptionTime;
@Schema(description = "处理状态", example = "0")
private Integer processStatus;
}

View File

@@ -0,0 +1,112 @@
package com.njcn.msgpush.module.system.controller.admin.logger.vo.apierrorlog;
import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
import cn.idev.excel.annotation.ExcelProperty;
import com.njcn.msgpush.framework.excel.core.annotations.DictFormat;
import com.njcn.msgpush.framework.excel.core.convert.DictConvert;
import com.njcn.msgpush.module.system.enums.DictTypeConstants;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - API 错误日志 Response VO")
@Data
@ExcelIgnoreUnannotated
public class ApiErrorLogRespVO {
@Schema(description = "编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@ExcelProperty("编号")
private Long id;
@Schema(description = "链路追踪编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "66600cb6-7852-11eb-9439-0242ac130002")
@ExcelProperty("链路追踪编号")
private String traceId;
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "666")
@ExcelProperty("用户编号")
private Long userId;
@Schema(description = "用户类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty(value = "用户类型", converter = DictConvert.class)
@DictFormat(DictTypeConstants.USER_TYPE)
private Integer userType;
@Schema(description = "应用名", requiredMode = Schema.RequiredMode.REQUIRED, example = "dashboard")
@ExcelProperty("应用名")
private String applicationName;
@Schema(description = "请求方法名", requiredMode = Schema.RequiredMode.REQUIRED, example = "GET")
@ExcelProperty("请求方法名")
private String requestMethod;
@Schema(description = "请求地址", requiredMode = Schema.RequiredMode.REQUIRED, example = "/xx/yy")
@ExcelProperty("请求地址")
private String requestUrl;
@Schema(description = "请求参数", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("请求参数")
private String requestParams;
@Schema(description = "用户 IP", requiredMode = Schema.RequiredMode.REQUIRED, example = "127.0.0.1")
@ExcelProperty("用户 IP")
private String userIp;
@Schema(description = "浏览器 UA", requiredMode = Schema.RequiredMode.REQUIRED, example = "Mozilla/5.0")
@ExcelProperty("浏览器 UA")
private String userAgent;
@Schema(description = "异常发生时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常发生时间")
private LocalDateTime exceptionTime;
@Schema(description = "异常名", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常名")
private String exceptionName;
@Schema(description = "异常导致的消息", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常导致的消息")
private String exceptionMessage;
@Schema(description = "异常导致的根消息", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常导致的根消息")
private String exceptionRootCauseMessage;
@Schema(description = "异常的栈轨迹", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常的栈轨迹")
private String exceptionStackTrace;
@Schema(description = "异常发生的类全名", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常发生的类全名")
private String exceptionClassName;
@Schema(description = "异常发生的类文件", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常发生的类文件")
private String exceptionFileName;
@Schema(description = "异常发生的方法名", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常发生的方法名")
private String exceptionMethodName;
@Schema(description = "异常发生的方法所在行", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("异常发生的方法所在行")
private Integer exceptionLineNumber;
@Schema(description = "处理状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@ExcelProperty(value = "处理状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.API_ERROR_LOG_PROCESS_STATUS)
private Integer processStatus;
@Schema(description = "处理时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("处理时间")
private LocalDateTime processTime;
@Schema(description = "处理用户编号", example = "233")
@ExcelProperty("处理用户编号")
private Integer processUserId;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
}

View File

@@ -29,7 +29,7 @@ public class OperateLogRespVO implements VO {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = "nickname", ref = "userName")
private Long userId;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen")
@ExcelProperty("操作人")
private String userName;

View File

@@ -1,16 +1,16 @@
package com.njcn.msgpush.module.system.controller.admin.notice;
import cn.hutool.core.lang.Assert;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.common.pojo.CommonResult;
import com.njcn.msgpush.framework.common.pojo.PageResult;
import com.njcn.msgpush.framework.common.util.object.BeanUtils;
import com.njcn.msgpush.module.infra.api.websocket.WebSocketSenderApi;
import com.njcn.msgpush.module.system.controller.admin.notice.vo.NoticePageReqVO;
import com.njcn.msgpush.module.system.controller.admin.notice.vo.NoticeRespVO;
import com.njcn.msgpush.module.system.controller.admin.notice.vo.NoticeSaveReqVO;
import com.njcn.msgpush.module.system.dal.dataobject.notice.NoticeDO;
import com.njcn.msgpush.module.system.service.notice.NoticeService;
import com.njcn.msgpush.framework.common.enums.UserTypeEnum;
import com.njcn.msgpush.framework.websocket.core.sender.WebSocketMessageSender;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -34,7 +34,7 @@ public class NoticeController {
private NoticeService noticeService;
@Resource
private WebSocketSenderApi webSocketSenderApi;
private WebSocketMessageSender webSocketMessageSender;
@PostMapping("/create")
@Operation(summary = "创建通知公告")
@@ -95,7 +95,7 @@ public class NoticeController {
NoticeDO notice = noticeService.getNotice(id);
Assert.notNull(notice, "公告不能为空");
// 通过 websocket 推送给在线的用户
webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), "notice-push", notice);
webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), "notice-push", notice);
return success(true);
}

View File

@@ -25,7 +25,7 @@ public class NotifyMessageRespVO {
@Schema(description = "模板编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "test_01")
private String templateCode;
@Schema(description = "模版发送人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
@Schema(description = "模版发送人名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "awen")
private String templateNickname;
@Schema(description = "模版内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "测试内容")

Some files were not shown because too many files have changed in this diff Show More