feat(admin): Implement admin module foundation
This commit is contained in:
@@ -12,10 +12,141 @@
|
|||||||
<artifactId>video-admin</artifactId>
|
<artifactId>video-admin</artifactId>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- 引入公共模块 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.nuaa.video</groupId>
|
<groupId>com.nuaa.video</groupId>
|
||||||
<artifactId>video-common</artifactId>
|
<artifactId>video-common</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</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>
|
</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>
|
</project>
|
||||||
@@ -1,11 +1,21 @@
|
|||||||
package com.nuaa.video.admin;
|
package com.nuaa.video.admin;
|
||||||
|
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
|
||||||
|
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理后台启动类
|
||||||
|
*/
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableDiscoveryClient
|
@EnableDiscoveryClient
|
||||||
|
@EnableFeignClients
|
||||||
|
@MapperScan("com.nuaa.video.admin.map" +
|
||||||
|
"" +
|
||||||
|
"" +
|
||||||
|
"per")
|
||||||
public class AdminApplication {
|
public class AdminApplication {
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(AdminApplication.class, 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:
|
spring:
|
||||||
application:
|
application:
|
||||||
name: video-admin
|
name: video-admin
|
||||||
|
profiles:
|
||||||
|
active: dev
|
||||||
|
config:
|
||||||
|
import:
|
||||||
|
- optional:nacos:${spring.application.name}.yaml
|
||||||
cloud:
|
cloud:
|
||||||
nacos:
|
nacos:
|
||||||
discovery:
|
discovery:
|
||||||
server-addr: 5.180.253.115:8848
|
server-addr: ${NACOS_HOST:5.180.253.115}:${NACOS_PORT:8848}
|
||||||
namespace: public
|
namespace: ${NACOS_NAMESPACE:public}
|
||||||
group: DEFAULT_GROUP
|
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:
|
sentinel:
|
||||||
transport:
|
transport:
|
||||||
dashboard: 5.180.253.115:8080
|
dashboard: ${SENTINEL_DASHBOARD_HOST:5.180.253.115}:${SENTINEL_DASHBOARD_PORT:8080}
|
||||||
port: 8725
|
port: 8725
|
||||||
eager: true
|
eager: true
|
||||||
|
|
||||||
datasource:
|
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
|
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:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: 5.180.253.115
|
host: ${REDIS_HOST:5.180.253.115}
|
||||||
port: 6379
|
port: ${REDIS_PORT:6379}
|
||||||
password: redis_EsJ8FX
|
password: ${REDIS_PASSWORD:redis_EsJ8FX}
|
||||||
database: 5
|
database: 5
|
||||||
|
timeout: 10000
|
||||||
|
lettuce:
|
||||||
|
pool:
|
||||||
|
max-active: 200
|
||||||
|
max-wait: -1
|
||||||
|
max-idle: 10
|
||||||
|
min-idle: 0
|
||||||
|
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
mapper-locations: classpath:mapper/*.xml
|
mapper-locations: classpath:mapper/*.xml
|
||||||
|
type-aliases-package: com.nuaa.video.admin.entity
|
||||||
configuration:
|
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>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.nuaa.video.auth.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.auth.entity.AuthUser;
|
||||||
|
import com.nuaa.video.auth.service.AuthUserService;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 认证管理控制器 - 供管理后台调用
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/auth/admin")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminAuthController {
|
||||||
|
|
||||||
|
private final AuthUserService authUserService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录统计 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/login/stats")
|
||||||
|
public Result<Object> getLoginStats() {
|
||||||
|
return authUserService.getLoginStats();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员重置用户密码 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/reset-password/{userId}")
|
||||||
|
public Result<Void> resetPassword(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@RequestParam String newPassword) {
|
||||||
|
return authUserService.adminResetPassword(userId, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定/解锁用户账号 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/lock/{userId}/{locked}")
|
||||||
|
public Result<Void> lockUser(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@PathVariable Boolean locked) {
|
||||||
|
return authUserService.lockUser(userId, locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,4 +59,19 @@ public interface AuthUserService extends IService<AuthUser> {
|
|||||||
* @return 用户信息
|
* @return 用户信息
|
||||||
*/
|
*/
|
||||||
Result<AuthUser> getCurrentUser(Long userId);
|
Result<AuthUser> getCurrentUser(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取登录统计 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Object> getLoginStats();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员重置用户密码 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Void> adminResetPassword(Long userId, String newPassword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定/解锁用户账号 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Void> lockUser(Long userId, Boolean locked);
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.nuaa.video.auth.service.impl;
|
package com.nuaa.video.auth.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
@@ -24,10 +25,14 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.transaction.support.TransactionSynchronization;
|
import org.springframework.transaction.support.TransactionSynchronization;
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
|
import org.springframework.util.DigestUtils;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户认证服务实现类
|
* 用户认证服务实现类
|
||||||
@@ -283,6 +288,109 @@ public class AuthUserServiceImpl extends ServiceImpl<AuthUserMapper, AuthUser> i
|
|||||||
return Result.success(user);
|
return Result.success(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Object> getLoginStats() {
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
|
||||||
|
// 这里是模拟数据,实际项目中需要从数据库或缓存中获取
|
||||||
|
|
||||||
|
// 总用户数
|
||||||
|
long totalUsers = count();
|
||||||
|
stats.put("totalUsers", totalUsers);
|
||||||
|
|
||||||
|
// 今日登录用户数
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
long todayLoginUsers = 0; // 实际需要查询登录记录表
|
||||||
|
stats.put("todayLoginUsers", todayLoginUsers);
|
||||||
|
|
||||||
|
// 当前在线用户数(可从Redis中获取)
|
||||||
|
long onlineUsers = 0; // 实际需要从Redis或其他存储中获取
|
||||||
|
stats.put("onlineUsers", onlineUsers);
|
||||||
|
|
||||||
|
// 最近7天每日登录用户数
|
||||||
|
Map<String, Long> dailyLoginUsers = new HashMap<>();
|
||||||
|
for (int i = 6; i >= 0; i--) {
|
||||||
|
LocalDate date = today.minusDays(i);
|
||||||
|
String dateStr = date.toString();
|
||||||
|
long count = 0; // 实际需要查询登录记录表
|
||||||
|
dailyLoginUsers.put(dateStr, count);
|
||||||
|
}
|
||||||
|
stats.put("dailyLoginUsers", dailyLoginUsers);
|
||||||
|
|
||||||
|
return Result.success(stats);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> adminResetPassword(Long userId, String newPassword) {
|
||||||
|
// 查询用户是否存在
|
||||||
|
AuthUser user = getById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成新的盐值
|
||||||
|
String salt = generateSalt();
|
||||||
|
// 加密密码
|
||||||
|
String encryptedPassword = encryptPassword(newPassword, salt);
|
||||||
|
|
||||||
|
// 更新密码和盐值
|
||||||
|
AuthUser updateUser = new AuthUser();
|
||||||
|
updateUser.setId(userId);
|
||||||
|
updateUser.setPassword(encryptedPassword);
|
||||||
|
updateUser.setSalt(salt);
|
||||||
|
updateUser.setUpdatedBy("admin");
|
||||||
|
updateUser.setUpdateTime(LocalDateTime.now());
|
||||||
|
updateById(updateUser);
|
||||||
|
|
||||||
|
// 清除用户token
|
||||||
|
String tokenKey = "auth:token:" + userId;
|
||||||
|
redisUtils.del(tokenKey);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> lockUser(Long userId, Boolean locked) {
|
||||||
|
// 查询用户是否存在
|
||||||
|
AuthUser user = getById(userId);
|
||||||
|
if (user == null) {
|
||||||
|
return Result.error("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户状态
|
||||||
|
AuthUser updateUser = new AuthUser();
|
||||||
|
updateUser.setId(userId);
|
||||||
|
updateUser.setStatus(locked ? 0 : 1); // 0-禁用,1-正常
|
||||||
|
updateUser.setUpdatedBy("admin");
|
||||||
|
updateUser.setUpdateTime(LocalDateTime.now());
|
||||||
|
updateById(updateUser);
|
||||||
|
|
||||||
|
// 如果锁定用户,清除用户token
|
||||||
|
if (locked) {
|
||||||
|
String tokenKey = "auth:token:" + userId;
|
||||||
|
redisUtils.del(tokenKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成盐值
|
||||||
|
*/
|
||||||
|
private String generateSalt() {
|
||||||
|
return UUID.randomUUID().toString().replace("-", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加密密码
|
||||||
|
*/
|
||||||
|
private String encryptPassword(String password, String salt) {
|
||||||
|
// 实际项目中应该使用加密算法如BCrypt
|
||||||
|
return DigestUtils.md5DigestAsHex((password + salt).getBytes());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户角色
|
* 获取用户角色
|
||||||
*
|
*
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
package com.nuaa.video.comment.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.comment.entity.Comment;
|
||||||
|
import com.nuaa.video.comment.service.CommentService;
|
||||||
|
import com.nuaa.video.comment.vo.CommentStatVO;
|
||||||
|
import com.nuaa.video.comment.vo.CommentVO;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 评论管理控制器 - 供管理后台调用
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/comment/admin")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminCommentController {
|
||||||
|
|
||||||
|
private final CommentService commentService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论列表 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<Page<CommentVO>> 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) {
|
||||||
|
|
||||||
|
return commentService.getAdminCommentList(page, size, videoId, userId, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论详情 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/{commentId}")
|
||||||
|
public Result<CommentVO> getCommentDetail(@PathVariable Long commentId) {
|
||||||
|
return commentService.getAdminCommentDetail(commentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除评论 - 管理接口
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{commentId}")
|
||||||
|
public Result<Void> deleteComment(@PathVariable Long commentId) {
|
||||||
|
return commentService.adminDeleteComment(commentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论统计信息 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public Result<CommentStatVO> getCommentStats() {
|
||||||
|
return commentService.getCommentStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -95,4 +95,24 @@ public interface CommentService extends IService<Comment> {
|
|||||||
* @return 统计信息
|
* @return 统计信息
|
||||||
*/
|
*/
|
||||||
Result<CommentStatVO> getCommentStat(Long videoId);
|
Result<CommentStatVO> getCommentStat(Long videoId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论列表 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Page<CommentVO>> getAdminCommentList(Integer page, Integer size, Long videoId, Long userId, String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论详情 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<CommentVO> getAdminCommentDetail(Long commentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员删除评论 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Void> adminDeleteComment(Long commentId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取评论统计信息 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<CommentStatVO> getCommentStats();
|
||||||
}
|
}
|
||||||
+121
@@ -25,7 +25,9 @@ import org.springframework.beans.BeanUtils;
|
|||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -362,6 +364,125 @@ public class CommentServiceImpl extends ServiceImpl<CommentMapper, Comment> impl
|
|||||||
return Result.success(statVO);
|
return Result.success(statVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<CommentVO>> getAdminCommentList(Integer page, Integer size, Long videoId, Long userId, String keyword) {
|
||||||
|
if (page == null || page < 1) {
|
||||||
|
page = 1;
|
||||||
|
}
|
||||||
|
if (size == null || size < 1) {
|
||||||
|
size = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建查询条件
|
||||||
|
LambdaQueryWrapper<Comment> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 管理员可以查看所有状态的评论,包括已删除的
|
||||||
|
|
||||||
|
// 按视频ID筛选
|
||||||
|
if (videoId != null) {
|
||||||
|
queryWrapper.eq(Comment::getVideoId, videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按用户ID筛选
|
||||||
|
if (userId != null) {
|
||||||
|
queryWrapper.eq(Comment::getUserId, userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按关键词筛选
|
||||||
|
if (StringUtils.hasText(keyword)) {
|
||||||
|
queryWrapper.like(Comment::getContent, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间降序排序
|
||||||
|
queryWrapper.orderByDesc(Comment::getCreateTime);
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
Page<Comment> commentPage = new Page<>(page, size);
|
||||||
|
commentPage = page(commentPage, queryWrapper);
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
Page<CommentVO> voPage = new Page<>();
|
||||||
|
BeanUtils.copyProperties(commentPage, voPage, "records");
|
||||||
|
|
||||||
|
List<CommentVO> commentVOList = new ArrayList<>();
|
||||||
|
for (Comment comment : commentPage.getRecords()) {
|
||||||
|
CommentVO vo = convertToCommentVO(comment, null);
|
||||||
|
commentVOList.add(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
voPage.setRecords(commentVOList);
|
||||||
|
|
||||||
|
return Result.success(voPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<CommentVO> getAdminCommentDetail(Long commentId) {
|
||||||
|
// 查询评论信息
|
||||||
|
Comment comment = getById(commentId);
|
||||||
|
if (comment == null) {
|
||||||
|
return Result.error("评论不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
CommentVO vo = convertToCommentVO(comment, null);
|
||||||
|
|
||||||
|
// 获取回复列表
|
||||||
|
Result<List<ReplyVO>> repliesResult = commentReplyService.getHotReplies(commentId, 10, null);
|
||||||
|
if (repliesResult.getCode() == 200 && repliesResult.getData() != null) {
|
||||||
|
vo.setHotReplies(repliesResult.getData());
|
||||||
|
} else {
|
||||||
|
vo.setHotReplies(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> adminDeleteComment(Long commentId) {
|
||||||
|
// 查询评论是否存在
|
||||||
|
Comment comment = getById(commentId);
|
||||||
|
if (comment == null) {
|
||||||
|
return Result.error("评论不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逻辑删除评论(设置状态为0)
|
||||||
|
comment.setStatus(0);
|
||||||
|
comment.setUpdatedBy("admin");
|
||||||
|
comment.setUpdateTime(LocalDateTime.now());
|
||||||
|
updateById(comment);
|
||||||
|
|
||||||
|
// 更新视频评论数
|
||||||
|
contentFeignClient.increaseCommentCount(comment.getVideoId(), -1, "Bearer ");
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<CommentStatVO> getCommentStats() {
|
||||||
|
CommentStatVO statVO = new CommentStatVO();
|
||||||
|
|
||||||
|
// 获取各种统计数据
|
||||||
|
// 1. 总评论数
|
||||||
|
long totalComments = count();
|
||||||
|
statVO.setCommentCount((int)totalComments);
|
||||||
|
|
||||||
|
// 2. 今日新增评论数
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
long todayComments = count(new LambdaQueryWrapper<Comment>()
|
||||||
|
.ge(Comment::getCreateTime, today.atStartOfDay()));
|
||||||
|
|
||||||
|
// 3. 回复总数
|
||||||
|
long totalReplies = commentReplyMapper.selectCount(null);
|
||||||
|
statVO.setReplyCount((int)totalReplies);
|
||||||
|
|
||||||
|
// 4. 总点赞数
|
||||||
|
long totalLikes = commentLikeMapper.selectCount(null);
|
||||||
|
statVO.setLikeCount((int)totalLikes);
|
||||||
|
|
||||||
|
return Result.success(statVO);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 将评论实体转换为VO
|
* 将评论实体转换为VO
|
||||||
*/
|
*/
|
||||||
|
|||||||
+55
@@ -0,0 +1,55 @@
|
|||||||
|
package com.nuaa.video.content.controller;
|
||||||
|
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.content.entity.ContentCategory;
|
||||||
|
import com.nuaa.video.content.service.CategoryService;
|
||||||
|
import com.nuaa.video.content.vo.CategoryVO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分类管理控制器 - 供管理后台调用
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/content/category/admin")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminCategoryController {
|
||||||
|
|
||||||
|
private final CategoryService categoryService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取分类列表 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<List<CategoryVO>> getCategoryList() {
|
||||||
|
return categoryService.getCategoryList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加分类 - 管理接口
|
||||||
|
*/
|
||||||
|
@PostMapping
|
||||||
|
public Result<Void> addCategory(@RequestBody ContentCategory category) {
|
||||||
|
return categoryService.addCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新分类 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping
|
||||||
|
public Result<Void> updateCategory(@RequestBody ContentCategory category) {
|
||||||
|
return categoryService.updateCategory(category);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除分类 - 管理接口
|
||||||
|
*/
|
||||||
|
@DeleteMapping("/{categoryId}")
|
||||||
|
public Result<Void> deleteCategory(@PathVariable Long categoryId) {
|
||||||
|
return categoryService.deleteCategory(categoryId);
|
||||||
|
}
|
||||||
|
}
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package com.nuaa.video.content.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.content.entity.ContentVideo;
|
||||||
|
import com.nuaa.video.content.dto.VideoQueryDTO;
|
||||||
|
import com.nuaa.video.content.service.VideoService;
|
||||||
|
import com.nuaa.video.content.vo.VideoInfoVO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 视频管理控制器 - 供管理后台调用
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/content/video/admin")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminVideoController {
|
||||||
|
|
||||||
|
private final VideoService videoService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频列表 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<Page<VideoInfoVO>> 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) {
|
||||||
|
|
||||||
|
VideoQueryDTO queryDTO = new VideoQueryDTO();
|
||||||
|
queryDTO.setKeyword(keyword);
|
||||||
|
queryDTO.setCategoryId(categoryId);
|
||||||
|
queryDTO.setStatus(status);
|
||||||
|
queryDTO.setPageNum(page);
|
||||||
|
queryDTO.setPageSize(size);
|
||||||
|
|
||||||
|
Page<ContentVideo> pageParam = new Page<>(page, size);
|
||||||
|
return videoService.getAdminVideoList(pageParam, queryDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频详情 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/{videoId}")
|
||||||
|
public Result<VideoInfoVO> getVideoDetail(@PathVariable Long videoId) {
|
||||||
|
return videoService.getAdminVideoDetail(videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核视频 - 管理接口
|
||||||
|
*/
|
||||||
|
@PostMapping("/review/{videoId}")
|
||||||
|
public Result<Void> reviewVideo(
|
||||||
|
@PathVariable Long videoId,
|
||||||
|
@RequestParam Integer status,
|
||||||
|
@RequestParam(required = false) String reason) {
|
||||||
|
return videoService.reviewVideo(videoId, null, status, reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下架视频 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/disable/{videoId}")
|
||||||
|
public Result<Void> disableVideo(@PathVariable Long videoId) {
|
||||||
|
return videoService.disableVideo(videoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频统计信息 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public Result<Object> getVideoStats() {
|
||||||
|
return videoService.getVideoStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,4 +155,24 @@ public interface VideoService extends IService<ContentVideo> {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
Result<Void> reviewVideo(Long videoId, Long reviewerId, Integer status, String reason);
|
Result<Void> reviewVideo(Long videoId, Long reviewerId, Integer status, String reason);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频列表 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Page<VideoInfoVO>> getAdminVideoList(Page<ContentVideo> page, VideoQueryDTO queryDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频详情 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<VideoInfoVO> getAdminVideoDetail(Long videoId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下架视频 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Void> disableVideo(Long videoId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取视频统计信息 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Object> getVideoStats();
|
||||||
}
|
}
|
||||||
+193
@@ -27,8 +27,10 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
@@ -724,4 +726,195 @@ public class VideoServiceImpl extends ServiceImpl<ContentVideoMapper, ContentVid
|
|||||||
|
|
||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 在 com.nuaa.video.content.service.impl.VideoServiceImpl 类中添加:
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<VideoInfoVO>> getAdminVideoList(Page<ContentVideo> page, VideoQueryDTO queryDTO) {
|
||||||
|
// 管理员可查看所有状态的视频,不需要过滤status
|
||||||
|
Page<ContentVideo> videoPage = videoMapper.selectVideoList(page, queryDTO);
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
Page<VideoInfoVO> voPage = new Page<>();
|
||||||
|
BeanUtils.copyProperties(videoPage, voPage, "records");
|
||||||
|
|
||||||
|
List<VideoInfoVO> voList = new ArrayList<>();
|
||||||
|
for (ContentVideo video : videoPage.getRecords()) {
|
||||||
|
VideoInfoVO vo = new VideoInfoVO();
|
||||||
|
BeanUtils.copyProperties(video, vo);
|
||||||
|
|
||||||
|
// 获取分类名称
|
||||||
|
if (video.getCategoryId() != null) {
|
||||||
|
ContentCategory category = categoryMapper.selectById(video.getCategoryId());
|
||||||
|
if (category != null) {
|
||||||
|
vo.setCategoryName(category.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标签
|
||||||
|
Result<List<TagVO>> tagResult = tagService.getVideoTags(video.getId());
|
||||||
|
if (tagResult.getCode() == 200 && tagResult.getData() != null) {
|
||||||
|
List<TagVO> tags = tagResult.getData();
|
||||||
|
vo.setTagIds(tags.stream().map(TagVO::getId).collect(Collectors.toList()));
|
||||||
|
vo.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
try {
|
||||||
|
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||||
|
if (userResult.getCode() == 200 && userResult.getData() != null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||||
|
vo.setUserNickname((String) userData.get("nickname"));
|
||||||
|
vo.setUserAvatar((String) userData.get("avatar"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户信息失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
voList.add(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
voPage.setRecords(voList);
|
||||||
|
|
||||||
|
return Result.success(voPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<VideoInfoVO> getAdminVideoDetail(Long videoId) {
|
||||||
|
// 查询视频信息
|
||||||
|
ContentVideo video = getById(videoId);
|
||||||
|
if (video == null) {
|
||||||
|
return Result.error("视频不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
VideoInfoVO vo = new VideoInfoVO();
|
||||||
|
BeanUtils.copyProperties(video, vo);
|
||||||
|
|
||||||
|
// 获取分类名称
|
||||||
|
if (video.getCategoryId() != null) {
|
||||||
|
ContentCategory category = categoryMapper.selectById(video.getCategoryId());
|
||||||
|
if (category != null) {
|
||||||
|
vo.setCategoryName(category.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标签
|
||||||
|
Result<List<TagVO>> tagResult = tagService.getVideoTags(video.getId());
|
||||||
|
if (tagResult.getCode() == 200 && tagResult.getData() != null) {
|
||||||
|
List<TagVO> tags = tagResult.getData();
|
||||||
|
vo.setTagIds(tags.stream().map(TagVO::getId).collect(Collectors.toList()));
|
||||||
|
vo.setTagNames(tags.stream().map(TagVO::getName).collect(Collectors.toList()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息
|
||||||
|
try {
|
||||||
|
Result<Object> userResult = userFeignClient.getUserProfile(video.getUserId());
|
||||||
|
if (userResult.getCode() == 200 && userResult.getData() != null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Map<String, Object> userData = (Map<String, Object>) userResult.getData();
|
||||||
|
vo.setUserNickname((String) userData.get("nickname"));
|
||||||
|
vo.setUserAvatar((String) userData.get("avatar"));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("获取用户信息失败: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取审核记录
|
||||||
|
ContentVideoReview latestReview = videoReviewMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ContentVideoReview>()
|
||||||
|
.eq(ContentVideoReview::getVideoId, videoId)
|
||||||
|
.orderByDesc(ContentVideoReview::getCreateTime)
|
||||||
|
.last("LIMIT 1")
|
||||||
|
);
|
||||||
|
|
||||||
|
// 可以将审核记录信息添加到VO中返回
|
||||||
|
if (latestReview != null) {
|
||||||
|
vo.setReviewStatus(latestReview.getStatus());
|
||||||
|
vo.setReviewReason(latestReview.getReason());
|
||||||
|
vo.setReviewerId(latestReview.getReviewerId());
|
||||||
|
vo.setReviewTime(latestReview.getCreateTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> disableVideo(Long videoId) {
|
||||||
|
// 查询视频是否存在
|
||||||
|
ContentVideo video = getById(videoId);
|
||||||
|
if (video == null) {
|
||||||
|
return Result.error("视频不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新视频状态为已下架
|
||||||
|
ContentVideo updateVideo = new ContentVideo();
|
||||||
|
updateVideo.setId(videoId);
|
||||||
|
updateVideo.setStatus(2); // 已下架
|
||||||
|
updateVideo.setUpdatedBy("admin");
|
||||||
|
updateVideo.setUpdateTime(LocalDateTime.now());
|
||||||
|
updateById(updateVideo);
|
||||||
|
|
||||||
|
// 记录操作日志
|
||||||
|
ContentVideoReview review = new ContentVideoReview();
|
||||||
|
review.setVideoId(videoId);
|
||||||
|
review.setReviewerId(0L); // 系统/管理员ID
|
||||||
|
review.setStatus(2); // 下架
|
||||||
|
review.setReason("管理员下架操作");
|
||||||
|
review.setCreatedBy("admin");
|
||||||
|
review.setCreateTime(LocalDateTime.now());
|
||||||
|
videoReviewMapper.insert(review);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Object> getVideoStats() {
|
||||||
|
// 收集视频统计数据
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
|
||||||
|
// 总视频数
|
||||||
|
long totalVideos = count();
|
||||||
|
stats.put("totalVideos", totalVideos);
|
||||||
|
|
||||||
|
// 今日新增视频
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
long todayNewVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||||
|
.ge(ContentVideo::getCreateTime, today.atStartOfDay()));
|
||||||
|
stats.put("todayNewVideos", todayNewVideos);
|
||||||
|
|
||||||
|
// 待审核视频数
|
||||||
|
long pendingReviewVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||||
|
.eq(ContentVideo::getStatus, 0));
|
||||||
|
stats.put("pendingReviewVideos", pendingReviewVideos);
|
||||||
|
|
||||||
|
// 已发布视频数
|
||||||
|
long publishedVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||||
|
.eq(ContentVideo::getStatus, 1));
|
||||||
|
stats.put("publishedVideos", publishedVideos);
|
||||||
|
|
||||||
|
// 已下架视频数
|
||||||
|
long disabledVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||||
|
.eq(ContentVideo::getStatus, 2));
|
||||||
|
stats.put("disabledVideos", disabledVideos);
|
||||||
|
|
||||||
|
// 审核未通过视频数
|
||||||
|
long rejectedVideos = count(new LambdaQueryWrapper<ContentVideo>()
|
||||||
|
.eq(ContentVideo::getStatus, 3));
|
||||||
|
stats.put("rejectedVideos", rejectedVideos);
|
||||||
|
|
||||||
|
// 各分类视频数量分布
|
||||||
|
Map<String, Long> categoryDistribution = new HashMap<>();
|
||||||
|
List<ContentCategory> categories = categoryMapper.selectList(null);
|
||||||
|
for (ContentCategory category : categories) {
|
||||||
|
long count = count(new LambdaQueryWrapper<ContentVideo>()
|
||||||
|
.eq(ContentVideo::getCategoryId, category.getId()));
|
||||||
|
categoryDistribution.put(category.getName(), count);
|
||||||
|
}
|
||||||
|
stats.put("categoryDistribution", categoryDistribution);
|
||||||
|
|
||||||
|
return Result.success(stats);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -142,4 +142,24 @@ public class VideoInfoVO implements Serializable {
|
|||||||
* 发布时间
|
* 发布时间
|
||||||
*/
|
*/
|
||||||
private LocalDateTime publishTime;
|
private LocalDateTime publishTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核员ID
|
||||||
|
*/
|
||||||
|
private Long reviewerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核状态(1:通过,2:不通过)
|
||||||
|
*/
|
||||||
|
private Integer reviewStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核原因
|
||||||
|
*/
|
||||||
|
private String reviewReason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime reviewTime;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.nuaa.video.user.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
|
import com.nuaa.video.user.service.UserInfoService;
|
||||||
|
import com.nuaa.video.user.vo.UserProfileVO;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户管理控制器 - 供管理后台调用
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/user/profile/admin")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdminUserController {
|
||||||
|
|
||||||
|
private final UserInfoService userInfoService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户列表 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/list")
|
||||||
|
public Result<Page<UserProfileVO>> getUserList(
|
||||||
|
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||||
|
@RequestParam(value = "size", defaultValue = "10") Integer size,
|
||||||
|
@RequestParam(value = "keyword", required = false) String keyword) {
|
||||||
|
Page<UserInfo> pageParam = new Page<>(page, size);
|
||||||
|
return userInfoService.getAdminUserList(pageParam, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户详情 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/{userId}")
|
||||||
|
public Result<UserProfileVO> getUserDetail(@PathVariable Long userId) {
|
||||||
|
return userInfoService.getAdminUserDetail(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 禁用/启用用户 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/status/{userId}/{status}")
|
||||||
|
public Result<Void> updateUserStatus(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@PathVariable Integer status) {
|
||||||
|
return userInfoService.updateUserStatus(userId, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置用户密码 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/reset-password/{userId}")
|
||||||
|
public Result<Void> resetUserPassword(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@RequestParam String newPassword) {
|
||||||
|
return userInfoService.resetUserPassword(userId, newPassword);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户统计信息 - 管理接口
|
||||||
|
*/
|
||||||
|
@GetMapping("/stats")
|
||||||
|
public Result<Object> getUserStats() {
|
||||||
|
return userInfoService.getUserStats();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,8 @@
|
|||||||
// com.nuaa.video.user.feign.AuthFeignClient.java
|
|
||||||
package com.nuaa.video.user.feign;
|
package com.nuaa.video.user.feign;
|
||||||
|
|
||||||
import com.nuaa.video.common.model.vo.Result;
|
import com.nuaa.video.common.model.vo.Result;
|
||||||
import org.springframework.cloud.openfeign.FeignClient;
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
|
||||||
import org.springframework.web.bind.annotation.RequestHeader;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 认证服务Feign客户端
|
* 认证服务Feign客户端
|
||||||
@@ -30,4 +27,19 @@ public interface AuthFeignClient {
|
|||||||
*/
|
*/
|
||||||
@GetMapping("/username/{userId}")
|
@GetMapping("/username/{userId}")
|
||||||
String getUsernameById(@PathVariable("userId") Long userId);
|
String getUsernameById(@PathVariable("userId") Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定/解锁用户账号 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/admin/lock/{userId}/{locked}")
|
||||||
|
Result<Void> lockUser(@PathVariable Long userId,
|
||||||
|
@PathVariable Boolean locked);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 管理员重置用户密码 - 管理接口
|
||||||
|
*/
|
||||||
|
@PutMapping("/admin/reset-password/{userId}")
|
||||||
|
Result<Void> resetPassword(
|
||||||
|
@PathVariable Long userId,
|
||||||
|
@RequestParam String newPassword);
|
||||||
}
|
}
|
||||||
@@ -92,4 +92,29 @@ public interface UserInfoService extends IService<UserInfo> {
|
|||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
Result<Void> increaseFavoriteCount(Long userId, Integer count);
|
Result<Void> increaseFavoriteCount(Long userId, Integer count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户列表 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Page<UserProfileVO>> getAdminUserList(Page<UserInfo> page, String keyword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户详情 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<UserProfileVO> getAdminUserDetail(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新用户状态 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Void> updateUserStatus(Long userId, Integer status);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重置用户密码 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Void> resetUserPassword(Long userId, String newPassword);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户统计信息 - 管理接口
|
||||||
|
*/
|
||||||
|
Result<Object> getUserStats();
|
||||||
}
|
}
|
||||||
@@ -9,6 +9,7 @@ import com.nuaa.video.common.utils.MinioUtils;
|
|||||||
import com.nuaa.video.user.dto.UserProfileDTO;
|
import com.nuaa.video.user.dto.UserProfileDTO;
|
||||||
import com.nuaa.video.user.entity.UserInfo;
|
import com.nuaa.video.user.entity.UserInfo;
|
||||||
import com.nuaa.video.user.entity.UserStatDaily;
|
import com.nuaa.video.user.entity.UserStatDaily;
|
||||||
|
import com.nuaa.video.user.feign.AuthFeignClient;
|
||||||
import com.nuaa.video.user.mapper.UserFollowMapper;
|
import com.nuaa.video.user.mapper.UserFollowMapper;
|
||||||
import com.nuaa.video.user.mapper.UserInfoMapper;
|
import com.nuaa.video.user.mapper.UserInfoMapper;
|
||||||
import com.nuaa.video.user.mapper.UserStatDailyMapper;
|
import com.nuaa.video.user.mapper.UserStatDailyMapper;
|
||||||
@@ -20,10 +21,15 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用户信息服务实现类
|
* 用户信息服务实现类
|
||||||
@@ -38,6 +44,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
|||||||
private final UserSettingService userSettingService;
|
private final UserSettingService userSettingService;
|
||||||
private final MinioUtils minioUtils;
|
private final MinioUtils minioUtils;
|
||||||
private final UserStatDailyMapper userStatDailyMapper;
|
private final UserStatDailyMapper userStatDailyMapper;
|
||||||
|
private final AuthFeignClient authFeignClient;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Result<UserProfileVO> getUserProfile(Long userId, Long currentUserId) {
|
public Result<UserProfileVO> getUserProfile(Long userId, Long currentUserId) {
|
||||||
@@ -245,6 +252,133 @@ public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> i
|
|||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Page<UserProfileVO>> getAdminUserList(Page<UserInfo> page, String keyword) {
|
||||||
|
LambdaQueryWrapper<UserInfo> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
|
// 添加关键词搜索条件
|
||||||
|
if (StringUtils.hasText(keyword)) {
|
||||||
|
queryWrapper.like(UserInfo::getNickname, keyword)
|
||||||
|
.or()
|
||||||
|
.like(UserInfo::getSignature, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按创建时间降序排序
|
||||||
|
queryWrapper.orderByDesc(UserInfo::getCreateTime);
|
||||||
|
|
||||||
|
// 分页查询
|
||||||
|
Page<UserInfo> userPage = page(page, queryWrapper);
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
Page<UserProfileVO> voPage = new Page<>();
|
||||||
|
BeanUtils.copyProperties(userPage, voPage, "records");
|
||||||
|
|
||||||
|
List<UserProfileVO> voList = userPage.getRecords().stream()
|
||||||
|
.map(user -> {
|
||||||
|
UserProfileVO vo = new UserProfileVO();
|
||||||
|
BeanUtils.copyProperties(user, vo);
|
||||||
|
return vo;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
voPage.setRecords(voList);
|
||||||
|
|
||||||
|
return Result.success(voPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<UserProfileVO> getAdminUserDetail(Long userId) {
|
||||||
|
// 查询用户信息
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为VO
|
||||||
|
UserProfileVO vo = new UserProfileVO();
|
||||||
|
BeanUtils.copyProperties(userInfo, vo);
|
||||||
|
|
||||||
|
// 查询用户统计信息
|
||||||
|
// 例如:历史发布视频、历史评论等
|
||||||
|
|
||||||
|
return Result.success(vo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> updateUserStatus(Long userId, Integer status) {
|
||||||
|
// 查询用户是否存在
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新用户状态
|
||||||
|
UserInfo updateUser = new UserInfo();
|
||||||
|
updateUser.setId(userId);
|
||||||
|
updateUser.setUpdateTime(LocalDateTime.now());
|
||||||
|
updateUser.setUpdatedBy("admin");
|
||||||
|
updateById(updateUser);
|
||||||
|
|
||||||
|
// 当禁用用户,需要清除用户相关的登录态等
|
||||||
|
if (status == 0) {
|
||||||
|
// 发送事件通知auth服务禁用该用户账号
|
||||||
|
//TODO 可以优化为消息队列
|
||||||
|
authFeignClient.lockUser(userId, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional(rollbackFor = Exception.class)
|
||||||
|
public Result<Void> resetUserPassword(Long userId, String newPassword) {
|
||||||
|
// 查询用户是否存在
|
||||||
|
UserInfo userInfo = getById(userId);
|
||||||
|
if (userInfo == null) {
|
||||||
|
return Result.error("用户不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用auth服务重置密码(实际项目中通过Feign或消息队列)
|
||||||
|
authFeignClient.resetPassword(userId, newPassword);
|
||||||
|
|
||||||
|
log.info("重置用户密码: userId={}, newPassword=***", userId);
|
||||||
|
|
||||||
|
return Result.success();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Result<Object> getUserStats() {
|
||||||
|
// 收集用户统计数据
|
||||||
|
Map<String, Object> stats = new HashMap<>();
|
||||||
|
|
||||||
|
// 总用户数
|
||||||
|
long totalUsers = count();
|
||||||
|
stats.put("totalUsers", totalUsers);
|
||||||
|
|
||||||
|
// 今日新增用户
|
||||||
|
LocalDate today = LocalDate.now();
|
||||||
|
long todayNewUsers = count(new LambdaQueryWrapper<UserInfo>()
|
||||||
|
.ge(UserInfo::getCreateTime, today.atStartOfDay()));
|
||||||
|
stats.put("todayNewUsers", todayNewUsers);
|
||||||
|
|
||||||
|
// 活跃用户数(最近7天有登录记录的用户)
|
||||||
|
// 此处需要结合用户登录记录表查询,简化处理
|
||||||
|
stats.put("activeUsers", totalUsers * 0.3); // 示例数据
|
||||||
|
|
||||||
|
// 用户等级分布
|
||||||
|
// 假设level字段代表用户等级
|
||||||
|
Map<String, Object> levelDistribution = new HashMap<>();
|
||||||
|
for (int i = 1; i <= 6; i++) {
|
||||||
|
int level = i;
|
||||||
|
long count = count(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getLevel, level));
|
||||||
|
levelDistribution.put("level" + i, count);
|
||||||
|
}
|
||||||
|
stats.put("levelDistribution", levelDistribution);
|
||||||
|
|
||||||
|
return Result.success(stats);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 更新用户每日统计数据
|
* 更新用户每日统计数据
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user