refactor(project): Reorganize frontend and backend directories

This commit is contained in:
2026-03-18 10:45:15 +08:00
parent cc9eaf4cdf
commit 92a139e470
513 changed files with 28251 additions and 287 deletions
+7
View File
@@ -0,0 +1,7 @@
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY target/video-admin-*.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
+152
View File
@@ -0,0 +1,152 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>video-platform</artifactId>
<groupId>com.nuaa.video</groupId>
<version>1.0.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>video-admin</artifactId>
<dependencies>
<!-- 引入公共模块 -->
<dependency>
<groupId>com.nuaa.video</groupId>
<artifactId>video-common</artifactId>
<version>${project.version}</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- Spring Boot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Spring Boot Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Spring Cloud OpenFeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- Spring Cloud LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- Spring Cloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- MySQL Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>3.5.11</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- JWT相关 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<!-- AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Hutool工具包 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -0,0 +1,23 @@
package com.nuaa.video.admin;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
/**
* 管理后台启动类
*/
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
@MapperScan("com.nuaa.video.admin.map" +
"" +
"" +
"per")
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class, args);
}
}
@@ -0,0 +1,16 @@
package com.nuaa.video.admin.annotation;
import java.lang.annotation.*;
/**
* 操作日志注解
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperationLog {
/**
* 操作描述
*/
String value() default "";
}
@@ -0,0 +1,220 @@
package com.nuaa.video.admin.aspect;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nuaa.video.admin.annotation.OperationLog;
import com.nuaa.video.admin.entity.AdminOperationLog;
import com.nuaa.video.admin.mapper.AdminOperationLogMapper;
import com.nuaa.video.admin.security.AdminUserDetails;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.List;
/**
* 操作日志切面
*/
@Slf4j
@Aspect
@Component
@RequiredArgsConstructor
public class OperationLogAspect {
private final AdminOperationLogMapper operationLogMapper;
private final ObjectMapper objectMapper;
@Value("${operation-log.enabled:true}")
private boolean enabled;
@Value("#{'${operation-log.exclude-paths:}'.split(',')}")
private List<String> excludePaths;
/**
* 切入点
*/
@Pointcut("@annotation(com.nuaa.video.admin.annotation.OperationLog)")
public void operationLogPointcut() {
}
/**
* 处理完请求后执行
*
* @param joinPoint 切点
* @param result 返回值
*/
@AfterReturning(pointcut = "operationLogPointcut()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
if (!enabled) {
return;
}
handleLog(joinPoint, null, result);
}
/**
* 拦截异常操作
*
* @param joinPoint 切点
* @param e 异常
*/
@AfterThrowing(value = "operationLogPointcut()", throwing = "e")
public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
if (!enabled) {
return;
}
handleLog(joinPoint, e, null);
}
/**
* 处理日志
*/
private void handleLog(JoinPoint joinPoint, Exception e, Object result) {
try {
// 获取请求信息
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes == null) {
return;
}
HttpServletRequest request = attributes.getRequest();
String requestURI = request.getRequestURI();
// 检查是否需要排除
if (isExclude(requestURI)) {
return;
}
// 获取注解信息
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
Method method = signature.getMethod();
OperationLog annotation = method.getAnnotation(OperationLog.class);
// 如果未配置注解,则不记录日志
if (annotation == null) {
return;
}
// 获取当前登录用户
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Long adminId = null;
String username = "unknown";
if (authentication != null && authentication.getPrincipal() instanceof AdminUserDetails) {
AdminUserDetails userDetails = (AdminUserDetails) authentication.getPrincipal();
adminId = userDetails.getId();
username = userDetails.getUsername();
} else if (authentication != null && authentication.getPrincipal() instanceof String) {
username = (String) authentication.getPrincipal();
}
// 获取操作
String operation = annotation.value();
// 获取请求方法
String methodName = joinPoint.getTarget().getClass().getName() + "." + method.getName();
// 获取请求参数
String params = getParams(joinPoint);
// 获取IP地址
String ip = getIpAddress(request);
// 创建日志对象
AdminOperationLog operationLog = new AdminOperationLog();
operationLog.setAdminId(adminId);
operationLog.setUsername(username);
operationLog.setOperation(operation);
operationLog.setMethod(methodName);
operationLog.setParams(params);
operationLog.setIp(ip);
operationLog.setOperationTime(LocalDateTime.now());
// 处理异常和结果
if (e != null) {
operationLog.setStatus(0); // 失败
operationLog.setErrorMsg(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : e.toString());
} else {
operationLog.setStatus(1); // 成功
}
// 保存日志
operationLogMapper.insert(operationLog);
} catch (Exception ex) {
log.error("记录操作日志失败", ex);
}
}
/**
* 获取请求参数
*/
private String getParams(JoinPoint joinPoint) {
try {
Object[] args = joinPoint.getArgs();
// 过滤掉一些不需要记录的参数类型
args = Arrays.stream(args)
.filter(arg -> !(arg instanceof HttpServletRequest || arg instanceof HttpServletRequest))
.toArray();
return objectMapper.writeValueAsString(args);
} catch (Exception e) {
log.error("获取请求参数失败", e);
return "获取参数失败";
}
}
/**
* 获取请求的IP地址
*/
private String getIpAddress(HttpServletRequest request) {
String ip = request.getHeader("X-Forwarded-For");
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP");
}
if (!StringUtils.hasText(ip) || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr();
}
// 多个代理的情况,第一个IP为客户端真实IP
if (ip != null && ip.indexOf(",") > 0) {
ip = ip.substring(0, ip.indexOf(","));
}
return ip;
}
/**
* 判断是否需要排除请求
*/
private boolean isExclude(String requestURI) {
if (excludePaths != null && !excludePaths.isEmpty()) {
for (String pattern : excludePaths) {
if (StringUtils.hasText(pattern) && requestURI.matches(pattern.trim())) {
return true;
}
}
}
return false;
}
}
@@ -0,0 +1,15 @@
package com.nuaa.video.admin.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@@ -0,0 +1,38 @@
package com.nuaa.video.admin.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 创建配置好的ObjectMapper
ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
// 使用Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(mapper, Object.class);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
@@ -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);
}
}
@@ -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);
}
@@ -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);
}
@@ -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);
}
@@ -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,80 @@
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;
/**
* 盐
*/
@JsonIgnore
private String salt;
/**
* 授权列表
*/
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,57 @@
package com.nuaa.video.admin.security;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.service.AdminUserService;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class AdminUserDetailsServiceImpl implements UserDetailsService {
private final AdminUserService adminUserService;
// 添加@Lazy注解打破循环依赖
public AdminUserDetailsServiceImpl(@Lazy AdminUserService adminUserService) {
this.adminUserService = adminUserService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库加载用户
AdminUser adminUser = adminUserService.getByUsername(username);
if (adminUser == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 构建UserDetails对象
AdminUserDetails userDetails = new AdminUserDetails();
userDetails.setId(adminUser.getId());
userDetails.setUsername(adminUser.getUsername());
userDetails.setPassword(adminUser.getPassword());
userDetails.setSalt(adminUser.getSalt());
// 设置权限
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
// 从用户的角色中设置权限...
if (adminUser.getRoles() != null) {
adminUser.getRoles().forEach(role -> {
authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getCode()));
if (role.getPermissions() != null) {
role.getPermissions().forEach(permission ->
authorities.add(new SimpleGrantedAuthority(permission.getCode()))
);
}
});
}
userDetails.setAuthorities(authorities);
return userDetails;
}
}
@@ -0,0 +1,43 @@
package com.nuaa.video.admin.security;
import com.nuaa.video.admin.service.PasswordService;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class CustomAuthenticationProvider extends DaoAuthenticationProvider {
private final PasswordService passwordService;
public CustomAuthenticationProvider(UserDetailsService userDetailsService,
PasswordEncoder passwordEncoder,
PasswordService passwordService) {
super();
setUserDetailsService(userDetailsService);
setPasswordEncoder(passwordEncoder);
this.passwordService = passwordService;
}
@Override
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
if (authentication.getCredentials() == null) {
throw new BadCredentialsException("认证失败:没有提供密码");
}
String presentedPassword = authentication.getCredentials().toString();
// 使用密码服务验证密码,不再需要盐值
if (!passwordService.matches(presentedPassword, "", userDetails.getPassword())) {
throw new BadCredentialsException("密码不正确");
}
}
}
@@ -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,70 @@
package com.nuaa.video.admin.security;
import com.nuaa.video.admin.service.PasswordService;
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.authentication.dao.DaoAuthenticationProvider;
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.core.userdetails.UserDetailsService;
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;
private final UserDetailsService userDetailsService;
private final PasswordService passwordService;
private final PasswordEncoder passwordEncoder; // Inject instead of creating
// Remove this method
// @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();
}
@Bean
public DaoAuthenticationProvider authenticationProvider() {
CustomAuthenticationProvider provider = new CustomAuthenticationProvider(
userDetailsService,
passwordEncoder, // Use injected passwordEncoder
passwordService);
return provider;
}
}
@@ -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);
}
@@ -0,0 +1,39 @@
package com.nuaa.video.admin.service;
import com.nuaa.video.admin.entity.AdminUser;
/**
* 密码处理服务接口 - 使用标准BCrypt方式
*/
public interface PasswordService {
/**
* 生成新的盐值 (BCrypt已内置盐值,此方法保留但不再使用)
* @return 盐值字符串
*/
String generateSalt();
/**
* 加密密码
* @param rawPassword 原始密码
* @param salt 盐值 (BCrypt已内置盐值,此参数不再使用)
* @return 加密后的密码
*/
String encryptPassword(String rawPassword, String salt);
/**
* 验证密码
* @param rawPassword 原始密码
* @param salt 盐值 (BCrypt已内置盐值,此参数不再使用)
* @param encodedPassword 加密后的密码
* @return 是否匹配
*/
boolean matches(String rawPassword, String salt, String encodedPassword);
/**
* 为用户设置密码
* @param user 用户对象
* @param rawPassword 原始密码
*/
void setPassword(AdminUser user, String rawPassword);
}
@@ -0,0 +1,72 @@
package com.nuaa.video.admin.service.impl;
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.PasswordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Slf4j
@Service
@RequiredArgsConstructor
public class AdminUserInitService implements ApplicationRunner {
private final AdminUserMapper adminUserMapper;
private final AdminUserRoleMapper adminUserRoleMapper;
private final PasswordService passwordService;
@Value("${app.init.admin-password:admin123}")
private String initAdminPassword;
@Override
public void run(ApplicationArguments args) throws Exception {
initAdminAccount();
}
// 移除 @Override 注解,因为这不是覆盖父类或接口的方法
public void initAdminAccount() {
// 检查是否已存在admin账号
AdminUser existingAdmin = adminUserMapper.selectByUsername("admin");
if (existingAdmin != null) {
log.info("管理员账号已存在,无需初始化");
return;
}
// 创建管理员账号
AdminUser adminUser = new AdminUser();
adminUser.setUsername("admin");
// 使用密码服务设置密码,不再需要手动设置盐值
passwordService.setPassword(adminUser, initAdminPassword);
adminUser.setRealName("系统管理员");
adminUser.setEmail("admin@example.com");
adminUser.setStatus(1); // 正常状态
adminUser.setSalt("aaaaaa"); // 盐值可以设置为默认值,该字段已抛弃
adminUser.setCreatedBy("system");
adminUser.setUpdatedBy("system");
adminUser.setCreateTime(LocalDateTime.now());
adminUser.setUpdateTime(LocalDateTime.now());
// 保存管理员
adminUserMapper.insert(adminUser);
// 创建管理员角色关联
AdminUserRole userRole = new AdminUserRole();
userRole.setAdminId(adminUser.getId());
userRole.setRoleId(1L); // 假设ID为1的角色是超级管理员
userRole.setCreatedBy("system");
userRole.setCreateTime(LocalDateTime.now());
adminUserRoleMapper.insert(userRole);
log.info("管理员账号初始化完成");
}
}
@@ -0,0 +1,310 @@
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.AdminPermissionMapper;
import com.nuaa.video.admin.mapper.AdminRoleMapper;
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.service.PasswordService;
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
// 在类定义中添加 PasswordService 依赖
@Service
@RequiredArgsConstructor
public class AdminUserServiceImpl extends ServiceImpl<AdminUserMapper, AdminUser> implements AdminUserService {
private final AdminUserMapper adminUserMapper;
private final AdminUserRoleMapper adminUserRoleMapper;
private final AdminRoleMapper adminRoleMapper;
private final AdminPermissionMapper adminPermissionMapper;
private final PasswordService passwordService;
// 移除 PasswordEncoder 依赖
// 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);
// 使用密码服务设置密码 - 使用标准BCrypt方式
String rawPassword = StringUtils.hasText(adminUserDTO.getPassword()) ?
adminUserDTO.getPassword() : "123456";
passwordService.setPassword(adminUser, rawPassword);
// 设置默认状态
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.setSalt(existUser.getSalt()); // 保留原有盐值
passwordService.setPassword(adminUser, adminUserDTO.getPassword());
} else {
// 不更新密码
adminUser.setPassword(null);
adminUser.setSalt(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.setSalt(existUser.getSalt()); // 保留原有盐值
// 使用密码服务设置新密码
passwordService.setPassword(adminUser, newPassword);
adminUser.setUpdatedBy(operator);
adminUser.setUpdateTime(LocalDateTime.now());
updateById(adminUser);
return Result.success();
}
}
@@ -0,0 +1,279 @@
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());
userDetails.setSalt(adminUser.getSalt());
// 设置权限
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,50 @@
package com.nuaa.video.admin.service.impl;
import com.nuaa.video.admin.entity.AdminUser;
import com.nuaa.video.admin.service.PasswordService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
/**
* 密码处理服务实现 - 使用标准BCrypt方式
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PasswordServiceImpl implements PasswordService {
private final PasswordEncoder passwordEncoder;
@Override
public String generateSalt() {
// BCrypt已包含盐值,此方法不再需要
return "";
}
@Override
public String encryptPassword(String rawPassword, String salt) {
// 忽略salt参数,直接使用BCrypt加密
return passwordEncoder.encode(rawPassword);
}
@Override
public boolean matches(String rawPassword, String salt, String encodedPassword) {
// 添加日志
log.debug("密码验证 - 原始密码: [隐藏], 加密密码: {}", encodedPassword);
// 忽略salt参数,直接使用BCrypt验证
boolean result = passwordEncoder.matches(rawPassword, encodedPassword);
log.debug("密码验证结果: {}", result);
return result;
}
@Override
public void setPassword(AdminUser user, String rawPassword) {
// 直接使用BCrypt加密密码
user.setPassword(passwordEncoder.encode(rawPassword));
// 不再需要设置盐值
user.setSalt(null);
}
}
@@ -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,53 @@
server:
port: 9004
spring:
application:
name: video-admin
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848}
namespace: public
group: DEFAULT_GROUP
sentinel:
transport:
dashboard: ${SENTINEL_HOST:sentinel}:${SENTINEL_PORT:8080}
port: 8724
eager: true
datasource:
url: jdbc:mysql://${MYSQL_HOST:mysql}:${MYSQL_PORT:3306}/video_admin?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
data:
redis:
host: ${REDIS_HOST:redis}
port: ${REDIS_PORT:6379}
password: redis_EsJ8FX
database: 3
timeout: 10000
lettuce:
pool:
max-active: 200
max-wait: -1
max-idle: 10
min-idle: 0
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.nuaa.video.admin.entity
configuration:
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: is_deleted
logic-delete-value: 1
logic-not-delete-value: 0
app:
init:
admin-password: admin123
@@ -0,0 +1,38 @@
server:
port: 9006
spring:
application:
name: video-admin
profiles:
active: ${SPRING_PROFILES_ACTIVE:dev}
config:
import:
- optional:nacos:common.yaml?refreshEnabled=true
- optional:nacos:common-redis-${spring.profiles.active}.yaml?refreshEnabled=true
- optional:nacos:${spring.application.name}.yaml?refreshEnabled=true
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:${NACOS_HOST:127.0.0.1}:${NACOS_PORT:8848}}
namespace: ${NACOS_NAMESPACE:}
group: ${NACOS_GROUP:DEFAULT_GROUP}
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
ip: ${SPRING_CLOUD_NACOS_DISCOVERY_IP:}
config:
server-addr: ${spring.cloud.nacos.discovery.server-addr}
namespace: ${spring.cloud.nacos.discovery.namespace}
group: ${spring.cloud.nacos.discovery.group}
file-extension: yaml
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
management:
endpoints:
web:
exposure:
include: '*'
endpoint:
health:
show-details: always
@@ -0,0 +1,23 @@
spring:
application:
name: video-admin
cloud:
nacos:
discovery:
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848}
namespace: public
group: DEFAULT_GROUP
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
ephemeral: true
ip: ${ADMIN_IP:}
port: ${server.port:9004}
fail-fast: true
register-enabled: true
config:
server-addr: ${NACOS_HOST:nacos}:${NACOS_PORT:8848}
namespace: public
group: DEFAULT_GROUP
username: ${NACOS_USERNAME:nacos}
password: ${NACOS_PASSWORD:nacos}
file-extension: yaml
@@ -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 &gt;= #{startTime}
</if>
<if test="endTime != null and endTime != ''">
AND operation_time &lt;= #{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>