feat(admin): Implement admin module foundation
This commit is contained in:
@@ -12,10 +12,141 @@
|
||||
<artifactId>video-admin</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<!-- 引入公共模块 -->
|
||||
<dependency>
|
||||
<groupId>com.nuaa.video</groupId>
|
||||
<artifactId>video-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Web -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Cloud Alibaba Nacos Discovery -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Cloud Alibaba Nacos Config -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Actuator -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Validation -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Cloud OpenFeign -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Cloud LoadBalancer -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Cloud Alibaba Sentinel -->
|
||||
<dependency>
|
||||
<groupId>com.alibaba.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- MySQL Connector -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- MyBatis Plus -->
|
||||
<dependency>
|
||||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
|
||||
<version>3.5.11</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Redis -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-redis</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- JWT相关 -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.12.3</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.12.3</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- AOP -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Lombok -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Hutool工具包 -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.26</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
@@ -1,11 +1,21 @@
|
||||
package com.nuaa.video.admin;
|
||||
|
||||
import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
|
||||
/**
|
||||
* 管理后台启动类
|
||||
*/
|
||||
@SpringBootApplication
|
||||
@EnableDiscoveryClient
|
||||
@EnableFeignClients
|
||||
@MapperScan("com.nuaa.video.admin.map" +
|
||||
"" +
|
||||
"" +
|
||||
"per")
|
||||
public class AdminApplication {
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(AdminApplication.class, args);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.nuaa.video.admin.annotation;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* 操作日志注解
|
||||
*/
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface OperationLog {
|
||||
/**
|
||||
* 操作描述
|
||||
*/
|
||||
String value() default "";
|
||||
}
|
||||
@@ -0,0 +1,220 @@
|
||||
package com.nuaa.video.admin.aspect;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nuaa.video.admin.annotation.OperationLog;
|
||||
import com.nuaa.video.admin.entity.AdminOperationLog;
|
||||
import com.nuaa.video.admin.mapper.AdminOperationLogMapper;
|
||||
import com.nuaa.video.admin.security.AdminUserDetails;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.annotation.AfterReturning;
|
||||
import org.aspectj.lang.annotation.AfterThrowing;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.aspectj.lang.reflect.MethodSignature;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 操作日志切面
|
||||
*/
|
||||
@Slf4j
|
||||
@Aspect
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class OperationLogAspect {
|
||||
|
||||
private final AdminOperationLogMapper operationLogMapper;
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
@Value("${operation-log.enabled:true}")
|
||||
private boolean enabled;
|
||||
|
||||
@Value("#{'${operation-log.exclude-paths:}'.split(',')}")
|
||||
private List<String> excludePaths;
|
||||
|
||||
/**
|
||||
* 切入点
|
||||
*/
|
||||
@Pointcut("@annotation(com.nuaa.video.admin.annotation.OperationLog)")
|
||||
public void operationLogPointcut() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理完请求后执行
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param result 返回值
|
||||
*/
|
||||
@AfterReturning(pointcut = "operationLogPointcut()", returning = "result")
|
||||
public void doAfterReturning(JoinPoint joinPoint, Object result) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleLog(joinPoint, null, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拦截异常操作
|
||||
*
|
||||
* @param joinPoint 切点
|
||||
* @param e 异常
|
||||
*/
|
||||
@AfterThrowing(value = "operationLogPointcut()", throwing = "e")
|
||||
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
|
||||
if (!enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
handleLog(joinPoint, e, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理日志
|
||||
*/
|
||||
private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
|
||||
try {
|
||||
// 获取请求信息
|
||||
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||
if (attributes == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
String requestURI = request.getRequestURI();
|
||||
|
||||
// 检查是否需要排除
|
||||
if (isExclude(requestURI)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取注解信息
|
||||
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
|
||||
Method method = signature.getMethod();
|
||||
OperationLog annotation = method.getAnnotation(OperationLog.class);
|
||||
|
||||
// 如果未配置注解,则不记录日志
|
||||
if (annotation == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取当前登录用户
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
Long adminId = null;
|
||||
String username = "unknown";
|
||||
|
||||
if (authentication != null && authentication.getPrincipal() instanceof AdminUserDetails) {
|
||||
AdminUserDetails userDetails = (AdminUserDetails) authentication.getPrincipal();
|
||||
adminId = userDetails.getId();
|
||||
username = userDetails.getUsername();
|
||||
} else if (authentication != null && authentication.getPrincipal() instanceof String) {
|
||||
username = (String) authentication.getPrincipal();
|
||||
}
|
||||
|
||||
// 获取操作
|
||||
String operation = annotation.value();
|
||||
|
||||
// 获取请求方法
|
||||
String methodName = joinPoint.getTarget().getClass().getName() + "." + method.getName();
|
||||
|
||||
// 获取请求参数
|
||||
String params = getParams(joinPoint);
|
||||
|
||||
// 获取IP地址
|
||||
String ip = getIpAddress(request);
|
||||
|
||||
// 创建日志对象
|
||||
AdminOperationLog operationLog = new AdminOperationLog();
|
||||
operationLog.setAdminId(adminId);
|
||||
operationLog.setUsername(username);
|
||||
operationLog.setOperation(operation);
|
||||
operationLog.setMethod(methodName);
|
||||
operationLog.setParams(params);
|
||||
operationLog.setIp(ip);
|
||||
operationLog.setOperationTime(LocalDateTime.now());
|
||||
|
||||
// 处理异常和结果
|
||||
if (e != null) {
|
||||
operationLog.setStatus(0); // 失败
|
||||
operationLog.setErrorMsg(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : e.toString());
|
||||
} else {
|
||||
operationLog.setStatus(1); // 成功
|
||||
}
|
||||
|
||||
// 保存日志
|
||||
operationLogMapper.insert(operationLog);
|
||||
|
||||
} catch (Exception ex) {
|
||||
log.error("记录操作日志失败", ex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*/
|
||||
private String getParams(JoinPoint joinPoint) {
|
||||
try {
|
||||
Object[] args = joinPoint.getArgs();
|
||||
|
||||
// 过滤掉一些不需要记录的参数类型
|
||||
args = Arrays.stream(args)
|
||||
.filter(arg -> !(arg instanceof HttpServletRequest || arg instanceof HttpServletRequest))
|
||||
.toArray();
|
||||
|
||||
return objectMapper.writeValueAsString(args);
|
||||
} catch (Exception e) {
|
||||
log.error("获取请求参数失败", e);
|
||||
return "获取参数失败";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的IP地址
|
||||
*/
|
||||
private String getIpAddress(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
// 多个代理的情况,第一个IP为客户端真实IP
|
||||
if (ip != null && ip.indexOf(",") > 0) {
|
||||
ip = ip.substring(0, ip.indexOf(","));
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要排除请求
|
||||
*/
|
||||
private boolean isExclude(String requestURI) {
|
||||
if (excludePaths != null && !excludePaths.isEmpty()) {
|
||||
for (String pattern : excludePaths) {
|
||||
if (StringUtils.hasText(pattern) && requestURI.matches(pattern.trim())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.nuaa.video.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.admin.annotation.OperationLog;
|
||||
import com.nuaa.video.admin.dto.AdminUserDTO;
|
||||
import com.nuaa.video.admin.entity.AdminUser;
|
||||
import com.nuaa.video.admin.service.AdminUserService;
|
||||
import com.nuaa.video.admin.vo.AdminUserVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 管理员控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin/user")
|
||||
@RequiredArgsConstructor
|
||||
public class AdminUserController {
|
||||
|
||||
private final AdminUserService adminUserService;
|
||||
|
||||
/**
|
||||
* 获取管理员列表
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@GetMapping("/list")
|
||||
public Result<Page<AdminUserVO>> listUsers(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||
Page<AdminUser> pageParam = new Page<>(page, size);
|
||||
return adminUserService.listUsers(pageParam, keyword);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取管理员详情
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@GetMapping("/{id}")
|
||||
public Result<AdminUserVO> getUser(@PathVariable Long id) {
|
||||
return adminUserService.getUserDetail(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加管理员
|
||||
*/
|
||||
@OperationLog("添加管理员")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@PostMapping
|
||||
public Result<Void> addUser(@RequestBody @Valid AdminUserDTO adminUserDTO) {
|
||||
return adminUserService.addUser(adminUserDTO, getCurrentUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新管理员
|
||||
*/
|
||||
@OperationLog("更新管理员")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@PutMapping
|
||||
public Result<Void> updateUser(@RequestBody @Valid AdminUserDTO adminUserDTO) {
|
||||
return adminUserService.updateUser(adminUserDTO, getCurrentUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除管理员
|
||||
*/
|
||||
@OperationLog("删除管理员")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@DeleteMapping("/{id}")
|
||||
public Result<Void> deleteUser(@PathVariable Long id) {
|
||||
return adminUserService.deleteUser(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用/禁用管理员
|
||||
*/
|
||||
@OperationLog("修改管理员状态")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@PutMapping("/{id}/status/{status}")
|
||||
public Result<Void> updateStatus(@PathVariable Long id, @PathVariable Integer status) {
|
||||
return adminUserService.updateStatus(id, status, getCurrentUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*/
|
||||
@OperationLog("重置管理员密码")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@PutMapping("/{id}/reset-password")
|
||||
public Result<Void> resetPassword(@PathVariable Long id, @RequestParam String newPassword) {
|
||||
return adminUserService.resetPassword(id, newPassword, getCurrentUsername());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录用户名
|
||||
*/
|
||||
private String getCurrentUsername() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
return authentication.getName();
|
||||
}
|
||||
return "system";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.nuaa.video.admin.controller;
|
||||
|
||||
import com.nuaa.video.admin.annotation.OperationLog;
|
||||
import com.nuaa.video.admin.dto.LoginDTO;
|
||||
import com.nuaa.video.admin.security.AdminUserDetails;
|
||||
import com.nuaa.video.admin.service.AuthService;
|
||||
import com.nuaa.video.admin.vo.CaptchaVO;
|
||||
import com.nuaa.video.admin.vo.LoginVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 认证控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin/auth")
|
||||
@RequiredArgsConstructor
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
/**
|
||||
* 获取验证码
|
||||
*/
|
||||
@GetMapping("/captcha")
|
||||
public Result<CaptchaVO> getCaptcha() {
|
||||
return authService.generateCaptcha();
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
@PostMapping("/login")
|
||||
public Result<LoginVO> login(@RequestBody @Valid LoginDTO loginDTO, HttpServletRequest request) {
|
||||
return authService.login(loginDTO, request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*/
|
||||
@OperationLog("退出登录")
|
||||
@PostMapping("/logout")
|
||||
public Result<Void> logout(@RequestHeader("Authorization") String authorization) {
|
||||
// 从请求头中提取token
|
||||
String token = null;
|
||||
if (authorization != null && authorization.startsWith("Bearer ")) {
|
||||
token = authorization.substring(7);
|
||||
}
|
||||
return authService.logout(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
@GetMapping("/info")
|
||||
public Result<LoginVO> getCurrentUser(@AuthenticationPrincipal AdminUserDetails userDetails) {
|
||||
return authService.getCurrentUser(userDetails);
|
||||
}
|
||||
}
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package com.nuaa.video.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.admin.annotation.OperationLog;
|
||||
import com.nuaa.video.admin.feign.ContentFeignClient;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 内容管理控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin/platform/content")
|
||||
@RequiredArgsConstructor
|
||||
public class ContentManageController {
|
||||
|
||||
private final ContentFeignClient contentFeignClient;
|
||||
|
||||
/**
|
||||
* 获取视频列表
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('video:manage')")
|
||||
@GetMapping("/video/list")
|
||||
public Result<Page<Object>> getVideoList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "categoryId", required = false) Long categoryId,
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.getVideoList(page, size, keyword, categoryId, status, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取视频详情
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('video:manage')")
|
||||
@GetMapping("/video/{videoId}")
|
||||
public Result<Object> getVideoDetail(
|
||||
@PathVariable Long videoId,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.getVideoDetail(videoId, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 审核视频
|
||||
*/
|
||||
@OperationLog("审核视频")
|
||||
@PreAuthorize("hasAuthority('video:manage')")
|
||||
@PostMapping("/video/review/{videoId}")
|
||||
public Result<Void> reviewVideo(
|
||||
@PathVariable Long videoId,
|
||||
@RequestParam Integer status,
|
||||
@RequestParam(required = false) String reason,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.reviewVideo(videoId, status, reason, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下架视频
|
||||
*/
|
||||
@OperationLog("下架视频")
|
||||
@PreAuthorize("hasAuthority('video:manage')")
|
||||
@PutMapping("/video/disable/{videoId}")
|
||||
public Result<Void> disableVideo(
|
||||
@PathVariable Long videoId,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.disableVideo(videoId, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分类列表
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('category:manage')")
|
||||
@GetMapping("/category/list")
|
||||
public Result<Object> getCategoryList(@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.getCategoryList(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加分类
|
||||
*/
|
||||
@OperationLog("添加分类")
|
||||
@PreAuthorize("hasAuthority('category:manage')")
|
||||
@PostMapping("/category")
|
||||
public Result<Void> addCategory(
|
||||
@RequestBody Object category,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.addCategory(category, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分类
|
||||
*/
|
||||
@OperationLog("更新分类")
|
||||
@PreAuthorize("hasAuthority('category:manage')")
|
||||
@PutMapping("/category")
|
||||
public Result<Void> updateCategory(
|
||||
@RequestBody Object category,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.updateCategory(category, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@OperationLog("删除分类")
|
||||
@PreAuthorize("hasAuthority('category:manage')")
|
||||
@DeleteMapping("/category/{categoryId}")
|
||||
public Result<Void> deleteCategory(
|
||||
@PathVariable Long categoryId,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return contentFeignClient.deleteCategory(categoryId, token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.nuaa.video.admin.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.admin.annotation.OperationLog;
|
||||
import com.nuaa.video.admin.feign.UserFeignClient;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户管理控制器
|
||||
*/
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/admin/platform/user")
|
||||
@RequiredArgsConstructor
|
||||
public class UserManageController {
|
||||
|
||||
private final UserFeignClient userFeignClient;
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@GetMapping("/list")
|
||||
public Result<Page<Object>> getUserList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return userFeignClient.getUserList(page, size, keyword, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@GetMapping("/{userId}")
|
||||
public Result<Object> getUserDetail(
|
||||
@PathVariable Long userId,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return userFeignClient.getUserDetail(userId, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 禁用/启用用户
|
||||
*/
|
||||
@OperationLog("修改用户状态")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@PutMapping("/{userId}/status/{status}")
|
||||
public Result<Void> updateUserStatus(
|
||||
@PathVariable Long userId,
|
||||
@PathVariable Integer status,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return userFeignClient.updateUserStatus(userId, status, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*/
|
||||
@OperationLog("重置用户密码")
|
||||
@PreAuthorize("hasAuthority('user:manage')")
|
||||
@PutMapping("/{userId}/reset-password")
|
||||
public Result<Void> resetUserPassword(
|
||||
@PathVariable Long userId,
|
||||
@RequestParam String newPassword,
|
||||
@RequestHeader("Authorization") String token) {
|
||||
return userFeignClient.resetUserPassword(userId, newPassword, token);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.nuaa.video.admin.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 权限DTO
|
||||
*/
|
||||
@Data
|
||||
public class AdminPermissionDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 权限ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 权限名称
|
||||
*/
|
||||
@NotBlank(message = "权限名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 权限编码
|
||||
*/
|
||||
@NotBlank(message = "权限编码不能为空")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 权限描述
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.admin.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色DTO
|
||||
*/
|
||||
@Data
|
||||
public class AdminRoleDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
@NotBlank(message = "角色名称不能为空")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
@NotBlank(message = "角色编码不能为空")
|
||||
@Pattern(regexp = "^ADMIN_[A-Z_]+$", message = "角色编码必须以ADMIN_开头")
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 权限ID集合
|
||||
*/
|
||||
private List<Long> permissionIds;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.nuaa.video.admin.dto;
|
||||
|
||||
import jakarta.validation.constraints.Email;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Pattern;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理员DTO
|
||||
*/
|
||||
@Data
|
||||
public class AdminUserDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@Size(min = 5, max = 20, message = "用户名长度必须在5-20之间")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
@NotBlank(message = "真实姓名不能为空")
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@Email(message = "邮箱格式不正确")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 状态(0:禁用,1:正常)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 角色ID集合
|
||||
*/
|
||||
private List<Long> roleIds;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.nuaa.video.admin.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 登录DTO
|
||||
*/
|
||||
@Data
|
||||
public class LoginDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 验证码
|
||||
*/
|
||||
private String captcha;
|
||||
|
||||
/**
|
||||
* 验证码Key
|
||||
*/
|
||||
private String captchaKey;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.nuaa.video.admin.dto;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 系统配置DTO
|
||||
*/
|
||||
@Data
|
||||
public class SystemConfigDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 配置ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 配置键
|
||||
*/
|
||||
@NotBlank(message = "配置键不能为空")
|
||||
private String configKey;
|
||||
|
||||
/**
|
||||
* 配置值
|
||||
*/
|
||||
@NotBlank(message = "配置值不能为空")
|
||||
private String configValue;
|
||||
|
||||
/**
|
||||
* 配置描述
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 操作日志实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_operation_log")
|
||||
public class AdminOperationLog implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
private Long adminId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 操作
|
||||
*/
|
||||
private String operation;
|
||||
|
||||
/**
|
||||
* 方法名
|
||||
*/
|
||||
private String method;
|
||||
|
||||
/**
|
||||
* 参数
|
||||
*/
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* IP地址
|
||||
*/
|
||||
private String ip;
|
||||
|
||||
/**
|
||||
* 状态(0:失败,1:成功)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
private String errorMsg;
|
||||
|
||||
/**
|
||||
* 操作时间
|
||||
*/
|
||||
private LocalDateTime operationTime;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 管理员权限实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_permission")
|
||||
public class AdminPermission implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 权限ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 权限名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 权限编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 权限描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理员角色实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_role")
|
||||
public class AdminRole implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 权限ID集合(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> permissionIds;
|
||||
|
||||
/**
|
||||
* 权限集合(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<AdminPermission> permissions;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 角色权限关联实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_role_permission")
|
||||
public class AdminRolePermission implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long roleId;
|
||||
|
||||
/**
|
||||
* 权限ID
|
||||
*/
|
||||
private Long permissionId;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统配置实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_system_config")
|
||||
public class AdminSystemConfig implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 配置键
|
||||
*/
|
||||
private String configKey;
|
||||
|
||||
/**
|
||||
* 配置值
|
||||
*/
|
||||
private String configValue;
|
||||
|
||||
/**
|
||||
* 配置描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理员实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_user")
|
||||
public class AdminUser implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码(加密)
|
||||
*/
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 加密盐值
|
||||
*/
|
||||
private String salt;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 状态(0:禁用,1:正常)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
private String lastLoginIp;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 更新人
|
||||
*/
|
||||
private String updatedBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
/**
|
||||
* 角色ID集合(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<Long> roleIds;
|
||||
|
||||
/**
|
||||
* 角色集合(非数据库字段)
|
||||
*/
|
||||
@TableField(exist = false)
|
||||
private List<AdminRole> roles;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.admin.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 管理员角色关联实体
|
||||
*/
|
||||
@Data
|
||||
@TableName("admin_user_role")
|
||||
public class AdminUserRole implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 主键ID
|
||||
*/
|
||||
@TableId(value = "id", type = IdType.AUTO)
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
private Long adminId;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long roleId;
|
||||
|
||||
/**
|
||||
* 创建人
|
||||
*/
|
||||
private String createdBy;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.nuaa.video.admin.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 评论服务Feign客户端
|
||||
*/
|
||||
@FeignClient(name = "video-comment", fallbackFactory = CommentFeignClientFallbackFactory.class)
|
||||
public interface CommentFeignClient {
|
||||
|
||||
/**
|
||||
* 获取评论列表
|
||||
*/
|
||||
@GetMapping("/comment/admin/list")
|
||||
Result<Page<Object>> getCommentList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "videoId", required = false) Long videoId,
|
||||
@RequestParam(value = "userId", required = false) Long userId,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 获取评论详情
|
||||
*/
|
||||
@GetMapping("/comment/admin/{commentId}")
|
||||
Result<Object> getCommentDetail(@PathVariable("commentId") Long commentId,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 删除评论
|
||||
*/
|
||||
@DeleteMapping("/comment/admin/{commentId}")
|
||||
Result<Void> deleteComment(@PathVariable("commentId") Long commentId,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 获取评论统计信息
|
||||
*/
|
||||
@GetMapping("/comment/admin/stats")
|
||||
Result<Object> getCommentStats(@RequestHeader("Authorization") String token);
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package com.nuaa.video.admin.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 评论服务Feign客户端降级工厂
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class CommentFeignClientFallbackFactory implements FallbackFactory<CommentFeignClient> {
|
||||
|
||||
@Override
|
||||
public CommentFeignClient create(Throwable throwable) {
|
||||
log.error("CommentFeignClient调用失败,原因:", throwable);
|
||||
|
||||
return new CommentFeignClient() {
|
||||
@Override
|
||||
public Result<Page<Object>> getCommentList(Integer page, Integer size, Long videoId, Long userId, String keyword, String token) {
|
||||
return Result.error("获取评论列表失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Object> getCommentDetail(Long commentId, String token) {
|
||||
return Result.error("获取评论详情失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> deleteComment(Long commentId, String token) {
|
||||
return Result.error("删除评论失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Object> getCommentStats(String token) {
|
||||
return Result.error("获取评论统计信息失败:服务不可用");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.nuaa.video.admin.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 内容服务Feign客户端
|
||||
*/
|
||||
@FeignClient(name = "video-content", fallbackFactory = ContentFeignClientFallbackFactory.class)
|
||||
public interface ContentFeignClient {
|
||||
|
||||
/**
|
||||
* 获取视频列表
|
||||
*/
|
||||
@GetMapping("/content/video/admin/list")
|
||||
Result<Page<Object>> getVideoList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestParam(value = "categoryId", required = false) Long categoryId,
|
||||
@RequestParam(value = "status", required = false) Integer status,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 获取视频详情
|
||||
*/
|
||||
@GetMapping("/content/video/admin/{videoId}")
|
||||
Result<Object> getVideoDetail(@PathVariable("videoId") Long videoId,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 审核视频
|
||||
*/
|
||||
@PostMapping("/content/video/admin/review/{videoId}")
|
||||
Result<Void> reviewVideo(@PathVariable("videoId") Long videoId,
|
||||
@RequestParam("status") Integer status,
|
||||
@RequestParam(value = "reason", required = false) String reason,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 下架视频
|
||||
*/
|
||||
@PutMapping("/content/video/admin/disable/{videoId}")
|
||||
Result<Void> disableVideo(@PathVariable("videoId") Long videoId,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 分类列表
|
||||
*/
|
||||
@GetMapping("/content/category/admin/list")
|
||||
Result<Object> getCategoryList(@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 添加分类
|
||||
*/
|
||||
@PostMapping("/content/category/admin")
|
||||
Result<Void> addCategory(@RequestBody Object category, @RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 更新分类
|
||||
*/
|
||||
@PutMapping("/content/category/admin")
|
||||
Result<Void> updateCategory(@RequestBody Object category, @RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 删除分类
|
||||
*/
|
||||
@DeleteMapping("/content/category/admin/{categoryId}")
|
||||
Result<Void> deleteCategory(@PathVariable("categoryId") Long categoryId,
|
||||
@RequestHeader("Authorization") String token);
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
package com.nuaa.video.admin.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 内容服务Feign客户端降级工厂
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ContentFeignClientFallbackFactory implements FallbackFactory<ContentFeignClient> {
|
||||
|
||||
@Override
|
||||
public ContentFeignClient create(Throwable throwable) {
|
||||
log.error("ContentFeignClient调用失败,原因:", throwable);
|
||||
|
||||
return new ContentFeignClient() {
|
||||
@Override
|
||||
public Result<Page<Object>> getVideoList(Integer page, Integer size, String keyword, Long categoryId, Integer status, String token) {
|
||||
return Result.error("获取视频列表失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Object> getVideoDetail(Long videoId, String token) {
|
||||
return Result.error("获取视频详情失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> reviewVideo(Long videoId, Integer status, String reason, String token) {
|
||||
return Result.error("审核视频失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> disableVideo(Long videoId, String token) {
|
||||
return Result.error("下架视频失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Object> getCategoryList(String token) {
|
||||
return Result.error("获取分类列表失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> addCategory(Object category, String token) {
|
||||
return Result.error("添加分类失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> updateCategory(Object category, String token) {
|
||||
return Result.error("更新分类失败:服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> deleteCategory(Long categoryId, String token) {
|
||||
return Result.error("删除分类失败:服务不可用");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.nuaa.video.admin.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 用户服务Feign客户端
|
||||
*/
|
||||
@FeignClient(name = "video-user", fallbackFactory = UserFeignClientFallbackFactory.class)
|
||||
public interface UserFeignClient {
|
||||
|
||||
/**
|
||||
* 获取用户列表
|
||||
*/
|
||||
@GetMapping("/user/profile/admin/list")
|
||||
Result<Page<Object>> getUserList(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||
@RequestParam(value = "keyword", required = false) String keyword,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 获取用户详情
|
||||
*/
|
||||
@GetMapping("/user/profile/admin/{userId}")
|
||||
Result<Object> getUserDetail(@PathVariable("userId") Long userId,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 禁用/启用用户
|
||||
*/
|
||||
@PutMapping("/user/profile/admin/status/{userId}/{status}")
|
||||
Result<Void> updateUserStatus(@PathVariable("userId") Long userId,
|
||||
@PathVariable("status") Integer status,
|
||||
@RequestHeader("Authorization") String token);
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*/
|
||||
@PutMapping("/user/profile/admin/reset-password/{userId}")
|
||||
Result<Void> resetUserPassword(@PathVariable("userId") Long userId,
|
||||
@RequestParam String newPassword,
|
||||
@RequestHeader("Authorization") String token);
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
package com.nuaa.video.admin.feign;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FallbackFactory;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 用户服务Feign客户端降级工厂
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class UserFeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
|
||||
|
||||
@Override
|
||||
public UserFeignClient create(Throwable cause) {
|
||||
log.error("用户服务调用失败:", cause);
|
||||
return new UserFeignClient() {
|
||||
@Override
|
||||
public Result<Page<Object>> getUserList(Integer page, Integer size, String keyword, String token) {
|
||||
log.error("获取用户列表失败");
|
||||
return Result.error("获取用户列表失败,用户服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Object> getUserDetail(Long userId, String token) {
|
||||
log.error("获取用户详情失败,用户ID: {}", userId);
|
||||
return Result.error("获取用户详情失败,用户服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> updateUserStatus(Long userId, Integer status, String token) {
|
||||
log.error("更新用户状态失败,用户ID: {}, 状态: {}", userId, status);
|
||||
return Result.error("更新用户状态失败,用户服务不可用");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> resetUserPassword(Long userId, String newPassword, String token) {
|
||||
log.error("重置用户密码失败,用户ID: {}", userId);
|
||||
return Result.error("重置用户密码失败,用户服务不可用");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.admin.entity.AdminOperationLog;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 操作日志Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminOperationLogMapper extends BaseMapper<AdminOperationLog> {
|
||||
|
||||
/**
|
||||
* 分页查询操作日志
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param username 用户名
|
||||
* @param operation 操作
|
||||
* @param status 状态
|
||||
* @param startTime 开始时间
|
||||
* @param endTime 结束时间
|
||||
* @return 操作日志列表
|
||||
*/
|
||||
Page<AdminOperationLog> selectLogList(
|
||||
Page<AdminOperationLog> page,
|
||||
@Param("username") String username,
|
||||
@Param("operation") String operation,
|
||||
@Param("status") Integer status,
|
||||
@Param("startTime") String startTime,
|
||||
@Param("endTime") String endTime);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.admin.entity.AdminPermission;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 权限Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminPermissionMapper extends BaseMapper<AdminPermission> {
|
||||
|
||||
/**
|
||||
* 获取角色的权限列表
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @return 权限列表
|
||||
*/
|
||||
List<AdminPermission> selectPermissionsByRoleId(@Param("roleId") Long roleId);
|
||||
|
||||
/**
|
||||
* 获取管理员的权限列表
|
||||
*
|
||||
* @param adminId 管理员ID
|
||||
* @return 权限列表
|
||||
*/
|
||||
List<AdminPermission> selectPermissionsByAdminId(@Param("adminId") Long adminId);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.admin.entity.AdminRole;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminRoleMapper extends BaseMapper<AdminRole> {
|
||||
|
||||
/**
|
||||
* 查询角色列表(包括权限)
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param keyword 关键字
|
||||
* @return 角色列表
|
||||
*/
|
||||
Page<AdminRole> selectRoleList(Page<AdminRole> page, @Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 获取角色详情(包括权限)
|
||||
*
|
||||
* @param id 角色ID
|
||||
* @return 角色详情
|
||||
*/
|
||||
AdminRole selectRoleDetail(@Param("id") Long id);
|
||||
|
||||
/**
|
||||
* 获取管理员的角色列表
|
||||
*
|
||||
* @param adminId 管理员ID
|
||||
* @return 角色列表
|
||||
*/
|
||||
List<AdminRole> selectRolesByAdminId(@Param("adminId") Long adminId);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.admin.entity.AdminRolePermission;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色权限关联Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminRolePermissionMapper extends BaseMapper<AdminRolePermission> {
|
||||
|
||||
/**
|
||||
* 批量插入角色权限关联
|
||||
*
|
||||
* @param roleId 角色ID
|
||||
* @param permissionIds 权限ID列表
|
||||
* @param createdBy 创建人
|
||||
* @return 影响行数
|
||||
*/
|
||||
int batchInsert(@Param("roleId") Long roleId, @Param("permissionIds") List<Long> permissionIds, @Param("createdBy") String createdBy);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.admin.entity.AdminSystemConfig;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 系统配置Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminSystemConfigMapper extends BaseMapper<AdminSystemConfig> {
|
||||
|
||||
/**
|
||||
* 根据配置键获取配置值
|
||||
*
|
||||
* @param configKey 配置键
|
||||
* @return 配置值
|
||||
*/
|
||||
String getConfigValue(@Param("configKey") String configKey);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.nuaa.video.admin.entity.AdminUser;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 管理员Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminUserMapper extends BaseMapper<AdminUser> {
|
||||
|
||||
/**
|
||||
* 根据用户名查询管理员信息(包括角色和权限)
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 管理员信息
|
||||
*/
|
||||
AdminUser selectByUsername(@Param("username") String username);
|
||||
|
||||
/**
|
||||
* 查询管理员列表(包括角色)
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param keyword 关键字
|
||||
* @return 管理员列表
|
||||
*/
|
||||
Page<AdminUser> selectUserList(Page<AdminUser> page, @Param("keyword") String keyword);
|
||||
|
||||
/**
|
||||
* 获取管理员详情(包括角色和权限)
|
||||
*
|
||||
* @param id 管理员ID
|
||||
* @return 管理员详情
|
||||
*/
|
||||
AdminUser selectUserDetail(@Param("id") Long id);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.nuaa.video.admin.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.nuaa.video.admin.entity.AdminUserRole;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 用户角色关联Mapper
|
||||
*/
|
||||
@Mapper
|
||||
public interface AdminUserRoleMapper extends BaseMapper<AdminUserRole> {
|
||||
|
||||
/**
|
||||
* 批量插入用户角色关联
|
||||
*
|
||||
* @param adminId 管理员ID
|
||||
* @param roleIds 角色ID列表
|
||||
* @param createdBy 创建人
|
||||
* @return 影响行数
|
||||
*/
|
||||
int batchInsert(@Param("adminId") Long adminId, @Param("roleIds") List<Long> roleIds, @Param("createdBy") String createdBy);
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.nuaa.video.admin.security;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import lombok.Data;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Spring Security用户详情
|
||||
*/
|
||||
@Data
|
||||
public class AdminUserDetails implements UserDetails {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 密码
|
||||
*/
|
||||
@JsonIgnore
|
||||
private String password;
|
||||
|
||||
/**
|
||||
* 授权列表
|
||||
*/
|
||||
private List<SimpleGrantedAuthority> authorities;
|
||||
|
||||
@Override
|
||||
public Collection<? extends GrantedAuthority> getAuthorities() {
|
||||
return authorities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAccountNonLocked() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCredentialsNonExpired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.nuaa.video.admin.security;
|
||||
|
||||
import jakarta.servlet.FilterChain;
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* JWT认证过滤器
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthenticationFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
// 从请求头获取JWT令牌
|
||||
String token = getJwtFromRequest(request);
|
||||
|
||||
// 验证令牌
|
||||
if (StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)) {
|
||||
// 从令牌中获取用户信息
|
||||
Long adminId = jwtTokenProvider.getAdminIdFromToken(token);
|
||||
String username = jwtTokenProvider.getUsernameFromToken(token);
|
||||
List<String> authorities = jwtTokenProvider.getAuthoritiesFromToken(token);
|
||||
|
||||
// 转换为Spring Security的授权对象
|
||||
List<SimpleGrantedAuthority> simpleGrantedAuthorities = authorities.stream()
|
||||
.map(SimpleGrantedAuthority::new)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 创建认证对象
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
username, null, simpleGrantedAuthorities);
|
||||
|
||||
// 设置用户详情
|
||||
AdminUserDetails userDetails = new AdminUserDetails();
|
||||
userDetails.setId(adminId);
|
||||
userDetails.setUsername(username);
|
||||
userDetails.setAuthorities(simpleGrantedAuthorities);
|
||||
authentication.setDetails(userDetails);
|
||||
|
||||
// 设置认证信息到安全上下文
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
// 将管理员ID和用户名设置到请求属性中,便于后续使用
|
||||
request.setAttribute("adminId", adminId);
|
||||
request.setAttribute("username", username);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("无法设置用户认证", e);
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从请求头中提取JWT令牌
|
||||
*/
|
||||
private String getJwtFromRequest(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package com.nuaa.video.admin.security;
|
||||
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.JwtException;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* JWT令牌提供者
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtTokenProvider {
|
||||
|
||||
@Value("${jwt.secret}")
|
||||
private String secret;
|
||||
|
||||
@Value("${jwt.expiration}")
|
||||
private Long expiration;
|
||||
|
||||
/**
|
||||
* 生成JWT令牌
|
||||
*/
|
||||
public String generateToken(AdminUserDetails userDetails) {
|
||||
Date now = new Date();
|
||||
Date expiryDate = new Date(now.getTime() + expiration * 1000);
|
||||
|
||||
return Jwts.builder()
|
||||
.subject(userDetails.getId().toString())
|
||||
.claim("username", userDetails.getUsername())
|
||||
.claim("authorities", userDetails.getAuthorities().stream()
|
||||
.map(auth -> auth.getAuthority())
|
||||
.toList())
|
||||
.issuedAt(now)
|
||||
.expiration(expiryDate)
|
||||
.signWith(getSignKey())
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JWT令牌
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
Jwts.parser().verifyWith(getSignKey()).build().parseSignedClaims(token);
|
||||
return true;
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
log.error("无效的JWT令牌: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取管理员ID
|
||||
*/
|
||||
public Long getAdminIdFromToken(String token) {
|
||||
String subject = getClaimFromToken(token, Claims::getSubject);
|
||||
return Long.parseLong(subject);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取用户名
|
||||
*/
|
||||
public String getUsernameFromToken(String token) {
|
||||
return getClaimFromToken(token, claims -> claims.get("username", String.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取权限列表
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public List<String> getAuthoritiesFromToken(String token) {
|
||||
try {
|
||||
return getClaimFromToken(token, claims -> claims.get("authorities", List.class));
|
||||
} catch (Exception e) {
|
||||
log.error("获取权限列表失败", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取JWT令牌的签名密钥
|
||||
*/
|
||||
private SecretKey getSignKey() {
|
||||
byte[] keyBytes = secret.getBytes(StandardCharsets.UTF_8);
|
||||
return Keys.hmacShaKeyFor(keyBytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从JWT令牌中获取特定声明
|
||||
*/
|
||||
private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
|
||||
Claims claims = Jwts.parser()
|
||||
.verifyWith(getSignKey())
|
||||
.build()
|
||||
.parseSignedClaims(token)
|
||||
.getPayload();
|
||||
return claimsResolver.apply(claims);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.nuaa.video.admin.security;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
|
||||
/**
|
||||
* 安全配置类
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity
|
||||
@RequiredArgsConstructor
|
||||
public class SecurityConfig {
|
||||
|
||||
private final JwtAuthenticationFilter jwtAuthenticationFilter;
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
|
||||
return authConfig.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.cors(cors -> cors.disable())
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
)
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/admin/auth/login", "/admin/auth/captcha", "/admin/auth/logout", "/actuator/**").permitAll()
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.nuaa.video.admin.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.nuaa.video.admin.dto.AdminUserDTO;
|
||||
import com.nuaa.video.admin.entity.AdminUser;
|
||||
import com.nuaa.video.admin.vo.AdminUserVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
|
||||
/**
|
||||
* 管理员服务接口
|
||||
*/
|
||||
public interface AdminUserService extends IService<AdminUser> {
|
||||
|
||||
/**
|
||||
* 根据用户名查询管理员信息
|
||||
*
|
||||
* @param username 用户名
|
||||
* @return 管理员信息
|
||||
*/
|
||||
AdminUser getByUsername(String username);
|
||||
|
||||
/**
|
||||
* 分页查询管理员列表
|
||||
*
|
||||
* @param page 分页参数
|
||||
* @param keyword 关键字
|
||||
* @return 管理员列表
|
||||
*/
|
||||
Result<Page<AdminUserVO>> listUsers(Page<AdminUser> page, String keyword);
|
||||
|
||||
/**
|
||||
* 获取管理员详情
|
||||
*
|
||||
* @param id 管理员ID
|
||||
* @return 管理员详情
|
||||
*/
|
||||
Result<AdminUserVO> getUserDetail(Long id);
|
||||
|
||||
/**
|
||||
* 添加管理员
|
||||
*
|
||||
* @param adminUserDTO 管理员信息
|
||||
* @param operator 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> addUser(AdminUserDTO adminUserDTO, String operator);
|
||||
|
||||
/**
|
||||
* 更新管理员
|
||||
*
|
||||
* @param adminUserDTO 管理员信息
|
||||
* @param operator 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> updateUser(AdminUserDTO adminUserDTO, String operator);
|
||||
|
||||
/**
|
||||
* 删除管理员
|
||||
*
|
||||
* @param id 管理员ID
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> deleteUser(Long id);
|
||||
|
||||
/**
|
||||
* 更新管理员状态
|
||||
*
|
||||
* @param id 管理员ID
|
||||
* @param status 状态
|
||||
* @param operator 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> updateStatus(Long id, Integer status, String operator);
|
||||
|
||||
/**
|
||||
* 重置密码
|
||||
*
|
||||
* @param id 管理员ID
|
||||
* @param newPassword 新密码
|
||||
* @param operator 操作人
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> resetPassword(Long id, String newPassword, String operator);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.nuaa.video.admin.service;
|
||||
|
||||
import com.nuaa.video.admin.dto.LoginDTO;
|
||||
import com.nuaa.video.admin.security.AdminUserDetails;
|
||||
import com.nuaa.video.admin.vo.CaptchaVO;
|
||||
import com.nuaa.video.admin.vo.LoginVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* 认证服务接口
|
||||
*/
|
||||
public interface AuthService {
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*
|
||||
* @return 验证码数据
|
||||
*/
|
||||
Result<CaptchaVO> generateCaptcha();
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*
|
||||
* @param loginDTO 登录参数
|
||||
* @param request HTTP请求
|
||||
* @return 登录结果
|
||||
*/
|
||||
Result<LoginVO> login(LoginDTO loginDTO, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 登出
|
||||
*
|
||||
* @param token JWT令牌
|
||||
* @return 结果
|
||||
*/
|
||||
Result<Void> logout(String token);
|
||||
|
||||
/**
|
||||
* 获取当前登录用户信息
|
||||
*
|
||||
* @param adminUserDetails 用户详情
|
||||
* @return 用户信息
|
||||
*/
|
||||
Result<LoginVO> getCurrentUser(AdminUserDetails adminUserDetails);
|
||||
}
|
||||
+302
@@ -0,0 +1,302 @@
|
||||
package com.nuaa.video.admin.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.nuaa.video.admin.dto.AdminUserDTO;
|
||||
import com.nuaa.video.admin.entity.AdminUser;
|
||||
import com.nuaa.video.admin.entity.AdminUserRole;
|
||||
import com.nuaa.video.admin.mapper.AdminUserMapper;
|
||||
import com.nuaa.video.admin.mapper.AdminUserRoleMapper;
|
||||
import com.nuaa.video.admin.service.AdminUserService;
|
||||
import com.nuaa.video.admin.vo.AdminRoleVO;
|
||||
import com.nuaa.video.admin.vo.AdminUserVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 管理员服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser> implements AdminUserService {
|
||||
|
||||
private final AdminUserMapper adminUserMapper;
|
||||
private final AdminUserRoleMapper adminUserRoleMapper;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Override
|
||||
public AdminUser getByUsername(String username) {
|
||||
return adminUserMapper.selectByUsername(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Page<AdminUserVO>> listUsers(Page<AdminUser> page, String keyword) {
|
||||
Page<AdminUser> userPage = adminUserMapper.selectUserList(page, keyword);
|
||||
|
||||
// 转换为VO
|
||||
Page<AdminUserVO> voPage = new Page<>();
|
||||
BeanUtils.copyProperties(userPage, voPage, "records");
|
||||
|
||||
List<AdminUserVO> userVOList = new ArrayList<>();
|
||||
for (AdminUser user : userPage.getRecords()) {
|
||||
AdminUserVO vo = new AdminUserVO();
|
||||
BeanUtils.copyProperties(user, vo);
|
||||
|
||||
// 设置角色ID列表
|
||||
if (user.getRoles() != null && !user.getRoles().isEmpty()) {
|
||||
vo.setRoleIds(user.getRoles().stream().map(role -> role.getId()).collect(Collectors.toList()));
|
||||
|
||||
// 设置角色列表
|
||||
List<AdminRoleVO> roleVOList = user.getRoles().stream().map(role -> {
|
||||
AdminRoleVO roleVO = new AdminRoleVO();
|
||||
BeanUtils.copyProperties(role, roleVO);
|
||||
return roleVO;
|
||||
}).collect(Collectors.toList());
|
||||
vo.setRoles(roleVOList);
|
||||
}
|
||||
|
||||
userVOList.add(vo);
|
||||
}
|
||||
|
||||
voPage.setRecords(userVOList);
|
||||
|
||||
return Result.success(voPage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<AdminUserVO> getUserDetail(Long id) {
|
||||
AdminUser user = adminUserMapper.selectUserDetail(id);
|
||||
if (user == null) {
|
||||
return Result.error("管理员不存在");
|
||||
}
|
||||
|
||||
// 转换为VO
|
||||
AdminUserVO vo = new AdminUserVO();
|
||||
BeanUtils.copyProperties(user, vo);
|
||||
|
||||
// 设置角色ID列表
|
||||
if (user.getRoles() != null && !user.getRoles().isEmpty()) {
|
||||
vo.setRoleIds(user.getRoles().stream().map(role -> role.getId()).collect(Collectors.toList()));
|
||||
|
||||
// 设置角色列表
|
||||
List<AdminRoleVO> roleVOList = user.getRoles().stream().map(role -> {
|
||||
AdminRoleVO roleVO = new AdminRoleVO();
|
||||
BeanUtils.copyProperties(role, roleVO);
|
||||
return roleVO;
|
||||
}).collect(Collectors.toList());
|
||||
vo.setRoles(roleVOList);
|
||||
}
|
||||
|
||||
return Result.success(vo);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> addUser(AdminUserDTO adminUserDTO, String operator) {
|
||||
// 检查用户名是否存在
|
||||
AdminUser existUser = getOne(new LambdaQueryWrapper<AdminUser>()
|
||||
.eq(AdminUser::getUsername, adminUserDTO.getUsername()));
|
||||
|
||||
if (existUser != null) {
|
||||
return Result.error("用户名已存在");
|
||||
}
|
||||
|
||||
// 检查邮箱是否存在
|
||||
if (StringUtils.hasText(adminUserDTO.getEmail())) {
|
||||
long count = count(new LambdaQueryWrapper<AdminUser>()
|
||||
.eq(AdminUser::getEmail, adminUserDTO.getEmail()));
|
||||
if (count > 0) {
|
||||
return Result.error("邮箱已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查手机号是否存在
|
||||
if (StringUtils.hasText(adminUserDTO.getPhone())) {
|
||||
long count = count(new LambdaQueryWrapper<AdminUser>()
|
||||
.eq(AdminUser::getPhone, adminUserDTO.getPhone()));
|
||||
if (count > 0) {
|
||||
return Result.error("手机号已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建管理员
|
||||
AdminUser adminUser = new AdminUser();
|
||||
BeanUtils.copyProperties(adminUserDTO, adminUser);
|
||||
|
||||
// 生成盐值
|
||||
String salt = UUID.randomUUID().toString().replace("-", "");
|
||||
adminUser.setSalt(salt);
|
||||
|
||||
// 加密密码
|
||||
if (StringUtils.hasText(adminUserDTO.getPassword())) {
|
||||
adminUser.setPassword(passwordEncoder.encode(adminUserDTO.getPassword()));
|
||||
} else {
|
||||
// 默认密码为123456
|
||||
adminUser.setPassword(passwordEncoder.encode("123456"));
|
||||
}
|
||||
|
||||
// 设置默认状态
|
||||
if (adminUser.getStatus() == null) {
|
||||
adminUser.setStatus(1); // 正常状态
|
||||
}
|
||||
|
||||
adminUser.setCreatedBy(operator);
|
||||
adminUser.setUpdatedBy(operator);
|
||||
adminUser.setCreateTime(LocalDateTime.now());
|
||||
adminUser.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存管理员
|
||||
save(adminUser);
|
||||
|
||||
// 保存角色关联
|
||||
if (adminUserDTO.getRoleIds() != null && !adminUserDTO.getRoleIds().isEmpty()) {
|
||||
adminUserRoleMapper.batchInsert(adminUser.getId(), adminUserDTO.getRoleIds(), operator);
|
||||
}
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> updateUser(AdminUserDTO adminUserDTO, String operator) {
|
||||
// 检查管理员是否存在
|
||||
AdminUser existUser = getById(adminUserDTO.getId());
|
||||
if (existUser == null) {
|
||||
return Result.error("管理员不存在");
|
||||
}
|
||||
|
||||
// 检查用户名是否重复
|
||||
if (!existUser.getUsername().equals(adminUserDTO.getUsername())) {
|
||||
AdminUser userWithSameName = getOne(new LambdaQueryWrapper<AdminUser>()
|
||||
.eq(AdminUser::getUsername, adminUserDTO.getUsername()));
|
||||
if (userWithSameName != null) {
|
||||
return Result.error("用户名已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查邮箱是否重复
|
||||
if (StringUtils.hasText(adminUserDTO.getEmail()) && !adminUserDTO.getEmail().equals(existUser.getEmail())) {
|
||||
long count = count(new LambdaQueryWrapper<AdminUser>()
|
||||
.eq(AdminUser::getEmail, adminUserDTO.getEmail())
|
||||
.ne(AdminUser::getId, adminUserDTO.getId()));
|
||||
if (count > 0) {
|
||||
return Result.error("邮箱已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 检查手机号是否重复
|
||||
if (StringUtils.hasText(adminUserDTO.getPhone()) && !adminUserDTO.getPhone().equals(existUser.getPhone())) {
|
||||
long count = count(new LambdaQueryWrapper<AdminUser>()
|
||||
.eq(AdminUser::getPhone, adminUserDTO.getPhone())
|
||||
.ne(AdminUser::getId, adminUserDTO.getId()));
|
||||
if (count > 0) {
|
||||
return Result.error("手机号已存在");
|
||||
}
|
||||
}
|
||||
|
||||
// 更新管理员信息
|
||||
AdminUser adminUser = new AdminUser();
|
||||
BeanUtils.copyProperties(adminUserDTO, adminUser);
|
||||
|
||||
// 如果密码不为空,则更新密码
|
||||
if (StringUtils.hasText(adminUserDTO.getPassword())) {
|
||||
adminUser.setPassword(passwordEncoder.encode(adminUserDTO.getPassword()));
|
||||
} else {
|
||||
// 不更新密码
|
||||
adminUser.setPassword(null);
|
||||
}
|
||||
|
||||
adminUser.setUpdatedBy(operator);
|
||||
adminUser.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
// 保存管理员
|
||||
updateById(adminUser);
|
||||
|
||||
// 更新角色关联
|
||||
if (adminUserDTO.getRoleIds() != null) {
|
||||
// 删除原有角色关联
|
||||
adminUserRoleMapper.delete(new LambdaQueryWrapper<AdminUserRole>()
|
||||
.eq(AdminUserRole::getAdminId, adminUser.getId()));
|
||||
|
||||
// 添加新的角色关联
|
||||
if (!adminUserDTO.getRoleIds().isEmpty()) {
|
||||
adminUserRoleMapper.batchInsert(adminUser.getId(), adminUserDTO.getRoleIds(), operator);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Result<Void> deleteUser(Long id) {
|
||||
// 检查管理员是否存在
|
||||
AdminUser existUser = getById(id);
|
||||
if (existUser == null) {
|
||||
return Result.error("管理员不存在");
|
||||
}
|
||||
|
||||
// 删除管理员
|
||||
removeById(id);
|
||||
|
||||
// 删除角色关联
|
||||
adminUserRoleMapper.delete(new LambdaQueryWrapper<AdminUserRole>()
|
||||
.eq(AdminUserRole::getAdminId, id));
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> updateStatus(Long id, Integer status, String operator) {
|
||||
// 检查管理员是否存在
|
||||
AdminUser existUser = getById(id);
|
||||
if (existUser == null) {
|
||||
return Result.error("管理员不存在");
|
||||
}
|
||||
|
||||
// 更新状态
|
||||
AdminUser adminUser = new AdminUser();
|
||||
adminUser.setId(id);
|
||||
adminUser.setStatus(status);
|
||||
adminUser.setUpdatedBy(operator);
|
||||
adminUser.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
updateById(adminUser);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> resetPassword(Long id, String newPassword, String operator) {
|
||||
// 检查管理员是否存在
|
||||
AdminUser existUser = getById(id);
|
||||
if (existUser == null) {
|
||||
return Result.error("管理员不存在");
|
||||
}
|
||||
|
||||
// 更新密码
|
||||
AdminUser adminUser = new AdminUser();
|
||||
adminUser.setId(id);
|
||||
adminUser.setPassword(passwordEncoder.encode(newPassword));
|
||||
adminUser.setUpdatedBy(operator);
|
||||
adminUser.setUpdateTime(LocalDateTime.now());
|
||||
|
||||
updateById(adminUser);
|
||||
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,278 @@
|
||||
package com.nuaa.video.admin.service.impl;
|
||||
|
||||
import cn.hutool.captcha.CaptchaUtil;
|
||||
import cn.hutool.captcha.LineCaptcha;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import com.nuaa.video.admin.dto.LoginDTO;
|
||||
import com.nuaa.video.admin.entity.AdminPermission;
|
||||
import com.nuaa.video.admin.entity.AdminRole;
|
||||
import com.nuaa.video.admin.entity.AdminUser;
|
||||
import com.nuaa.video.admin.security.AdminUserDetails;
|
||||
import com.nuaa.video.admin.security.JwtTokenProvider;
|
||||
import com.nuaa.video.admin.service.AdminUserService;
|
||||
import com.nuaa.video.admin.service.AuthService;
|
||||
import com.nuaa.video.admin.vo.CaptchaVO;
|
||||
import com.nuaa.video.admin.vo.LoginVO;
|
||||
import com.nuaa.video.common.model.vo.Result;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 认证服务实现类
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
public class AuthServiceImpl implements AuthService {
|
||||
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final JwtTokenProvider jwtTokenProvider;
|
||||
private final AdminUserService adminUserService;
|
||||
private final RedisTemplate<String, Object> redisTemplate;
|
||||
|
||||
@Value("${login.retry-count:5}")
|
||||
private int maxRetryCount;
|
||||
|
||||
@Value("${login.lock-time:30}")
|
||||
private int lockTime;
|
||||
|
||||
private static final String CAPTCHA_KEY_PREFIX = "admin:captcha:";
|
||||
private static final String TOKEN_KEY_PREFIX = "admin:token:";
|
||||
private static final String LOGIN_RETRY_KEY_PREFIX = "admin:login:retry:";
|
||||
private static final String LOGIN_LOCK_KEY_PREFIX = "admin:login:lock:";
|
||||
|
||||
@Override
|
||||
public Result<CaptchaVO> generateCaptcha() {
|
||||
// 生成验证码
|
||||
LineCaptcha captcha = CaptchaUtil.createLineCaptcha(120, 40, 4, 100);
|
||||
String code = captcha.getCode();
|
||||
String imageBase64 = captcha.getImageBase64();
|
||||
|
||||
// 生成验证码key
|
||||
String captchaKey = IdUtil.fastSimpleUUID();
|
||||
|
||||
// 将验证码存入Redis,有效期5分钟
|
||||
redisTemplate.opsForValue().set(CAPTCHA_KEY_PREFIX + captchaKey, code, 5, TimeUnit.MINUTES);
|
||||
|
||||
// 构建返回数据
|
||||
CaptchaVO captchaVO = new CaptchaVO();
|
||||
captchaVO.setCaptchaKey(captchaKey);
|
||||
captchaVO.setCaptchaImage("data:image/png;base64," + imageBase64);
|
||||
|
||||
return Result.success(captchaVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<LoginVO> login(LoginDTO loginDTO, HttpServletRequest request) {
|
||||
String username = loginDTO.getUsername();
|
||||
String password = loginDTO.getPassword();
|
||||
|
||||
// 检查账户是否被锁定
|
||||
Boolean isLocked = (Boolean) redisTemplate.opsForValue().get(LOGIN_LOCK_KEY_PREFIX + username);
|
||||
if (Boolean.TRUE.equals(isLocked)) {
|
||||
return Result.error("账户已被锁定,请稍后再试");
|
||||
}
|
||||
|
||||
// 验证验证码
|
||||
if (StringUtils.hasText(loginDTO.getCaptchaKey()) && StringUtils.hasText(loginDTO.getCaptcha())) {
|
||||
String captchaKey = CAPTCHA_KEY_PREFIX + loginDTO.getCaptchaKey();
|
||||
String captchaValue = (String) redisTemplate.opsForValue().get(captchaKey);
|
||||
|
||||
if (!StringUtils.hasText(captchaValue) || !captchaValue.equalsIgnoreCase(loginDTO.getCaptcha())) {
|
||||
return Result.error("验证码错误");
|
||||
}
|
||||
|
||||
// 验证通过后删除验证码
|
||||
redisTemplate.delete(captchaKey);
|
||||
}
|
||||
|
||||
try {
|
||||
// 进行认证
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(username, password));
|
||||
|
||||
// 认证成功,清除登录失败记录
|
||||
redisTemplate.delete(LOGIN_RETRY_KEY_PREFIX + username);
|
||||
|
||||
// 获取用户详情
|
||||
AdminUser adminUser = adminUserService.getByUsername(username);
|
||||
|
||||
// 设置最后登录时间和IP
|
||||
AdminUser updateUser = new AdminUser();
|
||||
updateUser.setId(adminUser.getId());
|
||||
updateUser.setLastLoginTime(LocalDateTime.now());
|
||||
updateUser.setLastLoginIp(getIpAddress(request));
|
||||
adminUserService.updateById(updateUser);
|
||||
|
||||
// 构建用户详情
|
||||
AdminUserDetails userDetails = buildUserDetails(adminUser);
|
||||
|
||||
// 生成token
|
||||
String token = jwtTokenProvider.generateToken(userDetails);
|
||||
|
||||
// 将token存入Redis,有效期等同于token过期时间
|
||||
redisTemplate.opsForValue().set(TOKEN_KEY_PREFIX + token, token, 24, TimeUnit.HOURS);
|
||||
|
||||
// 构建登录返回数据
|
||||
LoginVO loginVO = buildLoginVO(adminUser, token);
|
||||
|
||||
return Result.success(loginVO);
|
||||
|
||||
} catch (BadCredentialsException e) {
|
||||
// 认证失败,记录登录失败次数
|
||||
Integer retryCount = (Integer) redisTemplate.opsForValue().get(LOGIN_RETRY_KEY_PREFIX + username);
|
||||
if (retryCount == null) {
|
||||
retryCount = 0;
|
||||
}
|
||||
|
||||
retryCount++;
|
||||
|
||||
// 如果超过最大重试次数,锁定账户
|
||||
if (retryCount >= maxRetryCount) {
|
||||
redisTemplate.opsForValue().set(LOGIN_LOCK_KEY_PREFIX + username, true, lockTime, TimeUnit.MINUTES);
|
||||
return Result.error("账户已被锁定,请" + lockTime + "分钟后再试");
|
||||
}
|
||||
|
||||
// 保存重试次数
|
||||
redisTemplate.opsForValue().set(LOGIN_RETRY_KEY_PREFIX + username, retryCount, 1, TimeUnit.HOURS);
|
||||
|
||||
return Result.error("用户名或密码错误,还剩" + (maxRetryCount - retryCount) + "次机会");
|
||||
} catch (Exception e) {
|
||||
log.error("登录异常", e);
|
||||
return Result.error("登录失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<Void> logout(String token) {
|
||||
if (StringUtils.hasText(token)) {
|
||||
// 从Redis中删除token
|
||||
redisTemplate.delete(TOKEN_KEY_PREFIX + token);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result<LoginVO> getCurrentUser(AdminUserDetails adminUserDetails) {
|
||||
// 获取用户信息
|
||||
AdminUser adminUser = adminUserService.getById(adminUserDetails.getId());
|
||||
if (adminUser == null) {
|
||||
return Result.error("用户不存在");
|
||||
}
|
||||
|
||||
// 构建登录返回数据
|
||||
LoginVO loginVO = buildLoginVO(adminUser, null);
|
||||
|
||||
return Result.success(loginVO);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建用户详情
|
||||
*/
|
||||
private AdminUserDetails buildUserDetails(AdminUser adminUser) {
|
||||
AdminUserDetails userDetails = new AdminUserDetails();
|
||||
userDetails.setId(adminUser.getId());
|
||||
userDetails.setUsername(adminUser.getUsername());
|
||||
userDetails.setPassword(adminUser.getPassword());
|
||||
|
||||
// 设置权限
|
||||
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
|
||||
|
||||
// 添加角色权限
|
||||
if (adminUser.getRoles() != null && !adminUser.getRoles().isEmpty()) {
|
||||
for (AdminRole role : adminUser.getRoles()) {
|
||||
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
|
||||
|
||||
// 添加权限
|
||||
if (role.getPermissions() != null && !role.getPermissions().isEmpty()) {
|
||||
for (AdminPermission permission : role.getPermissions()) {
|
||||
authorities.add(new SimpleGrantedAuthority(permission.getCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userDetails.setAuthorities(authorities);
|
||||
|
||||
return userDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建登录返回数据
|
||||
*/
|
||||
private LoginVO buildLoginVO(AdminUser adminUser, String token) {
|
||||
LoginVO loginVO = new LoginVO();
|
||||
loginVO.setToken(token);
|
||||
loginVO.setTokenType("Bearer");
|
||||
loginVO.setExpiresIn(24 * 60 * 60L); // 24小时
|
||||
loginVO.setAdminId(adminUser.getId());
|
||||
loginVO.setUsername(adminUser.getUsername());
|
||||
loginVO.setRealName(adminUser.getRealName());
|
||||
loginVO.setAvatar(adminUser.getAvatar());
|
||||
|
||||
// 设置角色和权限
|
||||
List<String> roles = new ArrayList<>();
|
||||
List<String> permissions = new ArrayList<>();
|
||||
|
||||
if (adminUser.getRoles() != null && !adminUser.getRoles().isEmpty()) {
|
||||
// 设置角色
|
||||
roles = adminUser.getRoles().stream()
|
||||
.map(AdminRole::getCode)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 设置权限
|
||||
for (AdminRole role : adminUser.getRoles()) {
|
||||
if (role.getPermissions() != null && !role.getPermissions().isEmpty()) {
|
||||
List<String> rolePermissions = role.getPermissions().stream()
|
||||
.map(AdminPermission::getCode)
|
||||
.collect(Collectors.toList());
|
||||
permissions.addAll(rolePermissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loginVO.setRoles(roles);
|
||||
loginVO.setPermissions(permissions);
|
||||
|
||||
return loginVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求的IP地址
|
||||
*/
|
||||
private String getIpAddress(HttpServletRequest request) {
|
||||
String ip = request.getHeader("X-Forwarded-For");
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("Proxy-Client-IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getHeader("WL-Proxy-Client-IP");
|
||||
}
|
||||
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
|
||||
ip = request.getRemoteAddr();
|
||||
}
|
||||
|
||||
// 多个代理的情况,第一个IP为客户端真实IP
|
||||
if (ip != null && ip.indexOf(",") > 0) {
|
||||
ip = ip.substring(0, ip.indexOf(","));
|
||||
}
|
||||
|
||||
return ip;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.nuaa.video.admin.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 权限VO
|
||||
*/
|
||||
@Data
|
||||
public class AdminPermissionVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 权限ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 权限名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 权限编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 权限描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.nuaa.video.admin.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 角色VO
|
||||
*/
|
||||
@Data
|
||||
public class AdminRoleVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 角色ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 角色名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 角色编码
|
||||
*/
|
||||
private String code;
|
||||
|
||||
/**
|
||||
* 角色描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 权限ID集合
|
||||
*/
|
||||
private List<Long> permissionIds;
|
||||
|
||||
/**
|
||||
* 权限列表
|
||||
*/
|
||||
private List<AdminPermissionVO> permissions;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.nuaa.video.admin.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 管理员VO
|
||||
*/
|
||||
@Data
|
||||
public class AdminUserVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 状态(0:禁用,1:正常)
|
||||
*/
|
||||
private Integer status;
|
||||
|
||||
/**
|
||||
* 最后登录时间
|
||||
*/
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
/**
|
||||
* 最后登录IP
|
||||
*/
|
||||
private String lastLoginIp;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 角色ID集合
|
||||
*/
|
||||
private List<Long> roleIds;
|
||||
|
||||
/**
|
||||
* 角色列表
|
||||
*/
|
||||
private List<AdminRoleVO> roles;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.nuaa.video.admin.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 验证码VO
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CaptchaVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 验证码Key
|
||||
*/
|
||||
private String captchaKey;
|
||||
|
||||
/**
|
||||
* 验证码图片(Base64编码)
|
||||
*/
|
||||
private String captchaImage;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.nuaa.video.admin.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 登录结果VO
|
||||
*/
|
||||
@Data
|
||||
public class LoginVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 访问令牌
|
||||
*/
|
||||
private String token;
|
||||
|
||||
/**
|
||||
* 令牌类型
|
||||
*/
|
||||
private String tokenType;
|
||||
|
||||
/**
|
||||
* 过期时间(秒)
|
||||
*/
|
||||
private Long expiresIn;
|
||||
|
||||
/**
|
||||
* 管理员ID
|
||||
*/
|
||||
private Long adminId;
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 真实姓名
|
||||
*/
|
||||
private String realName;
|
||||
|
||||
/**
|
||||
* 头像URL
|
||||
*/
|
||||
private String avatar;
|
||||
|
||||
/**
|
||||
* 角色编码列表
|
||||
*/
|
||||
private List<String> roles;
|
||||
|
||||
/**
|
||||
* 权限编码列表
|
||||
*/
|
||||
private List<String> permissions;
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package com.nuaa.video.admin.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 系统配置VO
|
||||
*/
|
||||
@Data
|
||||
public class SystemConfigVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* 配置ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 配置键
|
||||
*/
|
||||
private String configKey;
|
||||
|
||||
/**
|
||||
* 配置值
|
||||
*/
|
||||
private String configValue;
|
||||
|
||||
/**
|
||||
* 配置描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 创建时间
|
||||
*/
|
||||
private LocalDateTime createTime;
|
||||
|
||||
/**
|
||||
* 更新时间
|
||||
*/
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
spring:
|
||||
datasource:
|
||||
dynamic:
|
||||
primary: master
|
||||
strict: false
|
||||
datasource:
|
||||
master:
|
||||
url: jdbc:mysql://5.180.253.115:3306/video_admin?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: root
|
||||
password: sty@G458221
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
|
||||
data:
|
||||
redis:
|
||||
database: 5
|
||||
host: 5.180.253.115
|
||||
port: 6379
|
||||
password: redis_EsJ8FX
|
||||
timeout: 10000
|
||||
|
||||
# 开发环境启用操作日志记录
|
||||
operation-log:
|
||||
enabled: true
|
||||
|
||||
# 开发环境日志配置
|
||||
logging:
|
||||
level:
|
||||
com.nuaa.video: debug
|
||||
org.springframework.security: debug
|
||||
@@ -4,30 +4,111 @@ server:
|
||||
spring:
|
||||
application:
|
||||
name: video-admin
|
||||
profiles:
|
||||
active: dev
|
||||
config:
|
||||
import:
|
||||
- optional:nacos:${spring.application.name}.yaml
|
||||
cloud:
|
||||
nacos:
|
||||
discovery:
|
||||
server-addr: 5.180.253.115:8848
|
||||
namespace: public
|
||||
group: DEFAULT_GROUP
|
||||
server-addr: ${NACOS_HOST:5.180.253.115}:${NACOS_PORT:8848}
|
||||
namespace: ${NACOS_NAMESPACE:public}
|
||||
group: ${NACOS_GROUP:DEFAULT_GROUP}
|
||||
config:
|
||||
server-addr: ${spring.cloud.nacos.discovery.server-addr}
|
||||
namespace: ${spring.cloud.nacos.discovery.namespace}
|
||||
group: ${spring.cloud.nacos.discovery.group}
|
||||
file-extension: yaml
|
||||
sentinel:
|
||||
transport:
|
||||
dashboard: 5.180.253.115:8080
|
||||
dashboard: ${SENTINEL_DASHBOARD_HOST:5.180.253.115}:${SENTINEL_DASHBOARD_PORT:8080}
|
||||
port: 8725
|
||||
eager: true
|
||||
|
||||
datasource:
|
||||
url: jdbc:mysql://5.180.253.115:3306/video_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
|
||||
username: root
|
||||
password: sty@G458221
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://${MYSQL_HOST:5.180.253.115}:${MYSQL_PORT:3306}/video_admin?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
username: ${MYSQL_USERNAME:root}
|
||||
password: ${MYSQL_PASSWORD:sty@G458221}
|
||||
hikari:
|
||||
minimum-idle: 5
|
||||
maximum-pool-size: 20
|
||||
idle-timeout: 30000
|
||||
max-lifetime: 1200000
|
||||
connection-timeout: 30000
|
||||
connection-test-query: SELECT 1
|
||||
|
||||
data:
|
||||
redis:
|
||||
host: 5.180.253.115
|
||||
port: 6379
|
||||
password: redis_EsJ8FX
|
||||
host: ${REDIS_HOST:5.180.253.115}
|
||||
port: ${REDIS_PORT:6379}
|
||||
password: ${REDIS_PASSWORD:redis_EsJ8FX}
|
||||
database: 5
|
||||
timeout: 10000
|
||||
lettuce:
|
||||
pool:
|
||||
max-active: 200
|
||||
max-wait: -1
|
||||
max-idle: 10
|
||||
min-idle: 0
|
||||
|
||||
mybatis-plus:
|
||||
mapper-locations: classpath:mapper/*.xml
|
||||
type-aliases-package: com.nuaa.video.admin.entity
|
||||
configuration:
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
map-underscore-to-camel-case: true
|
||||
cache-enabled: false
|
||||
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
global-config:
|
||||
db-config:
|
||||
id-type: auto
|
||||
logic-delete-field: status
|
||||
logic-delete-value: 0
|
||||
logic-not-delete-value: 1
|
||||
|
||||
# JWT配置
|
||||
jwt:
|
||||
secret: video-platform-admin-secret-key-2024-nuaa
|
||||
expiration: 86400 # 24小时
|
||||
token-header: Authorization
|
||||
token-prefix: "Bearer "
|
||||
|
||||
# 登录配置
|
||||
login:
|
||||
# 最大重试次数
|
||||
retry-count: 5
|
||||
# 锁定时间(分钟)
|
||||
lock-time: 30
|
||||
|
||||
# 操作日志配置
|
||||
operation-log:
|
||||
enabled: true
|
||||
exclude-paths:
|
||||
- /admin/auth/login
|
||||
- /admin/auth/logout
|
||||
- /admin/auth/captcha
|
||||
- /actuator/**
|
||||
|
||||
management:
|
||||
endpoints:
|
||||
web:
|
||||
exposure:
|
||||
include: '*'
|
||||
endpoint:
|
||||
health:
|
||||
show-details: always
|
||||
|
||||
feign:
|
||||
client:
|
||||
config:
|
||||
default:
|
||||
connectTimeout: 5000
|
||||
readTimeout: 5000
|
||||
circuitbreaker:
|
||||
enabled: true
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.nuaa.video: info
|
||||
org.springframework.security: info
|
||||
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminOperationLogMapper">
|
||||
|
||||
<!-- 分页查询操作日志 -->
|
||||
<select id="selectLogList" resultType="com.nuaa.video.admin.entity.AdminOperationLog">
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
admin_operation_log
|
||||
<where>
|
||||
<if test="username != null and username != ''">
|
||||
AND username LIKE CONCAT('%', #{username}, '%')
|
||||
</if>
|
||||
<if test="operation != null and operation != ''">
|
||||
AND operation LIKE CONCAT('%', #{operation}, '%')
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND status = #{status}
|
||||
</if>
|
||||
<if test="startTime != null and startTime != ''">
|
||||
AND operation_time >= #{startTime}
|
||||
</if>
|
||||
<if test="endTime != null and endTime != ''">
|
||||
AND operation_time <= #{endTime}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY operation_time DESC
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminPermissionMapper">
|
||||
|
||||
<!-- 获取角色的权限列表 -->
|
||||
<select id="selectPermissionsByRoleId" resultType="com.nuaa.video.admin.entity.AdminPermission">
|
||||
SELECT
|
||||
p.*
|
||||
FROM
|
||||
admin_permission p
|
||||
INNER JOIN
|
||||
admin_role_permission rp ON p.id = rp.permission_id
|
||||
WHERE
|
||||
rp.role_id = #{roleId}
|
||||
</select>
|
||||
|
||||
<!-- 获取管理员的权限列表 -->
|
||||
<select id="selectPermissionsByAdminId" resultType="com.nuaa.video.admin.entity.AdminPermission">
|
||||
SELECT DISTINCT
|
||||
p.*
|
||||
FROM
|
||||
admin_permission p
|
||||
INNER JOIN
|
||||
admin_role_permission rp ON p.id = rp.permission_id
|
||||
INNER JOIN
|
||||
admin_role r ON rp.role_id = r.id
|
||||
INNER JOIN
|
||||
admin_user_role ur ON r.id = ur.role_id
|
||||
WHERE
|
||||
ur.admin_id = #{adminId}
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminRoleMapper">
|
||||
|
||||
<!-- 分页查询角色列表 -->
|
||||
<select id="selectRoleList" resultMap="roleMap">
|
||||
SELECT
|
||||
r.*,
|
||||
p.id AS perm_id,
|
||||
p.name AS perm_name,
|
||||
p.code AS perm_code,
|
||||
p.description AS perm_description
|
||||
FROM
|
||||
admin_role r
|
||||
LEFT JOIN
|
||||
admin_role_permission rp ON r.id = rp.role_id
|
||||
LEFT JOIN
|
||||
admin_permission p ON rp.permission_id = p.id
|
||||
<where>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND (r.name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR r.code LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR r.description LIKE CONCAT('%', #{keyword}, '%'))
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY r.id ASC
|
||||
</select>
|
||||
|
||||
<!-- 获取角色详情 -->
|
||||
<select id="selectRoleDetail" resultMap="roleMap">
|
||||
SELECT
|
||||
r.*,
|
||||
p.id AS perm_id,
|
||||
p.name AS perm_name,
|
||||
p.code AS perm_code,
|
||||
p.description AS perm_description
|
||||
FROM
|
||||
admin_role r
|
||||
LEFT JOIN
|
||||
admin_role_permission rp ON r.id = rp.role_id
|
||||
LEFT JOIN
|
||||
admin_permission p ON rp.permission_id = p.id
|
||||
WHERE
|
||||
r.id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 获取管理员的角色列表 -->
|
||||
<select id="selectRolesByAdminId" resultType="com.nuaa.video.admin.entity.AdminRole">
|
||||
SELECT
|
||||
r.*
|
||||
FROM
|
||||
admin_role r
|
||||
INNER JOIN
|
||||
admin_user_role ur ON r.id = ur.role_id
|
||||
WHERE
|
||||
ur.admin_id = #{adminId}
|
||||
</select>
|
||||
|
||||
<!-- 结果映射 -->
|
||||
<resultMap id="roleMap" type="com.nuaa.video.admin.entity.AdminRole">
|
||||
<id property="id" column="id"/>
|
||||
<result property="name" column="name"/>
|
||||
<result property="code" column="code"/>
|
||||
<result property="description" column="description"/>
|
||||
<result property="createdBy" column="created_by"/>
|
||||
<result property="updatedBy" column="updated_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
|
||||
<!-- 权限集合 -->
|
||||
<collection property="permissions" ofType="com.nuaa.video.admin.entity.AdminPermission">
|
||||
<id property="id" column="perm_id"/>
|
||||
<result property="name" column="perm_name"/>
|
||||
<result property="code" column="perm_code"/>
|
||||
<result property="description" column="perm_description"/>
|
||||
</collection>
|
||||
</resultMap>
|
||||
</mapper>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminRolePermissionMapper">
|
||||
|
||||
<!-- 批量插入角色权限关联 -->
|
||||
<insert id="batchInsert">
|
||||
INSERT INTO admin_role_permission (role_id, permission_id, created_by, create_time)
|
||||
VALUES
|
||||
<foreach collection="permissionIds" item="permissionId" separator=",">
|
||||
(#{roleId}, #{permissionId}, #{createdBy}, NOW())
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminSystemConfigMapper">
|
||||
|
||||
<!-- 根据配置键获取配置值 -->
|
||||
<select id="getConfigValue" resultType="java.lang.String">
|
||||
SELECT
|
||||
config_value
|
||||
FROM
|
||||
admin_system_config
|
||||
WHERE
|
||||
config_key = #{configKey}
|
||||
LIMIT 1
|
||||
</select>
|
||||
</mapper>
|
||||
@@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminUserMapper">
|
||||
|
||||
<!-- 根据用户名查询管理员信息 -->
|
||||
<select id="selectByUsername" resultMap="adminUserMap">
|
||||
SELECT
|
||||
au.*,
|
||||
ar.id AS role_id,
|
||||
ar.name AS role_name,
|
||||
ar.code AS role_code,
|
||||
ar.description AS role_description,
|
||||
ap.id AS perm_id,
|
||||
ap.name AS perm_name,
|
||||
ap.code AS perm_code,
|
||||
ap.description AS perm_description
|
||||
FROM
|
||||
admin_user au
|
||||
LEFT JOIN
|
||||
admin_user_role aur ON au.id = aur.admin_id
|
||||
LEFT JOIN
|
||||
admin_role ar ON aur.role_id = ar.id
|
||||
LEFT JOIN
|
||||
admin_role_permission arp ON ar.id = arp.role_id
|
||||
LEFT JOIN
|
||||
admin_permission ap ON arp.permission_id = ap.id
|
||||
WHERE
|
||||
au.username = #{username}
|
||||
</select>
|
||||
|
||||
<!-- 分页查询管理员列表 -->
|
||||
<select id="selectUserList" resultMap="adminUserMap">
|
||||
SELECT
|
||||
au.*,
|
||||
ar.id AS role_id,
|
||||
ar.name AS role_name,
|
||||
ar.code AS role_code
|
||||
FROM
|
||||
admin_user au
|
||||
LEFT JOIN
|
||||
admin_user_role aur ON au.id = aur.admin_id
|
||||
LEFT JOIN
|
||||
admin_role ar ON aur.role_id = ar.id
|
||||
<where>
|
||||
<if test="keyword != null and keyword != ''">
|
||||
AND (au.username LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR au.real_name LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR au.email LIKE CONCAT('%', #{keyword}, '%')
|
||||
OR au.phone LIKE CONCAT('%', #{keyword}, '%'))
|
||||
</if>
|
||||
</where>
|
||||
GROUP BY au.id
|
||||
ORDER BY au.id ASC
|
||||
</select>
|
||||
|
||||
<!-- 获取管理员详情 -->
|
||||
<select id="selectUserDetail" resultMap="adminUserMap">
|
||||
SELECT
|
||||
au.*,
|
||||
ar.id AS role_id,
|
||||
ar.name AS role_name,
|
||||
ar.code AS role_code,
|
||||
ar.description AS role_description,
|
||||
ap.id AS perm_id,
|
||||
ap.name AS perm_name,
|
||||
ap.code AS perm_code,
|
||||
ap.description AS perm_description
|
||||
FROM
|
||||
admin_user au
|
||||
LEFT JOIN
|
||||
admin_user_role aur ON au.id = aur.admin_id
|
||||
LEFT JOIN
|
||||
admin_role ar ON aur.role_id = ar.id
|
||||
LEFT JOIN
|
||||
admin_role_permission arp ON ar.id = arp.role_id
|
||||
LEFT JOIN
|
||||
admin_permission ap ON arp.permission_id = ap.id
|
||||
WHERE
|
||||
au.id = #{id}
|
||||
</select>
|
||||
|
||||
<!-- 结果映射 -->
|
||||
<resultMap id="adminUserMap" type="com.nuaa.video.admin.entity.AdminUser">
|
||||
<id property="id" column="id"/>
|
||||
<result property="username" column="username"/>
|
||||
<result property="password" column="password"/>
|
||||
<result property="salt" column="salt"/>
|
||||
<result property="realName" column="real_name"/>
|
||||
<result property="avatar" column="avatar"/>
|
||||
<result property="email" column="email"/>
|
||||
<result property="phone" column="phone"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="lastLoginTime" column="last_login_time"/>
|
||||
<result property="lastLoginIp" column="last_login_ip"/>
|
||||
<result property="createdBy" column="created_by"/>
|
||||
<result property="updatedBy" column="updated_by"/>
|
||||
<result property="createTime" column="create_time"/>
|
||||
<result property="updateTime" column="update_time"/>
|
||||
|
||||
<!-- 角色集合 -->
|
||||
<collection property="roles" ofType="com.nuaa.video.admin.entity.AdminRole">
|
||||
<id property="id" column="role_id"/>
|
||||
<result property="name" column="role_name"/>
|
||||
<result property="code" column="role_code"/>
|
||||
<result property="description" column="role_description"/>
|
||||
|
||||
<!-- 权限集合 -->
|
||||
<collection property="permissions" ofType="com.nuaa.video.admin.entity.AdminPermission">
|
||||
<id property="id" column="perm_id"/>
|
||||
<result property="name" column="perm_name"/>
|
||||
<result property="code" column="perm_code"/>
|
||||
<result property="description" column="perm_description"/>
|
||||
</collection>
|
||||
</collection>
|
||||
</resultMap>
|
||||
</mapper>
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="com.nuaa.video.admin.mapper.AdminUserRoleMapper">
|
||||
|
||||
<!-- 批量插入用户角色关联 -->
|
||||
<insert id="batchInsert">
|
||||
INSERT INTO admin_user_role (admin_id, role_id, created_by, create_time)
|
||||
VALUES
|
||||
<foreach collection="roleIds" item="roleId" separator=",">
|
||||
(#{adminId}, #{roleId}, #{createdBy}, NOW())
|
||||
</foreach>
|
||||
</insert>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user