8 Commits

Author SHA1 Message Date
f4201cba5b add游戏管理相关逻辑 2026-06-01 15:07:05 +08:00
5f414dceae add游戏管理相关逻辑 2026-06-01 13:43:51 +08:00
6ce84f6f9f 修复逻辑删除 2026-05-31 18:01:44 +08:00
3f46bb02cf 修复sql 日志 2026-05-31 09:31:10 +08:00
4496a31334 修复权限 2026-05-31 06:48:12 +08:00
290f8bacd2 登录修复2 2026-05-30 21:33:51 +08:00
b99834bdc0 登录修复 2026-05-30 21:21:47 +08:00
ce5873793a 第2版代码 2026-05-30 01:24:31 +08:00
102 changed files with 4198 additions and 226 deletions

View File

@@ -1,7 +1,14 @@
{
"permissions": {
"allow": [
"Bash(mvn clean *)"
"Bash(mvn clean *)",
"mcp__codegraph__codegraph_context",
"mcp__codegraph__codegraph_files",
"mcp__codegraph__codegraph_search",
"mcp__codegraph__codegraph_explore",
"mcp__codegraph__codegraph_node",
"Bash(python -)",
"mcp__codegraph__codegraph_status"
]
}
}

View File

@@ -14,3 +14,10 @@ cache/
# Hook markers
.dirty
# Compiled class files
target/
*.class

1
.gitignore vendored
View File

@@ -36,3 +36,4 @@ application-test.yml
# Compiled class files
*.class
/.codegraph/daemon.pid

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,6 @@
{
"lastAnalyzedAt": "2026-05-28T18:03:22.405243Z",
"gitCommitHash": "8cb9c14b64ed71d8e46a304143ae43200dc0acc3",
"version": "1.0.0",
"analyzedFiles": 56
}

View File

@@ -103,7 +103,8 @@ SysUser (entity) + UserCreateDTO (dto) + UserVO (vo)
1. **Single-table queries**: Use MyBatis-Plus native methods (`selectById`, `selectList`, `selectPage`, `LambdaQueryWrapper`)
2. **Join queries**: Use MyBatis XML mapper (`@MapperScan("com.leiyuwei.mhxy.**.mapper")`)
3. **Logical deletion**: All entities have `deleted` field (0=active, 1=deleted)
3. **No logical deletion**: Project does not use `deleted` field for filtering - query all records
4. **No wildcard select**: Do not use `SELECT *` or table wildcards like `r.*` / `p.*` in SQL. Always list only the fields actually needed by the mapper result.
### Security & Authentication
@@ -125,7 +126,7 @@ SysUser (entity) + UserCreateDTO (dto) + UserVO (vo)
### File Upload (MinIO + Deduplication)
**Location**: `com.leiyuwei.mhxy.business.service.impl.FileServiceImpl`
**Location**: `com.leiyuwei.mhxy.game.service.impl.FileServiceImpl`
**Deduplication flow**:
1. Calculate SHA-256 hash of uploaded file
@@ -140,7 +141,7 @@ SysUser (entity) + UserCreateDTO (dto) + UserVO (vo)
### Invitation Code System
**Location**: `com.leiyuwei.mhxy.business.service.InvitationCodeService`
**Location**: `com.leiyuwei.mhxy.game.service.InvitationCodeService`
Three formats supported (not yet implemented in code):
- RANDOM: Random string (X7K9M2P4)
@@ -211,7 +212,7 @@ Created by `V1.0.1__init_admin_data.sql`:
- `spring.datasource.*` - MySQL connection
- `minio.*` - MinIO endpoint, access key, secret key, bucket name
- `jwt.*` - Secret key, token expiration times
- `mybatis-plus.*` - ID type (auto), logical delete field (`deleted`)
- `mybatis-plus.*` - ID type (auto)
**Druid monitoring**: http://localhost:8080/druid/index.html (user: admin, password: admin)

View File

@@ -22,6 +22,27 @@ This file indexes all memory entries stored in the project's memory system.
Current session ID: 2026-05-27
### Completed Tasks
1. **Package Name Migration** - Fixed imports for RoleVO and FileUploadVO in controllers/services
2. **Created Missing Classes**:
- PermissionTreeVO - Permission tree structure VO
- SysPermissionService - Permission service interface
- SysPermissionServiceImpl - Permission service implementation with tree building logic
3. **Fixed PageVO.from() API** - Changed from incorrect MyBatis-Plus methods to correct ones
4. **MinioClient Refactoring**:
- Created MinioConfig with @Bean for MinioClient
- Updated FileServiceImpl to use dependency injection instead of creating client
- Added getMinioClient() method to FileService interface for Controller use
5. **Added Mapper Methods**:
- SysPermissionMapper.selectPermissionsByRoleId() - Query role permissions
- Created corresponding XML mapper
### Issues Resolved
- Fixed SysPermissionController imports (PermissionTreeVO and SysPermissionService)
- Fixed PageVO.from() to use correct IPage API methods
- Fixed FileServiceImpl to use injected MinioClient instead of creating instance
- Fixed FileServiceImpl.getPresignedUrl() to use minioConfig.getBucketName()
---
## Long-term Memory (Typed)
@@ -38,6 +59,12 @@ Current session ID: 2026-05-27
- Location: `AuthServiceImpl.java`
- BCrypt encoder configured in `SecurityConfig.passwordEncoder()`
- **MinIO Integration**:
- Created MinioConfig with @Bean for MinioClient
- FileServiceImpl uses constructor injection
- MinioClient injected via Bean, not created dynamically
- getMinioClient() exposed in FileService interface for Controller use
- **Missing TODOs**:
- `AuthServiceImpl.register()` - Invitation code validation
- `SysPermissionController.getMenus()` - User-specific menu tree
@@ -52,6 +79,6 @@ Current session ID: 2026-05-27
## Memory Stats
- Total entries: 3
- Memory size: ~3 KB
- Total entries: 4
- Memory size: ~4 KB
- Last updated: 2026-05-27

40
pom.xml
View File

@@ -27,6 +27,7 @@
<jwt.version>0.12.3</jwt.version>
<flyway.version>10.4.1</flyway.version>
<hutool.version>5.8.24</hutool.version>
<lombok.version>1.18.38</lombok.version>
</properties>
<dependencies>
@@ -49,7 +50,7 @@
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
@@ -94,14 +95,14 @@
</dependency>
<!-- Flyway 数据库迁移 -->
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.flywaydb</groupId>-->
<!-- <artifactId>flyway-core</artifactId>-->
<!-- </dependency>-->
<!-- <dependency>-->
<!-- <groupId>org.flywaydb</groupId>-->
<!-- <artifactId>flyway-mysql</artifactId>-->
<!-- </dependency>-->
<!-- Hutool 工具类 -->
<dependency>
@@ -110,11 +111,16 @@
<version>${hutool.version}</version>
</dependency>
<!-- Caffeine 高性能缓存 -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Security -->
@@ -140,6 +146,20 @@
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${java.version}</release>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>

21
sql/logback.xml Normal file
View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<!-- 控制台输出 -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- MyBatis SQL 日志 -->
<logger name="com.leiyuwei.mhxy.mapper" level="DEBUG"/>
<logger name="com.baomidou.mybatisplus" level="DEBUG"/>
<logger name="org.apache.ibatis" level="DEBUG"/>
<!-- 打印 SQL 参数(更详细) -->
<logger name="com.leiyuwei.mhxy.mapper" level="TRACE"/>
<root level="INFO">
<appender-ref ref="CONSOLE"/>
</root>
</configuration>

View File

@@ -3,11 +3,13 @@ package com.leiyuwei.mhxy;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* 梦幻西游管理后台 - 主启动类
*/
@SpringBootApplication
@EnableCaching
@MapperScan("com.leiyuwei.mhxy.**.mapper")
public class MhxyToolsApplication {

View File

@@ -0,0 +1,73 @@
package com.leiyuwei.mhxy.common.config;
import com.github.benmanes.caffeine.cache.Caffeine;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
/**
* Caffeine 缓存配置类
*/
@Slf4j
@Configuration
@EnableCaching
public class CacheConfig {
/**
* Caffeine 配置参数(可移到配置文件)
*/
@Value("${caffeine.initial-capacity:100}")
private int initialCapacity;
@Value("${caffeine.maximum-size:10000}")
private long maximumSize;
@Value("${caffeine.expire-minutes:30}")
private int expireMinutes;
/**
* 配置 Caffeine 缓存
*/
// @Bean(name = "caffeineCache")
// public Cache<String, Object> genericCache() {
// Cache<String, Object> cache = Caffeine.newBuilder()
// .initialCapacity(100)
// .maximumSize(10000)
// .expireAfterWrite(30, TimeUnit.MINUTES)
// .recordStats()
// .build();
//
// log.info("通用 Caffeine 缓存初始化完成 (initial=100, max=10000, TTL=15min)");
// return cache;
// }
/**
* 配置 Caffeine 缓存管理器
*/
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager(
"userPermissions",
"roles",
"permissions",
"default"
);
cacheManager.setCaffeine(Caffeine.newBuilder()
.initialCapacity(initialCapacity)
.maximumSize(maximumSize)
.expireAfterWrite(expireMinutes, TimeUnit.MINUTES)
.recordStats());
cacheManager.setAllowNullValues(false);
log.info("Caffeine 缓存管理器初始化完成,已注册缓存: userPermissions, roles, permissions, default");
return cacheManager;
}
}

View File

@@ -1,11 +1,9 @@
package com.leiyuwei.mhxy.common.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -14,7 +12,7 @@ import org.springframework.context.annotation.Configuration;
*/
@Slf4j
@Configuration
@MapperScan("com.leiyuwei.mhxy.mapper")
//@MapperScan("com.leiyuwei.mhxy.**.mapper")
public class MyBatisPlusConfig {
/**
@@ -31,8 +29,8 @@ public class MyBatisPlusConfig {
/**
* MyBatis-Plus 元数据填充处理器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new CustomMetaObjectHandler();
}
// @Bean
// public MetaObjectHandler metaObjectHandler() {
// return new CustomMetaObjectHandler();
// }
}

View File

@@ -1,9 +1,13 @@
package com.leiyuwei.mhxy.common.config;
import com.leiyuwei.mhxy.common.security.JwtAuthenticationFilter;
import com.leiyuwei.mhxy.system.service.impl.UserDetailsServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -13,6 +17,7 @@ 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;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
/**
* SpringSecurity 配置类
@@ -20,10 +25,12 @@ import org.springframework.security.web.authentication.UsernamePasswordAuthentic
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableCaching
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtAuthenticationFilter jwtAuthenticationFilter;
private final UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
@@ -33,33 +40,50 @@ public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF
.csrf(AbstractHttpConfigurer::disable)
// 禁用 CSRF
.csrf(AbstractHttpConfigurer::disable)
// 设置 Session 管理为无状态
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 设置 Session 管理为无状态
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// 配置权限规则
.authorizeHttpRequests(auth -> auth
// 认证相关接口开放
.requestMatchers("/api/auth/**").permitAll()
// Knife4j 文档接口开放
.requestMatchers("/doc.html", "/webjars/**", "/swagger-ui/**", "/swagger-resources/**").permitAll()
// 邀请码验证接口开放
.requestMatchers("/api/business/invitation-codes/validate").permitAll()
// Druid 监控接口开放
.requestMatchers("/druid/**").permitAll()
// 健康检查接口
.requestMatchers("/actuator/**").permitAll()
// 其他所有请求需要认证
.anyRequest().authenticated()
)
// 配置权限规则
.authorizeHttpRequests(auth -> auth
// 添加 JWT 过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
// 认证相关接口开放
.requestMatchers(AntPathRequestMatcher.antMatcher("/api/auth/**")).permitAll()
// Knife4j 文档接口开放
.requestMatchers(AntPathRequestMatcher.antMatcher("/webjars/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/doc.html")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger-ui/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/v3/api-docs/**")).permitAll()
.requestMatchers(AntPathRequestMatcher.antMatcher("/swagger-resources/**")).permitAll()
// 邀请码验证接口开放
.requestMatchers(AntPathRequestMatcher.antMatcher("/api/business/invitation-codes/validate")).permitAll()
// Druid 监控接口开放
.requestMatchers(AntPathRequestMatcher.antMatcher("/druid/**")).permitAll()
// 健康检查接口
.requestMatchers(AntPathRequestMatcher.antMatcher("/actuator/**")).permitAll()
// 其他所有请求需要认证
.anyRequest().authenticated()
)
// 添加 JWT 过滤器
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
// 配置 AuthenticationManager
.authenticationProvider(authenticationProvider());
return http.build();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
}

View File

@@ -1,6 +1,9 @@
package com.leiyuwei.mhxy.common.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.time.LocalDateTime;
@@ -14,7 +17,7 @@ public class BaseEntity {
/**
* 主键 ID
*/
@TableId(type = IdType.AUTO)
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
@@ -32,11 +35,12 @@ public class BaseEntity {
/**
* 逻辑删除字段
*/
@TableLogic
private Integer deleted;
// @TableField
// private Integer deleted;
/**
* 乐观锁版本号
*/
@TableField(exist = false)
private Integer version;
}

View File

@@ -28,17 +28,17 @@ public class Result<T> {
/**
* 时间戳
*/
private Long timestamp;
//private Long timestamp;
private Result() {
this.timestamp = System.currentTimeMillis();
//this.timestamp = System.currentTimeMillis();
}
private Result(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
this.timestamp = System.currentTimeMillis();
//this.timestamp = System.currentTimeMillis();
}
/**

View File

@@ -1,6 +1,7 @@
package com.leiyuwei.mhxy.common.security;
import com.leiyuwei.mhxy.common.util.JwtUtil;
import io.jsonwebtoken.Claims;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
@@ -8,22 +9,29 @@ import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.Collections;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
/**
* JWT 认证过滤器
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private static final String BEARER_PREFIX = "Bearer ";
private final JwtUtil jwtUtil;
@Override
@@ -33,21 +41,20 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
try {
String token = getTokenFromRequest(request);
if (StringUtils.hasText(token) && jwtUtil.validateToken(token)) {
if (StringUtils.hasText(token)
&& SecurityContextHolder.getContext().getAuthentication() == null
&& jwtUtil.validateToken(token)) {
Long userId = jwtUtil.getUserIdFromToken(token);
String username = jwtUtil.getUsernameFromToken(token);
Claims claims = jwtUtil.getClaimsFromToken(token);
Collection<? extends GrantedAuthority> authorities = buildAuthorities(claims);
// 设置 Spring Security 认证信息
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
username,
null,
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
);
new UsernamePasswordAuthenticationToken(username, null, authorities);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
log.debug("设置用户认证信息: userId={}, username={}", userId, username);
log.debug("设置用户认证信息: userId={}, username={}, authorities={}", userId, username, authorities);
}
} catch (Exception e) {
log.error("无法设置用户认证: {}", e.getMessage());
@@ -56,14 +63,50 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
filterChain.doFilter(request, response);
}
private Collection<? extends GrantedAuthority> buildAuthorities(Claims claims) {
LinkedHashSet<String> authorityCodes = new LinkedHashSet<>();
authorityCodes.addAll(extractStringList(claims.get("roleCodes")));
authorityCodes.addAll(extractStringList(claims.get("permissionCodes")));
return authorityCodes.stream()
.map(SimpleGrantedAuthority::new)
.toList();
}
private List<String> extractStringList(Object claimValue) {
if (!(claimValue instanceof List<?> values)) {
return List.of();
}
return values.stream()
.filter(String.class::isInstance)
.map(String.class::cast)
.filter(StringUtils::hasText)
.toList();
}
/**
* 从请求头获取 Token
*/
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
String authorization = request.getHeader("Authorization");
if (StringUtils.hasText(authorization)) {
if (authorization.regionMatches(true, 0, BEARER_PREFIX, 0, BEARER_PREFIX.length())) {
return authorization.substring(BEARER_PREFIX.length()).trim();
}
return authorization.trim();
}
String token = request.getHeader("token");
if (StringUtils.hasText(token)) {
return token.trim();
}
String accessToken = request.getParameter("accessToken");
if (StringUtils.hasText(accessToken)) {
return accessToken.trim();
}
return null;
}
}

View File

@@ -71,7 +71,7 @@ public class JwtUtil {
/**
* 从 Token 中获取 Claims
*/
private Claims getClaimsFromToken(String token) {
public Claims getClaimsFromToken(String token) {
return Jwts.parser()
.verifyWith(getSignKey())
.build()

View File

@@ -0,0 +1,70 @@
package com.leiyuwei.mhxy.game.controller;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.AccountCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.AccountQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.AccountUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.AccountVO;
import com.leiyuwei.mhxy.game.service.AccountService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/game/accounts")
@RequiredArgsConstructor
@Tag(name = "游戏账号管理", description = "游戏账号增删改查")
public class AccountController {
private final AccountService accountService;
@GetMapping("/list")
@Operation(summary = "分页查询账号")
@PreAuthorize("hasAuthority('game:account:list')")
public Result<PageVO<AccountVO>> getAccountList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String accountName,
@RequestParam(required = false) Long userId) {
AccountQueryDTO dto = new AccountQueryDTO();
dto.setPageNum(page);
dto.setPageSize(size);
dto.setAccountName(accountName);
dto.setUserId(userId);
return Result.success(accountService.getAccountList(dto));
}
@GetMapping("/detail/{id}")
@Operation(summary = "查询账号详情")
@PreAuthorize("hasAuthority('game:account:view')")
public Result<AccountVO> getAccountById(@PathVariable Long id) {
return Result.success(accountService.getAccountById(id));
}
@PostMapping("/add")
@Operation(summary = "新增账号")
@PreAuthorize("hasAuthority('game:account:add')")
public Result<AccountVO> createAccount(@Valid @RequestBody AccountCreateDTO dto) {
return Result.success(accountService.createAccount(dto));
}
@PutMapping("/edit/{id}")
@Operation(summary = "编辑账号")
@PreAuthorize("hasAuthority('game:account:edit')")
public Result<Void> updateAccount(@PathVariable Long id, @Valid @RequestBody AccountUpdateDTO dto) {
accountService.updateAccount(id, dto);
return Result.success();
}
@DeleteMapping("/remove/{id}")
@Operation(summary = "删除账号")
@PreAuthorize("hasAuthority('game:account:delete')")
public Result<Void> deleteAccount(@PathVariable Long id) {
accountService.deleteAccount(id);
return Result.success();
}
}

View File

@@ -0,0 +1,70 @@
package com.leiyuwei.mhxy.game.controller;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.CharacterImageVO;
import com.leiyuwei.mhxy.game.service.CharacterImageService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/game/character-images")
@RequiredArgsConstructor
@Tag(name = "角色图片管理", description = "角色图片增删改查")
public class CharacterImageController {
private final CharacterImageService characterImageService;
@GetMapping("/list")
@Operation(summary = "分页查询角色图片")
@PreAuthorize("hasAuthority('game:character:list')")
public Result<PageVO<CharacterImageVO>> getCharacterImageList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) Long characterId,
@RequestParam(required = false) String fileHash) {
CharacterImageQueryDTO dto = new CharacterImageQueryDTO();
dto.setPageNum(page);
dto.setPageSize(size);
dto.setCharacterId(characterId);
dto.setFileHash(fileHash);
return Result.success(characterImageService.getCharacterImageList(dto));
}
@GetMapping("/detail/{id}")
@Operation(summary = "查询角色图片详情")
@PreAuthorize("hasAuthority('game:character:image:view')")
public Result<CharacterImageVO> getCharacterImageById(@PathVariable Long id) {
return Result.success(characterImageService.getCharacterImageById(id));
}
@PostMapping("/add")
@Operation(summary = "新增角色图片")
@PreAuthorize("hasAuthority('game:character:image:add')")
public Result<CharacterImageVO> createCharacterImage(@Valid @RequestBody CharacterImageCreateDTO dto) {
return Result.success(characterImageService.createCharacterImage(dto));
}
@PutMapping("/edit/{id}")
@Operation(summary = "编辑角色图片")
@PreAuthorize("hasAuthority('game:character:image:add')")
public Result<Void> updateCharacterImage(@PathVariable Long id, @Valid @RequestBody CharacterImageUpdateDTO dto) {
characterImageService.updateCharacterImage(id, dto);
return Result.success();
}
@DeleteMapping("/remove/{id}")
@Operation(summary = "删除角色图片")
@PreAuthorize("hasAuthority('game:character:image:delete')")
public Result<Void> deleteCharacterImage(@PathVariable Long id) {
characterImageService.deleteCharacterImage(id);
return Result.success();
}
}

View File

@@ -0,0 +1,73 @@
package com.leiyuwei.mhxy.game.controller;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.GameCharacterKeywordVO;
import com.leiyuwei.mhxy.game.service.GameCharacterKeywordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/game/character-keywords")
@RequiredArgsConstructor
@Tag(name = "角色关键字管理", description = "角色关键字增删改查")
public class CharacterKeywordController {
private final GameCharacterKeywordService characterKeywordService;
@GetMapping("/list")
@Operation(summary = "分页查询关键字")
@PreAuthorize("hasAuthority('game:characterKeyword:list')")
public Result<PageVO<GameCharacterKeywordVO>> getCharacterKeywordList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) Long characterId,
@RequestParam(required = false) String keyword) {
GameCharacterKeywordQueryDTO dto = new GameCharacterKeywordQueryDTO();
dto.setPageNum(page);
dto.setPageSize(size);
dto.setCharacterId(characterId);
dto.setKeyword(keyword);
return Result.success(characterKeywordService.getCharacterKeywordList(dto));
}
@GetMapping("/detail/{id}")
@Operation(summary = "查询关键字详情")
@PreAuthorize("hasAuthority('game:characterKeyword:view')")
public Result<GameCharacterKeywordVO> getCharacterKeywordById(@PathVariable Long id) {
return Result.success(characterKeywordService.getCharacterKeywordById(id));
}
@PostMapping("/add")
@Operation(summary = "新增关键字")
@PreAuthorize("hasAuthority('game:characterKeyword:add')")
public Result<GameCharacterKeywordVO> createCharacterKeyword(@Valid @RequestBody GameCharacterKeywordCreateDTO dto) {
return Result.success(characterKeywordService.createCharacterKeyword(dto));
}
@PutMapping("/edit/{id}")
@Operation(summary = "编辑关键字")
@PreAuthorize("hasAuthority('game:characterKeyword:edit')")
public Result<Void> updateCharacterKeyword(@PathVariable Long id, @Valid @RequestBody GameCharacterKeywordUpdateDTO dto) {
characterKeywordService.updateCharacterKeyword(id, dto);
return Result.success();
}
@DeleteMapping("/remove/{id}")
@Operation(summary = "删除关键字")
@PreAuthorize("hasAuthority('game:characterKeyword:delete')")
public Result<Void> deleteCharacterKeyword(@PathVariable Long id) {
characterKeywordService.deleteCharacterKeyword(id);
return Result.success();
}
}

View File

@@ -1,13 +1,15 @@
package com.leiyuwei.mhxy.business.controller;
package com.leiyuwei.mhxy.game.controller;
import com.leiyuwei.mhxy.business.model.vo.FileUploadVO;
import com.leiyuwei.mhxy.business.service.FileService;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.vo.FileUploadVO;
import com.leiyuwei.mhxy.game.service.FileService;
import io.minio.GetObjectArgs;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -16,26 +18,22 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 文件管理控制器
*/
@RestController
@RequestMapping("/api/business/files")
@RequestMapping({"/api/business/files", "/api/game/files"})
@RequiredArgsConstructor
@Tag(name = "文件管理", description = "文件上传、下载、删除")
public class FileController {
private final FileService fileService;
/**
* 上传文件
*/
@PostMapping("/upload")
@Operation(summary = "上传文件", description = "上传文件到 MinIO自动去重")
@PreAuthorize("hasAuthority('game:file:add')")
public Result<Map<String, Object>> uploadFile(@RequestParam("file") MultipartFile file) {
FileUploadVO uploadVO = fileService.uploadFile(file);
Map<String, Object> result = new HashMap<>();
result.put("id", uploadVO.getId());
result.put("fileHash", uploadVO.getFileHash());
result.put("fileName", uploadVO.getObjectName());
result.put("fileSize", uploadVO.getFileSize());
@@ -45,33 +43,25 @@ public class FileController {
return Result.success(result);
}
/**
* 批量上传文件
*/
@PostMapping("/upload/batch")
@Operation(summary = "批量上传文件", description = "批量上传文件到 MinIO")
@PreAuthorize("hasAuthority('game:file:add')")
public Result<List<FileUploadVO>> uploadFiles(@RequestParam("files") List<MultipartFile> files) {
List<FileUploadVO> result = fileService.uploadFiles(files);
return Result.success(result);
}
/**
* 下载文件
*/
@GetMapping("/download/{fileHash}")
@Operation(summary = "下载文件", description = "下载指定文件")
@PreAuthorize("hasAuthority('game:file:view')")
public void downloadFile(@PathVariable String fileHash,
HttpServletResponse response) {
HttpServletResponse response) {
try {
FileUploadVO fileVO = fileService.getFileByHash(fileHash);
// 设置响应头
response.setContentType(fileVO.getContentType());
response.setContentLength(fileVO.getFileSize().intValue());
response.setHeader("Content-Disposition",
"attachment; filename=\"" + fileVO.getObjectName() + "\"");
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileVO.getObjectName() + "\"");
// MinIO 下载文件
InputStream inputStream = fileService.getMinioClient().getObject(
GetObjectArgs.builder()
.bucket("mhxy-files")
@@ -79,40 +69,49 @@ public class FileController {
.build()
);
// 写入响应流
inputStream.transferTo(response.getOutputStream());
response.getOutputStream().flush();
} catch (Exception e) {
throw new RuntimeException("文件下载失败: " + e.getMessage());
}
}
/**
* 删除文件
*/
@DeleteMapping("/{fileHash}")
@Operation(summary = "删除文件", description = "删除文件(引用计数-1为0时才真正删除")
@PreAuthorize("hasAuthority('game:file:delete')")
public Result<Void> deleteFile(@PathVariable String fileHash) {
fileService.deleteFile(fileHash);
return Result.success("文件删除成功", null);
}
/**
* 获取文件信息
*/
@GetMapping("/{fileHash}")
@Operation(summary = "获取文件信息", description = "根据 fileHash 获取文件详细信息")
@PreAuthorize("hasAuthority('game:file:view')")
public Result<FileUploadVO> getFileByHash(@PathVariable String fileHash) {
FileUploadVO fileVO = fileService.getFileByHash(fileHash);
return Result.success(fileVO);
}
/**
* 生成预签名 URL
*/
@GetMapping("/detail/{id}")
@Operation(summary = "按ID获取文件信息")
@PreAuthorize("hasAuthority('game:file:view')")
public Result<FileUploadVO> getFileById(@PathVariable Long id) {
return Result.success(fileService.getFileById(id));
}
@GetMapping("/list")
@Operation(summary = "分页查询文件")
@PreAuthorize("hasAuthority('game:file:list')")
public Result<PageVO<FileUploadVO>> getFileList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String fileHash) {
return Result.success(fileService.getFileList(page, size, fileHash));
}
@GetMapping("/preview/{fileHash}")
@Operation(summary = "生成预签名 URL", description = "生成文件预签名的临时访问 URL")
@PreAuthorize("hasAuthority('game:file:view')")
public Result<String> generatePresignedUrl(
@PathVariable String fileHash,
@RequestParam(defaultValue = "30") Integer expirationMinutes) {

View File

@@ -0,0 +1,73 @@
package com.leiyuwei.mhxy.game.controller;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.CharacterCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.CharacterVO;
import com.leiyuwei.mhxy.game.service.GameCharacterService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/game/characters")
@RequiredArgsConstructor
@Tag(name = "游戏角色管理", description = "游戏角色增删改查")
public class GameCharacterController {
private final GameCharacterService characterService;
@GetMapping("/list")
@Operation(summary = "分页查询角色")
@PreAuthorize("hasAuthority('game:character:list')")
public Result<PageVO<CharacterVO>> getCharacterList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String characterName,
@RequestParam(required = false) Long accountId,
@RequestParam(required = false) Long serverId) {
CharacterQueryDTO dto = new CharacterQueryDTO();
dto.setPageNum(page);
dto.setPageSize(size);
dto.setCharacterName(characterName);
dto.setAccountId(accountId);
dto.setServerId(serverId);
return Result.success(characterService.getCharacterList(dto));
}
@GetMapping("/detail/{id}")
@Operation(summary = "查询角色详情")
@PreAuthorize("hasAuthority('game:character:view')")
public Result<CharacterVO> getCharacterById(@PathVariable Long id) {
return Result.success(characterService.getCharacterById(id));
}
@PostMapping("/add")
@Operation(summary = "新增角色")
@PreAuthorize("hasAuthority('game:character:add')")
public Result<CharacterVO> createCharacter(@Valid @RequestBody CharacterCreateDTO dto) {
return Result.success(characterService.createCharacter(dto));
}
@PutMapping("/edit/{id}")
@Operation(summary = "编辑角色")
@PreAuthorize("hasAuthority('game:character:edit')")
public Result<Void> updateCharacter(@PathVariable Long id, @Valid @RequestBody CharacterUpdateDTO dto) {
characterService.updateCharacter(id, dto);
return Result.success();
}
@DeleteMapping("/remove/{id}")
@Operation(summary = "删除角色")
@PreAuthorize("hasAuthority('game:character:delete')")
public Result<Void> deleteCharacter(@PathVariable Long id) {
characterService.deleteCharacter(id);
return Result.success();
}
}

View File

@@ -0,0 +1,80 @@
package com.leiyuwei.mhxy.game.controller;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.ServerCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.ServerQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.ServerUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.ServerVO;
import com.leiyuwei.mhxy.game.service.ServerService;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
/**
* 游戏服务器管理控制器
*/
@RestController
@RequestMapping("/api/game/servers")
@RequiredArgsConstructor
public class ServerController {
private final ServerService serverService;
/**
* 分页查询服务器列表
*/
@GetMapping("/list")
@PreAuthorize("hasAuthority('game:server:list')")
public Result<PageVO<ServerVO>> getServerList(
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String keyword){
ServerQueryDTO dto = new ServerQueryDTO();
dto.setPageNum(page);
dto.setPageSize(size);
dto.setKeyword(keyword);
PageVO<ServerVO> serverPage = serverService.getServerList(dto);
return Result.success(serverPage);
}
/**
* 根据ID查询服务器详情
*/
@GetMapping("/detail/{id}")
@PreAuthorize("hasAuthority('game:server:view')")
public Result<ServerVO> getServerById(@PathVariable Long id) {
ServerVO vo = serverService.getServerById(id);
return Result.success(vo);
}
/**
* 创建新服务器
*/
@PostMapping("/add")
@PreAuthorize("hasAuthority('game:server:add')")
public Result<ServerVO> createServer(@RequestBody ServerCreateDTO dto) {
ServerVO vo = serverService.createServer(dto);
return Result.success(vo);
}
/**
* 更新服务器信息
*/
@PutMapping("/edit/{id}")
@PreAuthorize("hasAuthority('game:server:edit')")
public Result<Void> updateServer(@PathVariable Long id, @RequestBody ServerUpdateDTO dto) {
serverService.updateServer(id, dto);
return Result.success();
}
/**
* 删除服务器
*/
@DeleteMapping("/remove/{id}")
@PreAuthorize("hasAuthority('game:server:delete')")
public Result<Void> deleteServer(@PathVariable Long id) {
serverService.deleteServer(id);
return Result.success();
}
}

View File

@@ -0,0 +1,9 @@
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.game.model.entity.Account;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface AccountMapper extends BaseMapper<Account> {
}

View File

@@ -0,0 +1,9 @@
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.game.model.entity.GameCharacterKeyword;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CharacterKeywordMapper extends BaseMapper<GameCharacterKeyword> {
}

View File

@@ -1,10 +1,9 @@
package com.leiyuwei.mhxy.business.mapper;
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.business.model.entity.FileHashReference;
import com.leiyuwei.mhxy.game.model.entity.FileHashReference;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* 文件 Hash 引用 Mapper
@@ -15,6 +14,5 @@ public interface FileHashReferenceMapper extends BaseMapper<FileHashReference> {
/**
* 按对象名称查询文件
*/
@Select("SELECT * FROM file_hash_reference WHERE object_name = #{objectName}")
FileHashReference selectByObjectName(@Param("objectName") String objectName);
}

View File

@@ -0,0 +1,9 @@
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.game.model.entity.GameCharacterImage;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface GameCharacterImageMapper extends BaseMapper<GameCharacterImage> {
}

View File

@@ -0,0 +1,9 @@
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.game.model.entity.GameCharacter;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface GameCharacterMapper extends BaseMapper<GameCharacter> {
}

View File

@@ -0,0 +1,18 @@
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.game.model.entity.InvitationCode;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* 邀请码 Mapper最小实现
*/
@Mapper
public interface InvitationCodeMapper extends BaseMapper<InvitationCode> {
/**
* 根据邀请码查询
*/
InvitationCode selectByCode(@Param("code") String code);
}

View File

@@ -0,0 +1,9 @@
package com.leiyuwei.mhxy.game.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.game.model.entity.Server;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface ServerMapper extends BaseMapper<Server> {
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class AccountCreateDTO {
@NotBlank(message = "账号名称不能为空")
@Size(max = 50, message = "账号名称长度不能超过 50")
private String accountName;
@NotNull(message = "用户ID不能为空")
private Long userId;
}

View File

@@ -0,0 +1,13 @@
package com.leiyuwei.mhxy.game.model.dto;
import com.leiyuwei.mhxy.common.dto.PageDTO;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class AccountQueryDTO extends PageDTO {
@Size(max = 50, message = "账号名称长度不能超过 50")
private String accountName;
private Long userId;
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class AccountUpdateDTO {
@NotBlank(message = "账号名称不能为空")
@Size(max = 50, message = "账号名称长度不能超过 50")
private String accountName;
@NotNull(message = "用户ID不能为空")
private Long userId;
}

View File

@@ -0,0 +1,19 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CharacterCreateDTO {
@NotBlank(message = "角色名称不能为空")
@Size(max = 50, message = "角色名称长度不能超过 50")
private String characterName;
@NotNull(message = "账号ID不能为空")
private Long accountId;
@NotNull(message = "服务器ID不能为空")
private Long serverId;
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CharacterImageCreateDTO {
@NotNull(message = "角色ID不能为空")
private Long characterId;
@Size(max = 64, message = "文件哈希长度不能超过 64")
private String fileHash;
private Long fileId;
}

View File

@@ -0,0 +1,13 @@
package com.leiyuwei.mhxy.game.model.dto;
import com.leiyuwei.mhxy.common.dto.PageDTO;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CharacterImageQueryDTO extends PageDTO {
private Long characterId;
@Size(max = 64, message = "文件哈希长度不能超过 64")
private String fileHash;
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CharacterImageUpdateDTO {
@NotNull(message = "角色ID不能为空")
private Long characterId;
@Size(max = 64, message = "文件哈希长度不能超过 64")
private String fileHash;
private Long fileId;
}

View File

@@ -0,0 +1,14 @@
package com.leiyuwei.mhxy.game.model.dto;
import com.leiyuwei.mhxy.common.dto.PageDTO;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CharacterQueryDTO extends PageDTO {
@Size(max = 50, message = "角色名称长度不能超过 50")
private String characterName;
private Long accountId;
private Long serverId;
}

View File

@@ -0,0 +1,19 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class CharacterUpdateDTO {
@NotBlank(message = "角色名称不能为空")
@Size(max = 50, message = "角色名称长度不能超过 50")
private String characterName;
@NotNull(message = "账号ID不能为空")
private Long accountId;
@NotNull(message = "服务器ID不能为空")
private Long serverId;
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class GameCharacterKeywordCreateDTO {
@NotBlank(message = "关键字不能为空")
@Size(max = 100, message = "关键字长度不能超过 100")
private String keyword;
@NotNull(message = "角色ID不能为空")
private Long characterId;
}

View File

@@ -0,0 +1,13 @@
package com.leiyuwei.mhxy.game.model.dto;
import com.leiyuwei.mhxy.common.dto.PageDTO;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class GameCharacterKeywordQueryDTO extends PageDTO {
private Long characterId;
@Size(max = 100, message = "关键字长度不能超过 100")
private String keyword;
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class GameCharacterKeywordUpdateDTO {
@NotBlank(message = "关键字不能为空")
@Size(max = 100, message = "关键字长度不能超过 100")
private String keyword;
@NotNull(message = "角色ID不能为空")
private Long characterId;
}

View File

@@ -0,0 +1,18 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ServerCreateDTO {
@NotBlank(message = "区服名称不能为空")
@Size(max = 50, message = "区服名称长度不能超过 50")
private String serverName;
@Size(max = 200, message = "描述长度不能超过 200")
private String description;
private Boolean enabled = true;
private Integer sort = 0;
}

View File

@@ -0,0 +1,17 @@
package com.leiyuwei.mhxy.game.model.dto;
import com.leiyuwei.mhxy.common.dto.PageDTO;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ServerQueryDTO extends PageDTO {
@Schema(title = "服务器名称")
@Size(max = 50, message = "区服名称长度不能超过 50")
private String serverName;
private Boolean enabled;
private String keyword;
}

View File

@@ -0,0 +1,18 @@
package com.leiyuwei.mhxy.game.model.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Data
public class ServerUpdateDTO {
@NotBlank(message = "区服名称不能为空")
@Size(max = 50, message = "区服名称长度不能超过 50")
private String serverName;
@Size(max = 200, message = "描述长度不能超过 200")
private String description;
private Boolean enabled;
private Integer sort;
}

View File

@@ -0,0 +1,15 @@
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.leiyuwei.mhxy.common.entity.BaseEntity;
import lombok.Data;
/**
* 游戏账号实体,对应 `account` 表。
*/
@Data
@TableName("account")
public class Account extends BaseEntity {
private String accountName;
private Long userId; // 关联 sys_user.id可为空
}

View File

@@ -1,4 +1,4 @@
package com.leiyuwei.mhxy.business.model.entity;
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.leiyuwei.mhxy.common.entity.BaseEntity;

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.leiyuwei.mhxy.common.entity.BaseEntity;
import lombok.Data;
/**
* 游戏角色实体,对应 `character` 表。
*/
@Data
@TableName("game_character")
public class GameCharacter extends BaseEntity {
private String characterName;
private Long accountId;
private Long serverId;
}

View File

@@ -0,0 +1,15 @@
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.leiyuwei.mhxy.common.entity.BaseEntity;
import lombok.Data;
/**
* 角色图片关联实体,对应 `game_character_image` 表。
*/
@Data
@TableName("game_character_image")
public class GameCharacterImage extends BaseEntity {
private String fileHash;
private Long gameCharacterId;
}

View File

@@ -0,0 +1,15 @@
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.leiyuwei.mhxy.common.entity.BaseEntity;
import lombok.Data;
/**
* 角色关键字实体,对应 `game_character_keyword` 表。
*/
@Data
@TableName("game_character_keyword")
public class GameCharacterKeyword extends BaseEntity {
private String keyword;
private Long gameCharacterId;
}

View File

@@ -0,0 +1,42 @@
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import com.leiyuwei.mhxy.common.entity.BaseEntity;
import lombok.Data;
/**
* 邀请码实体(最小实现)
*/
@Data
@TableName("invitation_code")
public class InvitationCode extends BaseEntity {
/**
* 邀请码唯一字符串
*/
private String code;
/**
* 最大使用次数
*/
private Integer maxUsage;
/**
* 已使用次数
*/
private Integer usedCount;
/**
* 失效时间戳(毫秒),为空表示永不过期
*/
private Long expireTime;
/**
* 是否启用1 启用0 禁用)
*/
private Integer enabled;
/**
* 是否已删除0 未删1 已删)
*/
private Integer deleted;
}

View File

@@ -0,0 +1,28 @@
package com.leiyuwei.mhxy.game.model.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.leiyuwei.mhxy.common.entity.BaseEntity;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class Server extends BaseEntity {
@TableId(type = IdType.ASSIGN_ID)
private Long id;
private String serverName;
private String description;
// private Boolean enabled = true;
// private Integer sort = 0;
// private Integer deleted = 0; // 逻辑删除字段
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,14 @@
package com.leiyuwei.mhxy.game.model.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class AccountVO {
private Long id;
private String accountName;
private Long userId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,14 @@
package com.leiyuwei.mhxy.game.model.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class CharacterImageVO {
private Long id;
private String fileHash;
private Long characterId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,15 @@
package com.leiyuwei.mhxy.game.model.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class CharacterVO {
private Long id;
private String characterName;
private Long accountId;
private Long serverId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -1,4 +1,4 @@
package com.leiyuwei.mhxy.business.model.vo;
package com.leiyuwei.mhxy.game.model.vo;
import lombok.Data;

View File

@@ -0,0 +1,14 @@
package com.leiyuwei.mhxy.game.model.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class GameCharacterKeywordVO {
private Long id;
private String keyword;
private Long characterId;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,16 @@
package com.leiyuwei.mhxy.game.model.vo;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class ServerVO {
private Long id;
private String serverName;
private String description;
private Boolean enabled;
private Integer sort;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,19 @@
package com.leiyuwei.mhxy.game.service;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.AccountCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.AccountQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.AccountUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.AccountVO;
public interface AccountService {
PageVO<AccountVO> getAccountList(AccountQueryDTO query);
AccountVO getAccountById(Long id);
AccountVO createAccount(AccountCreateDTO dto);
AccountVO updateAccount(Long id, AccountUpdateDTO dto);
void deleteAccount(Long id);
}

View File

@@ -0,0 +1,19 @@
package com.leiyuwei.mhxy.game.service;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.CharacterImageVO;
public interface CharacterImageService {
PageVO<CharacterImageVO> getCharacterImageList(CharacterImageQueryDTO query);
CharacterImageVO getCharacterImageById(Long id);
CharacterImageVO createCharacterImage(CharacterImageCreateDTO dto);
CharacterImageVO updateCharacterImage(Long id, CharacterImageUpdateDTO dto);
void deleteCharacterImage(Long id);
}

View File

@@ -1,6 +1,7 @@
package com.leiyuwei.mhxy.business.service;
package com.leiyuwei.mhxy.game.service;
import com.leiyuwei.mhxy.business.model.vo.FileUploadVO;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.vo.FileUploadVO;
import io.minio.MinioClient;
import org.springframework.web.multipart.MultipartFile;
@@ -36,6 +37,26 @@ public interface FileService {
*/
FileUploadVO getFileByHash(String fileHash);
/**
* 根据ID获取文件信息
*/
FileUploadVO getFileById(Long id);
/**
* 分页查询文件
*/
PageVO<FileUploadVO> getFileList(Integer pageNum, Integer pageSize, String fileHash);
/**
* 文件引用计数 +1
*/
void increaseReferenceByHash(String fileHash);
/**
* 文件引用计数 -1
*/
void decreaseReferenceByHash(String fileHash);
/**
* 生成临时访问 URL
*/

View File

@@ -0,0 +1,19 @@
package com.leiyuwei.mhxy.game.service;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.GameCharacterKeywordVO;
public interface GameCharacterKeywordService {
PageVO<GameCharacterKeywordVO> getCharacterKeywordList(GameCharacterKeywordQueryDTO query);
GameCharacterKeywordVO getCharacterKeywordById(Long id);
GameCharacterKeywordVO createCharacterKeyword(GameCharacterKeywordCreateDTO dto);
GameCharacterKeywordVO updateCharacterKeyword(Long id, GameCharacterKeywordUpdateDTO dto);
void deleteCharacterKeyword(Long id);
}

View File

@@ -0,0 +1,19 @@
package com.leiyuwei.mhxy.game.service;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.CharacterCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.CharacterVO;
public interface GameCharacterService {
PageVO<CharacterVO> getCharacterList(CharacterQueryDTO query);
CharacterVO getCharacterById(Long id);
CharacterVO createCharacter(CharacterCreateDTO dto);
CharacterVO updateCharacter(Long id, CharacterUpdateDTO dto);
void deleteCharacter(Long id);
}

View File

@@ -0,0 +1,34 @@
package com.leiyuwei.mhxy.game.service;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.model.dto.ServerCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.ServerQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.ServerUpdateDTO;
import com.leiyuwei.mhxy.game.model.vo.ServerVO;
public interface ServerService {
/**
* 分页查询服务器列表
*/
PageVO<ServerVO> getServerList(ServerQueryDTO query);
/**
* 创建服务器
*/
ServerVO createServer(ServerCreateDTO dto);
/**
* 根据ID更新服务器
*/
ServerVO updateServer(Long id, ServerUpdateDTO dto);
/**
* 根据ID删除服务器
*/
void deleteServer(Long id);
/**
* 根据ID查询详情
*/
ServerVO getServerById(Long id);
}

View File

@@ -0,0 +1,132 @@
package com.leiyuwei.mhxy.game.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.mapper.AccountMapper;
import com.leiyuwei.mhxy.game.mapper.GameCharacterMapper;
import com.leiyuwei.mhxy.game.model.dto.AccountCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.AccountQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.AccountUpdateDTO;
import com.leiyuwei.mhxy.game.model.entity.Account;
import com.leiyuwei.mhxy.game.model.entity.GameCharacter;
import com.leiyuwei.mhxy.game.model.vo.AccountVO;
import com.leiyuwei.mhxy.game.service.AccountService;
import com.leiyuwei.mhxy.system.mapper.SysUserMapper;
import com.leiyuwei.mhxy.system.model.entity.SysUser;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
@RequiredArgsConstructor
public class AccountServiceImpl implements AccountService {
private final AccountMapper accountMapper;
private final GameCharacterMapper characterMapper;
private final SysUserMapper sysUserMapper;
@Override
public PageVO<AccountVO> getAccountList(AccountQueryDTO query) {
LambdaQueryWrapper<Account> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(query.getAccountName())) {
wrapper.like(Account::getAccountName, query.getAccountName());
}
if (query.getUserId() != null) {
wrapper.eq(Account::getUserId, query.getUserId());
}
wrapper.orderByDesc(Account::getId);
IPage<Account> page = accountMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()), wrapper);
IPage<AccountVO> voPage = page.convert(this::toVO);
return PageVO.from(voPage);
}
@Override
public AccountVO getAccountById(Long id) {
Account account = accountMapper.selectById(id);
if (account == null) {
throw new BusinessException("游戏账号不存在");
}
return toVO(account);
}
@Override
@Transactional(rollbackFor = Exception.class)
public AccountVO createAccount(AccountCreateDTO dto) {
checkUserExists(dto.getUserId());
checkNameDuplicate(null, dto.getAccountName());
Account account = new Account();
account.setAccountName(dto.getAccountName());
account.setUserId(dto.getUserId());
accountMapper.insert(account);
return toVO(account);
}
@Override
@Transactional(rollbackFor = Exception.class)
public AccountVO updateAccount(Long id, AccountUpdateDTO dto) {
Account account = accountMapper.selectById(id);
if (account == null) {
throw new BusinessException("游戏账号不存在");
}
checkUserExists(dto.getUserId());
checkNameDuplicate(id, dto.getAccountName());
account.setAccountName(dto.getAccountName());
account.setUserId(dto.getUserId());
accountMapper.updateById(account);
return toVO(account);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteAccount(Long id) {
Account account = accountMapper.selectById(id);
if (account == null) {
throw new BusinessException("游戏账号不存在");
}
Long linkedCharacterCount = characterMapper.selectCount(
new LambdaQueryWrapper<GameCharacter>().eq(GameCharacter::getAccountId, id)
);
if (linkedCharacterCount != null && linkedCharacterCount > 0) {
throw new BusinessException("该账号下存在角色,无法删除");
}
accountMapper.deleteById(id);
}
private void checkUserExists(Long userId) {
SysUser user = sysUserMapper.selectById(userId);
if (user == null) {
throw new BusinessException("关联用户不存在");
}
}
private void checkNameDuplicate(Long selfId, String accountName) {
LambdaQueryWrapper<Account> wrapper = new LambdaQueryWrapper<Account>()
.eq(Account::getAccountName, accountName);
if (selfId != null) {
wrapper.ne(Account::getId, selfId);
}
Long count = accountMapper.selectCount(wrapper);
if (count != null && count > 0) {
throw new BusinessException("账号名称已存在");
}
}
private AccountVO toVO(Account entity) {
AccountVO vo = new AccountVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}
}

View File

@@ -0,0 +1,132 @@
package com.leiyuwei.mhxy.game.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.mapper.GameCharacterImageMapper;
import com.leiyuwei.mhxy.game.mapper.GameCharacterMapper;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterImageUpdateDTO;
import com.leiyuwei.mhxy.game.model.entity.GameCharacter;
import com.leiyuwei.mhxy.game.model.entity.GameCharacterImage;
import com.leiyuwei.mhxy.game.model.vo.CharacterImageVO;
import com.leiyuwei.mhxy.game.model.vo.FileUploadVO;
import com.leiyuwei.mhxy.game.service.CharacterImageService;
import com.leiyuwei.mhxy.game.service.FileService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
@RequiredArgsConstructor
public class CharacterImageServiceImpl implements CharacterImageService {
private final GameCharacterImageMapper characterImageMapper;
private final GameCharacterMapper characterMapper;
private final FileService fileService;
@Override
public PageVO<CharacterImageVO> getCharacterImageList(CharacterImageQueryDTO query) {
LambdaQueryWrapper<GameCharacterImage> wrapper = new LambdaQueryWrapper<>();
if (query.getCharacterId() != null) {
wrapper.eq(GameCharacterImage::getGameCharacterId, query.getCharacterId());
}
if (StringUtils.hasText(query.getFileHash())) {
wrapper.eq(GameCharacterImage::getFileHash, query.getFileHash());
}
wrapper.orderByDesc(GameCharacterImage::getId);
IPage<GameCharacterImage> page = characterImageMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()), wrapper);
IPage<CharacterImageVO> voPage = page.convert(this::toVO);
return PageVO.from(voPage);
}
@Override
public CharacterImageVO getCharacterImageById(Long id) {
GameCharacterImage image = characterImageMapper.selectById(id);
if (image == null) {
throw new BusinessException("图片不存在");
}
return toVO(image);
}
@Override
@Transactional(rollbackFor = Exception.class)
public CharacterImageVO createCharacterImage(CharacterImageCreateDTO dto) {
checkCharacterExists(dto.getCharacterId());
String fileHash = resolveFileHash(dto.getFileHash(), dto.getFileId());
GameCharacterImage image = new GameCharacterImage();
image.setGameCharacterId(dto.getCharacterId());
image.setFileHash(fileHash);
characterImageMapper.insert(image);
fileService.increaseReferenceByHash(fileHash);
return toVO(image);
}
@Override
@Transactional(rollbackFor = Exception.class)
public CharacterImageVO updateCharacterImage(Long id, CharacterImageUpdateDTO dto) {
GameCharacterImage image = characterImageMapper.selectById(id);
if (image == null) {
throw new BusinessException("图片不存在");
}
checkCharacterExists(dto.getCharacterId());
String newFileHash = resolveFileHash(dto.getFileHash(), dto.getFileId());
if (!newFileHash.equals(image.getFileHash())) {
fileService.decreaseReferenceByHash(image.getFileHash());
fileService.increaseReferenceByHash(newFileHash);
image.setFileHash(newFileHash);
}
image.setGameCharacterId(dto.getCharacterId());
characterImageMapper.updateById(image);
return toVO(image);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteCharacterImage(Long id) {
GameCharacterImage image = characterImageMapper.selectById(id);
if (image == null) {
throw new BusinessException("图片不存在");
}
fileService.decreaseReferenceByHash(image.getFileHash());
characterImageMapper.deleteById(id);
}
private String resolveFileHash(String fileHash, Long fileId) {
if (StringUtils.hasText(fileHash)) {
fileService.getFileByHash(fileHash);
return fileHash;
}
if (fileId != null) {
FileUploadVO fileUploadVO = fileService.getFileById(fileId);
return fileUploadVO.getFileHash();
}
throw new BusinessException("文件哈希或文件ID必须提供一个");
}
private void checkCharacterExists(Long characterId) {
GameCharacter gameCharacter = characterMapper.selectById(characterId);
if (gameCharacter == null) {
throw new BusinessException("关联角色不存在");
}
}
private CharacterImageVO toVO(GameCharacterImage entity) {
CharacterImageVO vo = new CharacterImageVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}
}

View File

@@ -1,18 +1,22 @@
package com.leiyuwei.mhxy.business.service.impl;
package com.leiyuwei.mhxy.game.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.leiyuwei.mhxy.business.mapper.FileHashReferenceMapper;
import com.leiyuwei.mhxy.business.model.entity.FileHashReference;
import com.leiyuwei.mhxy.business.model.vo.FileUploadVO;
import com.leiyuwei.mhxy.business.service.FileService;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.config.MinioConfig;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.mapper.FileHashReferenceMapper;
import com.leiyuwei.mhxy.game.model.entity.FileHashReference;
import com.leiyuwei.mhxy.game.model.vo.FileUploadVO;
import com.leiyuwei.mhxy.game.service.FileService;
import io.minio.*;
import io.minio.http.Method;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.InputStream;
@@ -156,6 +160,48 @@ public class FileServiceImpl implements FileService {
return convertToVO(fileRef);
}
@Override
public FileUploadVO getFileById(Long id) {
FileHashReference fileRef = fileHashReferenceMapper.selectById(id);
if (fileRef == null) {
throw new RuntimeException("文件不存在");
}
return convertToVO(fileRef);
}
@Override
public PageVO<FileUploadVO> getFileList(Integer pageNum, Integer pageSize, String fileHash) {
LambdaQueryWrapper<FileHashReference> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(fileHash)) {
wrapper.like(FileHashReference::getFileHash, fileHash);
}
wrapper.orderByDesc(FileHashReference::getId);
IPage<FileHashReference> page = fileHashReferenceMapper.selectPage(new Page<>(pageNum, pageSize), wrapper);
IPage<FileUploadVO> voPage = page.convert(this::convertToVO);
return PageVO.from(voPage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void increaseReferenceByHash(String fileHash) {
FileHashReference fileRef = fileHashReferenceMapper.selectOne(
new LambdaQueryWrapper<FileHashReference>()
.eq(FileHashReference::getFileHash, fileHash)
);
if (fileRef == null) {
throw new RuntimeException("文件不存在");
}
fileRef.setReferenceCount(fileRef.getReferenceCount() + 1);
fileHashReferenceMapper.updateById(fileRef);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void decreaseReferenceByHash(String fileHash) {
deleteFile(fileHash);
}
@Override
public String generatePresignedUrl(String fileHash, Integer expirationMinutes) {
try {

View File

@@ -0,0 +1,125 @@
package com.leiyuwei.mhxy.game.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.mapper.CharacterKeywordMapper;
import com.leiyuwei.mhxy.game.mapper.GameCharacterMapper;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordUpdateDTO;
import com.leiyuwei.mhxy.game.model.dto.GameCharacterKeywordQueryDTO;
import com.leiyuwei.mhxy.game.model.entity.GameCharacter;
import com.leiyuwei.mhxy.game.model.entity.GameCharacterKeyword;
import com.leiyuwei.mhxy.game.model.vo.GameCharacterKeywordVO;
import com.leiyuwei.mhxy.game.service.GameCharacterKeywordService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
@RequiredArgsConstructor
public class GameCharacterKeywordServiceImpl implements GameCharacterKeywordService {
private final CharacterKeywordMapper characterKeywordMapper;
private final GameCharacterMapper characterMapper;
@Override
public PageVO<GameCharacterKeywordVO> getCharacterKeywordList(GameCharacterKeywordQueryDTO query) {
LambdaQueryWrapper<GameCharacterKeyword> wrapper = new LambdaQueryWrapper<>();
if (query.getCharacterId() != null) {
wrapper.eq(GameCharacterKeyword::getGameCharacterId, query.getCharacterId());
}
if (StringUtils.hasText(query.getKeyword())) {
wrapper.like(GameCharacterKeyword::getKeyword, query.getKeyword());
}
wrapper.orderByDesc(GameCharacterKeyword::getId);
IPage<GameCharacterKeyword> page = characterKeywordMapper.selectPage(new Page<>(query.getPageNum(), query.getPageSize()), wrapper);
IPage<GameCharacterKeywordVO> voPage = page.convert(this::toVO);
return PageVO.from(voPage);
}
@Override
public GameCharacterKeywordVO getCharacterKeywordById(Long id) {
GameCharacterKeyword keyword = characterKeywordMapper.selectById(id);
if (keyword == null) {
throw new BusinessException("关键字不存在");
}
return toVO(keyword);
}
@Override
@Transactional(rollbackFor = Exception.class)
public GameCharacterKeywordVO createCharacterKeyword(GameCharacterKeywordCreateDTO dto) {
checkCharacterExists(dto.getCharacterId());
checkKeywordDuplicate(null, dto.getCharacterId(), dto.getKeyword());
GameCharacterKeyword keyword = new GameCharacterKeyword();
keyword.setGameCharacterId(dto.getCharacterId());
keyword.setKeyword(dto.getKeyword());
characterKeywordMapper.insert(keyword);
return toVO(keyword);
}
@Override
@Transactional(rollbackFor = Exception.class)
public GameCharacterKeywordVO updateCharacterKeyword(Long id, GameCharacterKeywordUpdateDTO dto) {
GameCharacterKeyword keyword = characterKeywordMapper.selectById(id);
if (keyword == null) {
throw new BusinessException("关键字不存在");
}
checkCharacterExists(dto.getCharacterId());
checkKeywordDuplicate(id, dto.getCharacterId(), dto.getKeyword());
keyword.setGameCharacterId(dto.getCharacterId());
keyword.setKeyword(dto.getKeyword());
characterKeywordMapper.updateById(keyword);
return toVO(keyword);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteCharacterKeyword(Long id) {
GameCharacterKeyword keyword = characterKeywordMapper.selectById(id);
if (keyword == null) {
throw new BusinessException("关键字不存在");
}
characterKeywordMapper.deleteById(id);
}
private void checkCharacterExists(Long characterId) {
GameCharacter character = characterMapper.selectById(characterId);
if (character == null) {
throw new BusinessException("关联角色不存在");
}
}
private void checkKeywordDuplicate(Long selfId, Long characterId, String keyword) {
LambdaQueryWrapper<GameCharacterKeyword> wrapper = new LambdaQueryWrapper<GameCharacterKeyword>()
.eq(GameCharacterKeyword::getGameCharacterId, characterId)
.eq(GameCharacterKeyword::getKeyword, keyword);
if (selfId != null) {
wrapper.ne(GameCharacterKeyword::getId, selfId);
}
Long count = characterKeywordMapper.selectCount(wrapper);
if (count != null && count > 0) {
throw new BusinessException("关键字已存在");
}
}
private GameCharacterKeywordVO toVO(GameCharacterKeyword entity) {
GameCharacterKeywordVO vo = new GameCharacterKeywordVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}
}

View File

@@ -0,0 +1,135 @@
package com.leiyuwei.mhxy.game.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.mapper.*;
import com.leiyuwei.mhxy.game.model.dto.CharacterCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.CharacterUpdateDTO;
import com.leiyuwei.mhxy.game.model.entity.*;
import com.leiyuwei.mhxy.game.model.vo.CharacterVO;
import com.leiyuwei.mhxy.game.service.GameCharacterService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
@Service
@RequiredArgsConstructor
public class GameCharacterServiceImpl implements GameCharacterService {
private final GameCharacterMapper characterMapper;
private final AccountMapper accountMapper;
private final ServerMapper serverMapper;
private final GameCharacterImageMapper characterImageMapper;
private final CharacterKeywordMapper characterKeywordMapper;
@Override
public PageVO<CharacterVO> getCharacterList(CharacterQueryDTO query) {
LambdaQueryWrapper<GameCharacter> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(query.getCharacterName())) {
wrapper.like(GameCharacter::getCharacterName, query.getCharacterName());
}
if (query.getAccountId() != null) {
wrapper.eq(GameCharacter::getAccountId, query.getAccountId());
}
if (query.getServerId() != null) {
wrapper.eq(GameCharacter::getServerId, query.getServerId());
}
wrapper.orderByDesc(GameCharacter::getId);
IPage<GameCharacter> page = characterMapper.selectPage(
new Page<>(query.getPageNum(), query.getPageSize()), wrapper
);
IPage<CharacterVO> voPage = page.convert(this::toVO);
return PageVO.from(voPage);
}
@Override
public CharacterVO getCharacterById(Long id) {
GameCharacter character = characterMapper.selectById(id);
if (character == null) {
throw new BusinessException("角色不存在");
}
return toVO(character);
}
@Override
@Transactional(rollbackFor = Exception.class)
public CharacterVO createCharacter(CharacterCreateDTO dto) {
checkRelatedExists(dto.getAccountId(), dto.getServerId());
GameCharacter character = new GameCharacter();
character.setCharacterName(dto.getCharacterName());
character.setAccountId(dto.getAccountId());
character.setServerId(dto.getServerId());
characterMapper.insert(character);
return toVO(character);
}
@Override
@Transactional(rollbackFor = Exception.class)
public CharacterVO updateCharacter(Long id, CharacterUpdateDTO dto) {
GameCharacter character = characterMapper.selectById(id);
if (character == null) {
throw new BusinessException("角色不存在");
}
checkRelatedExists(dto.getAccountId(), dto.getServerId());
character.setCharacterName(dto.getCharacterName());
character.setAccountId(dto.getAccountId());
character.setServerId(dto.getServerId());
characterMapper.updateById(character);
return toVO(character);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteCharacter(Long id) {
GameCharacter character = characterMapper.selectById(id);
if (character == null) {
throw new BusinessException("角色不存在");
}
Long imageCount = characterImageMapper.selectCount(
new LambdaQueryWrapper<GameCharacterImage>().eq(GameCharacterImage::getGameCharacterId, id)
);
if (imageCount != null && imageCount > 0) {
throw new BusinessException("角色下存在图片,无法删除");
}
Long keywordCount = characterKeywordMapper.selectCount(
new LambdaQueryWrapper<GameCharacterKeyword>().eq(GameCharacterKeyword::getGameCharacterId, id)
);
if (keywordCount != null && keywordCount > 0) {
throw new BusinessException("角色下存在关键字,无法删除");
}
characterMapper.deleteById(id);
}
private void checkRelatedExists(Long accountId, Long serverId) {
Account account = accountMapper.selectById(accountId);
if (account == null) {
throw new BusinessException("关联账号不存在");
}
Server server = serverMapper.selectById(serverId);
if (server == null) {
throw new BusinessException("关联服务器不存在");
}
}
private CharacterVO toVO(GameCharacter entity) {
CharacterVO vo = new CharacterVO();
BeanUtils.copyProperties(entity, vo);
return vo;
}
}

View File

@@ -0,0 +1,141 @@
package com.leiyuwei.mhxy.game.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.game.mapper.ServerMapper;
import com.leiyuwei.mhxy.game.model.dto.ServerCreateDTO;
import com.leiyuwei.mhxy.game.model.dto.ServerQueryDTO;
import com.leiyuwei.mhxy.game.model.dto.ServerUpdateDTO;
import com.leiyuwei.mhxy.game.model.entity.Server;
import com.leiyuwei.mhxy.game.model.vo.ServerVO;
import com.leiyuwei.mhxy.game.service.ServerService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDateTime;
@Service
@RequiredArgsConstructor
public class ServerServiceImpl implements ServerService {
private final ServerMapper serverMapper;
@Override
public PageVO<ServerVO> getServerList(ServerQueryDTO query) {
LambdaQueryWrapper<Server> wrapper = new LambdaQueryWrapper<>();
if (StringUtils.hasText(query.getServerName())) {
wrapper.like(Server::getServerName, query.getServerName());
}
// if (query.getEnabled() != null) {
// wrapper.eq(Server::getEnabled, query.getEnabled());
// }
wrapper.orderByDesc(Server::getId);
Page<Server> page = new Page<>(query.getPageNum(), query.getPageSize());
IPage<Server> serverPage = serverMapper.selectPage(page, wrapper);
IPage<ServerVO> voPage = serverPage.convert(this::convertToVO);
return PageVO.from(voPage);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ServerVO createServer(ServerCreateDTO dto) {
// 检查服务器名称是否重复
if (isServerNameExists(dto.getServerName())) {
throw new BusinessException("服务器名称已存在");
}
// 创建实体
Server server = new Server();
server.setServerName(dto.getServerName());
server.setDescription(dto.getDescription());
//server.setEnabled(dto.getEnabled());
// server.setSort(dto.getSort());
// 插入数据库
serverMapper.insert(server);
return convertToVO(server);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ServerVO updateServer(Long id, ServerUpdateDTO dto) {
// 检查服务器是否存在
Server existingServer = serverMapper.selectById(id);
if (existingServer == null) {
throw new BusinessException("服务器不存在");
}
// 检查服务器名称是否重复(排除自身)
if (!existingServer.getServerName().equals(dto.getServerName())
&& isServerNameExists(dto.getServerName())) {
throw new BusinessException("服务器名称已存在");
}
// 更新实体
existingServer.setServerName(dto.getServerName());
existingServer.setDescription(dto.getDescription());
// existingServer.setEnabled(dto.getEnabled());
// existingServer.setSort(dto.getSort());
existingServer.setUpdatedAt(LocalDateTime.now());
serverMapper.updateById(existingServer);
return convertToVO(existingServer);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteServer(Long id) {
Server server = serverMapper.selectById(id);
if (server == null) {
throw new BusinessException("服务器不存在");
}
// 删除服务器
serverMapper.deleteById(id);
}
@Override
public ServerVO getServerById(Long id) {
Server server = serverMapper.selectById(id);
if (server == null) {
throw new BusinessException("服务器不存在");
}
return convertToVO(server);
}
/**
* 检查服务器名称是否已存在
*/
private boolean isServerNameExists(String serverName) {
LambdaQueryWrapper<Server> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(Server::getServerName, serverName);
return serverMapper.selectCount(wrapper) > 0;
}
/**
* 转换为VO对象
*/
private ServerVO convertToVO(Server server) {
ServerVO vo = new ServerVO();
vo.setId(server.getId());
vo.setServerName(server.getServerName());
vo.setDescription(server.getDescription());
//vo.setEnabled(server.getEnabled());
// vo.setSort(server.getSort());
vo.setCreatedAt(server.getCreatedAt());
vo.setUpdatedAt(server.getUpdatedAt());
return vo;
}
}

View File

@@ -26,7 +26,7 @@ public class AuthController {
* 用户登录
*/
@PostMapping("/login")
@Operation(summary = "用户登录", description = "用户名密码登录,返回 Access Token 和 Refresh Token")
@Operation(summary = "用户登录", description = "用户名密码登录,返回 Access Token 和 Refresh Token。后续访问受保护接口时,请在 Authorization 请求头中传 Bearer {accessToken}")
public Result<LoginVO> login(@Valid @RequestBody UserLoginDTO dto) {
LoginVO loginVO = authService.login(dto);
return Result.success(loginVO);

View File

@@ -33,7 +33,7 @@ public class SysPermissionController {
*/
@GetMapping
@Operation(summary = "获取权限列表", description = "分页查询权限列表")
@PreAuthorize("hasAuthority('permission:list')")
@PreAuthorize("hasAuthority('system:permission:list')")
public Result<PageVO<SysPermission>> getPermissionList(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
@@ -50,7 +50,7 @@ public class SysPermissionController {
*/
@GetMapping("/tree")
@Operation(summary = "获取权限树", description = "获取权限树形结构")
@PreAuthorize("hasAuthority('permission:view')")
@PreAuthorize("hasAuthority('system:permission:list')")
public Result<List<PermissionTreeVO>> getPermissionTree() {
List<PermissionTreeVO> tree = sysPermissionService.getPermissionTree();
return Result.success(tree);
@@ -61,10 +61,9 @@ public class SysPermissionController {
*/
@GetMapping("/menus")
@Operation(summary = "获取当前用户菜单树", description = "获取当前用户有权限访问的菜单树")
@PreAuthorize("hasAuthority('permission:view')")
@PreAuthorize("hasAuthority('system:permission:list')")
public Result<List<PermissionTreeVO>> getMenus() {
// TODO: 实现从 SecurityContext 获取用户权限
List<PermissionTreeVO> menus = sysPermissionService.getPermissionTree();
List<PermissionTreeVO> menus = sysPermissionService.getMenus();
return Result.success(menus);
}
@@ -73,7 +72,7 @@ public class SysPermissionController {
*/
@PostMapping
@Operation(summary = "创建权限", description = "创建新权限")
@PreAuthorize("hasAuthority('permission:create')")
@PreAuthorize("hasAuthority('system:permission:add')")
public Result<SysPermission> createPermission(@Valid @RequestBody SysPermission permission) {
sysPermissionService.createPermission(permission);
return Result.success("权限创建成功", permission);
@@ -84,7 +83,7 @@ public class SysPermissionController {
*/
@PutMapping("/{id}")
@Operation(summary = "更新权限", description = "更新权限信息")
@PreAuthorize("hasAuthority('permission:update')")
@PreAuthorize("hasAuthority('system:permission:edit')")
public Result<Void> updatePermission(@PathVariable Long id, @RequestBody SysPermission permission) {
permission.setId(id);
sysPermissionService.updatePermission(permission);
@@ -96,7 +95,7 @@ public class SysPermissionController {
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除权限", description = "删除指定权限(会同时删除子权限)")
@PreAuthorize("hasAuthority('permission:delete')")
@PreAuthorize("hasAuthority('system:permission:delete')")
public Result<Void> deletePermission(@PathVariable Long id) {
sysPermissionService.deletePermission(id);
return Result.success("权限删除成功", null);

View File

@@ -31,7 +31,7 @@ public class SysRoleController {
*/
@GetMapping
@Operation(summary = "获取角色列表", description = "分页查询角色列表")
@PreAuthorize("hasAuthority('role:list')")
@PreAuthorize("hasAuthority('system:role:list')")
public Result<PageVO<RoleVO>> getRoleList(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
@@ -43,7 +43,7 @@ public class SysRoleController {
* 获取角色详情
*/
@GetMapping("/{id}")
@PreAuthorize("hasAuthority('role:view')")
@PreAuthorize("hasAuthority('system:role:list')")
public Result<RoleVO> getRoleById(@PathVariable Long id) {
RoleVO roleVO = sysRoleService.getRoleById(id);
return Result.success(roleVO);
@@ -54,7 +54,7 @@ public class SysRoleController {
*/
@PostMapping
@Operation(summary = "创建角色", description = "创建新角色")
@PreAuthorize("hasAuthority('role:create')")
@PreAuthorize("hasAuthority('system:role:add')")
public Result<RoleVO> createRole(@Valid @RequestBody SysRole role) {
sysRoleService.createRole(role);
return Result.success("角色创建成功", null);
@@ -65,7 +65,7 @@ public class SysRoleController {
*/
@PutMapping("/{id}")
@Operation(summary = "更新角色", description = "更新角色信息")
@PreAuthorize("hasAuthority('role:update')")
@PreAuthorize("hasAuthority('system:role:edit')")
public Result<Void> updateRole(@PathVariable Long id, @RequestBody SysRole role) {
role.setId(id);
sysRoleService.updateRole(role);
@@ -77,7 +77,7 @@ public class SysRoleController {
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除角色", description = "删除指定角色(逻辑删除)")
@PreAuthorize("hasAuthority('role:delete')")
@PreAuthorize("hasAuthority('system:role:delete')")
public Result<Void> deleteRole(@PathVariable Long id) {
sysRoleService.deleteRole(id);
return Result.success("角色删除成功", null);
@@ -88,7 +88,7 @@ public class SysRoleController {
*/
@PutMapping("/{id}/permissions")
@Operation(summary = "分配权限", description = "为角色分配权限")
@PreAuthorize("hasAuthority('role:assign-permissions')")
@PreAuthorize("hasAuthority('system:role:assign')")
public Result<Void> assignPermissions(@PathVariable Long id, @RequestBody List<Long> permissionIds) {
sysRoleService.assignPermissions(id, permissionIds);
return Result.success("权限分配成功", null);

View File

@@ -1,6 +1,7 @@
package com.leiyuwei.mhxy.system.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.result.Result;
import com.leiyuwei.mhxy.common.vo.PageVO;
import com.leiyuwei.mhxy.system.model.dto.UserRegisterDTO;
@@ -12,6 +13,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@@ -34,9 +37,18 @@ public class SysUserController {
@GetMapping("/current")
@Operation(summary = "获取当前用户信息", description = "获取当前登录用户的详细信息")
public Result<UserVO> getCurrentUser() {
// TODO: 从 SecurityContext 获取当前用户 ID
Long userId = 1L;
UserVO userVO = sysUserService.getUserById(userId);
// 从 SecurityContext 获取当前用户
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}
UserVO userVO = sysUserService.getUserByUsername(username);
if (userVO == null) {
throw new BusinessException("当前用户不存在");
}
return Result.success(userVO);
}
@@ -45,7 +57,7 @@ public class SysUserController {
*/
@GetMapping
@Operation(summary = "获取用户列表", description = "分页查询用户列表")
@PreAuthorize("hasAuthority('user:list')")
@PreAuthorize("hasAuthority('system:user:list')")
public Result<PageVO<UserVO>> getUserList(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
@@ -58,7 +70,7 @@ public class SysUserController {
*/
@GetMapping("/{id}")
@Operation(summary = "获取用户详情", description = "根据用户 ID 查询用户详细信息")
@PreAuthorize("hasAuthority('user:view')")
@PreAuthorize("hasAuthority('system:user:list')")
public Result<UserVO> getUserById(@PathVariable Long id) {
UserVO userVO = sysUserService.getUserById(id);
return Result.success(userVO);
@@ -69,10 +81,10 @@ public class SysUserController {
*/
@PostMapping
@Operation(summary = "创建用户", description = "创建新用户")
@PreAuthorize("hasAuthority('user:create')")
@PreAuthorize("hasAuthority('system:user:add')")
public Result<UserVO> createUser(@Valid @RequestBody UserRegisterDTO dto) {
// TODO: 实现创建用户逻辑
return Result.success("用户创建成功", null);
UserVO userVO = sysUserService.createUser(dto);
return Result.success("用户创建成功", userVO);
}
/**
@@ -80,9 +92,9 @@ public class SysUserController {
*/
@PutMapping("/{id}")
@Operation(summary = "更新用户", description = "更新用户信息")
@PreAuthorize("hasAuthority('user:update')")
@PreAuthorize("hasAuthority('system:user:edit')")
public Result<Void> updateUser(@PathVariable Long id, @RequestBody UserRegisterDTO dto) {
// TODO: 实现更新用户逻辑
sysUserService.updateUser(id, dto);
return Result.success("用户更新成功", null);
}
@@ -91,9 +103,9 @@ public class SysUserController {
*/
@DeleteMapping("/{id}")
@Operation(summary = "删除用户", description = "删除指定用户(逻辑删除)")
@PreAuthorize("hasAuthority('user:delete')")
@PreAuthorize("hasAuthority('system:user:delete')")
public Result<Void> deleteUser(@PathVariable Long id) {
// TODO: 实现删除用户逻辑
sysUserService.deleteUser(id);
return Result.success("用户删除成功", null);
}
@@ -102,9 +114,9 @@ public class SysUserController {
*/
@PutMapping("/{id}/reset-password")
@Operation(summary = "重置密码", description = "重置用户密码")
@PreAuthorize("hasAuthority('user:reset-password')")
@PreAuthorize("hasAuthority('system:auth:password')")
public Result<Void> resetPassword(@PathVariable Long id, @RequestParam String newPassword) {
// TODO: 实现重置密码逻辑
sysUserService.resetPassword(id, newPassword);
return Result.success("密码重置成功", null);
}
@@ -113,9 +125,9 @@ public class SysUserController {
*/
@PutMapping("/{id}/roles")
@Operation(summary = "分配角色", description = "为用户分配角色")
@PreAuthorize("hasAuthority('user:assign-roles')")
@PreAuthorize("hasAuthority('system:user:assign')")
public Result<Void> assignRoles(@PathVariable Long id, @RequestBody List<Long> roleIds) {
// TODO: 实现分配角色逻辑
sysUserService.assignRoles(id, roleIds);
return Result.success("角色分配成功", null);
}
}

View File

@@ -17,4 +17,9 @@ public interface SysPermissionMapper extends BaseMapper<SysPermission> {
* 查询角色的权限列表
*/
List<SysPermission> selectPermissionsByRoleId(@Param("roleId") Long roleId);
/**
* 批量查询角色的权限列表
*/
List<SysPermission> selectPermissionsByRoleIds(@Param("roleIds") List<Long> roleIds);
}

View File

@@ -5,7 +5,6 @@ import com.leiyuwei.mhxy.system.model.entity.SysPermission;
import com.leiyuwei.mhxy.system.model.entity.SysRole;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@@ -18,8 +17,10 @@ public interface SysRoleMapper extends BaseMapper<SysRole> {
/**
* 查询角色的权限列表
*/
@Select("SELECT p.* FROM sys_permission p " +
"INNER JOIN sys_role_permission rp ON p.id = rp.permission_id " +
"WHERE rp.role_id = #{roleId} AND p.deleted = 0")
List<SysPermission> selectPermissionsByRoleId(@Param("roleId") Long roleId);
}
/**
* 根据角色编码查询角色
*/
SysRole selectByCode(@Param("code") String code);
}

View File

@@ -3,9 +3,9 @@ package com.leiyuwei.mhxy.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leiyuwei.mhxy.system.model.entity.SysRole;
import com.leiyuwei.mhxy.system.model.entity.SysUser;
import com.leiyuwei.mhxy.system.model.vo.UserPermissionsVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
@@ -18,14 +18,15 @@ public interface SysUserMapper extends BaseMapper<SysUser> {
/**
* 根据用户名查询用户
*/
@Select("SELECT * FROM sys_user WHERE username = #{username} AND deleted = 0")
SysUser selectByUsername(@Param("username") String username);
/**
* 查询用户的角色列表
*/
@Select("SELECT r.* FROM sys_role r " +
"INNER JOIN sys_user_role ur ON r.id = ur.role_id " +
"WHERE ur.user_id = #{userId} AND r.deleted = 0 AND r.enabled = 1")
List<SysRole> selectRolesByUserId(@Param("userId") Long userId);
/**
* 查询用户的权限信息(包含角色和权限编码)
*/
List<UserPermissionsVO> selectUserPermissionsById(@Param("userId") Long userId);
}

View File

@@ -34,6 +34,7 @@ public class SysPermission extends BaseEntity {
/**
* 父权限 ID
*/
private Long parentId;
/**

View File

@@ -11,6 +11,9 @@ import lombok.Data;
@TableName("sys_role")
public class SysRole extends BaseEntity {
// @TableId(type = IdType.ASSIGN_ID)
// private Long id;
/**
* 角色编码
*/

View File

@@ -1,6 +1,5 @@
package com.leiyuwei.mhxy.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -9,17 +8,20 @@ import lombok.Data;
*/
@Data
@TableName("sys_role_permission")
public class SysRolePermission {
public class SysRolePermission {
// @TableId(type = IdType.ASSIGN_ID)
// private Long id;
/**
* 角色 ID
*/
@TableId
private Long roleId;
/**
* 权限 ID
*/
@TableId
private Long permissionId;
}

View File

@@ -11,6 +11,7 @@ import lombok.Data;
@TableName("sys_user")
public class SysUser extends BaseEntity {
/**
* 用户名
*/

View File

@@ -1,6 +1,5 @@
package com.leiyuwei.mhxy.system.model.entity;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
@@ -9,17 +8,19 @@ import lombok.Data;
*/
@Data
@TableName("sys_user_role")
public class SysUserRole {
public class SysUserRole {
/**
* 用户 ID
*/
@TableId
private Long userId;
/**
* 角色 ID
*/
@TableId
private Long roleId;
}

View File

@@ -2,6 +2,8 @@ package com.leiyuwei.mhxy.system.model.vo;
import lombok.Data;
import java.util.List;
/**
* 登录响应 VO
*/
@@ -23,6 +25,26 @@ public class LoginVO {
*/
private String nickname;
/**
* 头像 URL
*/
private String avatar;
/**
* 角色编码列表
*/
private List<String> roleCodes;
/**
* 权限编码列表
*/
private List<String> permissionCodes;
/**
* 是否为超级管理员
*/
private Boolean isAdmin;
/**
* Access Token
*/
@@ -32,4 +54,4 @@ public class LoginVO {
* Refresh Token
*/
private String refreshToken;
}
}

View File

@@ -0,0 +1,35 @@
package com.leiyuwei.mhxy.system.model.vo;
import lombok.Data;
/**
* 用户权限信息VO
*/
@Data
public class UserPermissionsVO {
/**
* 用户 ID
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 角色编码
*/
private String roleCode;
/**
* 权限编码
*/
private String permissionCode;
/**
* 是否为超级管理员
*/
private Boolean isAdmin;
}

View File

@@ -11,6 +11,11 @@ import java.util.List;
*/
public interface SysPermissionService {
/**
* 获取角色下的权限列表
*/
List<SysPermission> getPermissionListByRoleId(Long roleId);
/**
* 获取权限列表
*/

View File

@@ -1,8 +1,11 @@
package com.leiyuwei.mhxy.system.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.leiyuwei.mhxy.system.model.dto.UserRegisterDTO;
import com.leiyuwei.mhxy.system.model.vo.UserVO;
import java.util.List;
/**
* 系统用户服务接口
*/
@@ -17,4 +20,34 @@ public interface SysUserService {
* 获取用户列表
*/
IPage<UserVO> getUserList(Integer pageNum, Integer pageSize);
/**
* 创建用户
*/
UserVO createUser(UserRegisterDTO dto);
/**
* 更新用户
*/
void updateUser(Long id, UserRegisterDTO dto);
/**
* 删除用户(逻辑删除)
*/
void deleteUser(Long id);
/**
* 重置密码
*/
void resetPassword(Long id, String newPassword);
/**
* 分配角色
*/
void assignRoles(Long userId, List<Long> roleIds);
/**
* 根据用户名获取用户信息
*/
UserVO getUserByUsername(String username);
}

View File

@@ -1,20 +1,25 @@
package com.leiyuwei.mhxy.system.service.impl;
import cn.hutool.crypto.digest.DigestUtil;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.common.util.JwtUtil;
import com.leiyuwei.mhxy.system.mapper.SysRoleMapper;
import com.leiyuwei.mhxy.system.mapper.SysUserMapper;
import com.leiyuwei.mhxy.system.mapper.SysUserRoleMapper;
import com.leiyuwei.mhxy.system.model.dto.UserLoginDTO;
import com.leiyuwei.mhxy.system.model.dto.UserRegisterDTO;
import com.leiyuwei.mhxy.system.model.entity.SysRole;
import com.leiyuwei.mhxy.system.model.entity.SysUser;
import com.leiyuwei.mhxy.system.model.entity.SysUserRole;
import com.leiyuwei.mhxy.system.model.vo.LoginVO;
import com.leiyuwei.mhxy.system.service.AuthService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@@ -26,7 +31,15 @@ import java.util.Map;
public class AuthServiceImpl implements AuthService {
private final SysUserMapper sysUserMapper;
private final SysUserRoleMapper sysUserRoleMapper;
private final SysRoleMapper sysRoleMapper;
private final JwtUtil jwtUtil;
private final PasswordEncoder passwordEncoder;
private final UserPermissionServiceImpl userPermissionService;
private static final String ROLE_ADMIN = "ROLE_ADMIN";
private static final String DEFAULT_ROLE_CODE = "ROLE_GAME";
@Override
@Transactional(rollbackFor = Exception.class)
@@ -34,13 +47,13 @@ public class AuthServiceImpl implements AuthService {
// 查询用户
SysUser user = sysUserMapper.selectByUsername(dto.getUsername());
if (user == null) {
throw new BusinessException("用户或密码错误");
throw new BusinessException("用户不存在或密码错误");
}
// 验证密码
String encryptedPassword = DigestUtil.md5Hex(dto.getPassword());
if (!encryptedPassword.equals(user.getPassword())) {
throw new BusinessException("用户或密码错误");
boolean passwordMatch = passwordEncoder.matches(dto.getPassword(), user.getPassword());
if (!passwordMatch) {
throw new BusinessException("用户不存在或密码错误");
}
// 检查用户是否启用
@@ -48,8 +61,25 @@ public class AuthServiceImpl implements AuthService {
throw new BusinessException("用户已被禁用");
}
// 生成 Token
// 查询用户权限信息(包含角色和权限编码)
Map<String, Object> permissionInfo = userPermissionService.getUserPermissionInfo(user.getId());
if (permissionInfo.isEmpty()) {
throw new BusinessException("用户未分配任何权限");
}
// 从缓存信息中提取角色和权限编码
@SuppressWarnings("unchecked")
List<String> roleCodes = (List<String>) permissionInfo.get("roleCodes");
@SuppressWarnings("unchecked")
List<String> permissionCodes = (List<String>) permissionInfo.get("permissionCodes");
boolean isAdmin = Boolean.TRUE.equals(permissionInfo.get("isAdmin"));
// 生成 Token携带角色和权限信息
Map<String, Object> extraClaims = new HashMap<>();
extraClaims.put("roleCodes", roleCodes);
extraClaims.put("permissionCodes", permissionCodes);
extraClaims.put("isAdmin", isAdmin);
String accessToken = jwtUtil.generateAccessToken(user.getId(), user.getUsername(), extraClaims);
String refreshToken = jwtUtil.generateRefreshToken(user.getId(), user.getUsername());
@@ -58,10 +88,14 @@ public class AuthServiceImpl implements AuthService {
vo.setUserId(user.getId());
vo.setUsername(user.getUsername());
vo.setNickname(user.getNickname());
vo.setAvatar(user.getAvatar());
vo.setRoleCodes(roleCodes);
vo.setPermissionCodes(permissionCodes);
vo.setIsAdmin(isAdmin);
vo.setAccessToken(accessToken);
vo.setRefreshToken(refreshToken);
log.info("用户登录成功: userId={}, username={}", user.getId(), user.getUsername());
log.info("用户登录成功: userId={}, username={}, roles={}", user.getId(), user.getUsername(), roleCodes);
return vo;
}
@@ -79,23 +113,47 @@ public class AuthServiceImpl implements AuthService {
throw new BusinessException("两次输入的密码不一致");
}
// TODO: 验证邀请码(需要在 InvitationCodeService 中实现)
// 加密密码
String encryptedPassword = DigestUtil.md5Hex(dto.getPassword());
String encryptedPassword = passwordEncoder.encode(dto.getPassword());
// 创建用户
SysUser user = new SysUser();
user.setUsername(dto.getUsername());
user.setPassword(encryptedPassword);
user.setNickname(dto.getUsername()); // 默认昵称为用户名
//user.setNickname(dto.getNickname() != null ? dto.getNickname() : dto.getUsername());
user.setNickname(dto.getUsername());
// user.setEmail(dto.getEmail());
// user.setPhone(dto.getPhone());
user.setEnabled(true);
sysUserMapper.insert(user);
// 分配默认角色 game_role
assignDefaultRole(user.getId());
log.info("用户注册成功: userId={}, username={}", user.getId(), user.getUsername());
}
/**
* 为用户分配默认角色
*/
private void assignDefaultRole(Long userId) {
// 查询默认角色
SysRole defaultRole = sysRoleMapper.selectByCode(DEFAULT_ROLE_CODE);
if (defaultRole == null) {
log.warn("默认角色 {} 不存在,跳过分配", DEFAULT_ROLE_CODE);
return;
}
// 创建用户角色关联
SysUserRole userRole = new SysUserRole();
userRole.setUserId(userId);
userRole.setRoleId(defaultRole.getId());
sysUserRoleMapper.insert(userRole);
log.info("为用户 {} 分配默认角色: {}", userId, DEFAULT_ROLE_CODE);
}
@Override
public LoginVO refreshToken(String refreshToken) {
// 验证 Refresh Token
@@ -107,8 +165,25 @@ public class AuthServiceImpl implements AuthService {
Long userId = jwtUtil.getUserIdFromToken(refreshToken);
String username = jwtUtil.getUsernameFromToken(refreshToken);
// 生成新的 Access Token
// 查询用户权限信息(包含角色和权限编码)
Map<String, Object> permissionInfo = userPermissionService.getUserPermissionInfo(userId);
if (permissionInfo.isEmpty()) {
throw new BusinessException("用户未分配任何权限");
}
// 从缓存信息中提取角色和权限编码
@SuppressWarnings("unchecked")
List<String> roleCodes = (List<String>) permissionInfo.get("roleCodes");
@SuppressWarnings("unchecked")
List<String> permissionCodes = (List<String>) permissionInfo.get("permissionCodes");
boolean isAdmin = Boolean.TRUE.equals(permissionInfo.get("isAdmin"));
// 生成新的 Access Token携带角色和权限信息
Map<String, Object> extraClaims = new HashMap<>();
extraClaims.put("roleCodes", roleCodes);
extraClaims.put("permissionCodes", permissionCodes);
extraClaims.put("isAdmin", isAdmin);
String newAccessToken = jwtUtil.generateAccessToken(userId, username, extraClaims);
// 生成新的 Refresh Token
@@ -118,10 +193,13 @@ public class AuthServiceImpl implements AuthService {
LoginVO vo = new LoginVO();
vo.setUserId(userId);
vo.setUsername(username);
vo.setRoleCodes(roleCodes);
vo.setPermissionCodes(permissionCodes);
vo.setIsAdmin(isAdmin);
vo.setAccessToken(newAccessToken);
vo.setRefreshToken(newRefreshToken);
log.info("Token 刷新成功: userId={}", userId);
return vo;
}
}
}

View File

@@ -0,0 +1,70 @@
package com.leiyuwei.mhxy.system.service.impl;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 权限缓存服务
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class PermissionCacheServiceImpl {
// private final SysUserMapper sysUserMapper;
//
// @Cacheable(cacheNames = "sys:user:permissions", key = "#userId")
// public List<UserPermissionsVO> getUserPermissions(Long userId) {
// return sysUserMapper.selectUserPermissionsById(userId);
// }
//
// @Cacheable(cacheNames = "sys:user:permissionCodes", key = "#userId")
// public List<String> getUserPermissionCodes(Long userId) {
// return sysUserMapper.selectUserPermissionsById(userId).stream()
// .map(UserPermissionsVO::getPermissionCode)
// .filter(permissionCode -> permissionCode != null)
// .distinct()
// .toList();
// }
//
// @Cacheable(cacheNames = "sys:user:roles", key = "#userId")
// public List<String> getUserRoles(Long userId) {
// return sysUserMapper.selectUserPermissionsById(userId).stream()
// .map(UserPermissionsVO::getRoleCode)
// .filter(roleCode -> roleCode != null)
// .distinct()
// .toList();
// }
//
// public boolean hasPermission(Long userId, String permissionCode) {
// List<String> permissions = getUserPermissionCodes(userId);
// return permissions != null && permissions.contains(permissionCode);
// }
//
// @Cacheable(cacheNames = "sys:user:isAdmin", key = "#userId")
// public boolean isAdmin(Long userId) {
// return sysUserMapper.selectUserPermissionsById(userId).stream()
// .anyMatch(p -> Boolean.TRUE.equals(p.getIsAdmin()));
// }
//
// @Caching(evict = {
// @CacheEvict(cacheNames = "sys:user:permissions", key = "#userId"),
// @CacheEvict(cacheNames = "sys:user:permissionCodes", key = "#userId"),
// @CacheEvict(cacheNames = "sys:user:roles", key = "#userId"),
// @CacheEvict(cacheNames = "sys:user:isAdmin", key = "#userId")
// })
// public void evictUserCache(Long userId) {
// log.info("已清除用户缓存userId: {}", userId);
// }
//
// @Caching(evict = {
// @CacheEvict(cacheNames = "sys:user:permissions", allEntries = true),
// @CacheEvict(cacheNames = "sys:user:permissionCodes", allEntries = true),
// @CacheEvict(cacheNames = "sys:user:roles", allEntries = true),
// @CacheEvict(cacheNames = "sys:user:isAdmin", allEntries = true)
// })
// public void evictAllCache() {
// log.info("已清除所有权限缓存");
// }
}

View File

@@ -3,17 +3,28 @@ package com.leiyuwei.mhxy.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.system.mapper.SysPermissionMapper;
import com.leiyuwei.mhxy.system.mapper.SysRolePermissionMapper;
import com.leiyuwei.mhxy.system.mapper.SysUserMapper;
import com.leiyuwei.mhxy.system.mapper.SysUserRoleMapper;
import com.leiyuwei.mhxy.system.model.entity.SysPermission;
import com.leiyuwei.mhxy.system.model.entity.SysRole;
import com.leiyuwei.mhxy.system.model.entity.SysUser;
import com.leiyuwei.mhxy.system.model.vo.PermissionTreeVO;
import com.leiyuwei.mhxy.system.service.SysPermissionService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@@ -27,26 +38,34 @@ import java.util.stream.Collectors;
public class SysPermissionServiceImpl implements SysPermissionService {
private final SysPermissionMapper sysPermissionMapper;
private final SysUserMapper sysUserMapper;
private final SysUserRoleMapper sysUserRoleMapper;
private final SysRolePermissionMapper sysRolePermissionMapper;
@Override
public IPage<SysPermission> getPermissionList(Integer pageNum, Integer pageSize) {
Page<SysPermission> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysPermission> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysPermission::getDeleted, 0)
.orderByAsc(SysPermission::getParentId)
wrapper.orderByAsc(SysPermission::getParentId)
.orderByAsc(SysPermission::getSort);
return sysPermissionMapper.selectPage(page, wrapper);
}
@Override
public List<SysPermission> getPermissionListByRoleId(Long roleId) {
if (roleId == null) {
return Collections.emptyList();
}
return sysPermissionMapper.selectPermissionsByRoleId(roleId);
}
@Override
// @Cacheable(cacheNames = "sys:permission:tree", key = "'all'")
public List<PermissionTreeVO> getPermissionTree() {
// 查询所有启用的权限
LambdaQueryWrapper<SysPermission> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysPermission::getDeleted, 0)
.orderByAsc(SysPermission::getParentId)
wrapper.orderByAsc(SysPermission::getParentId)
.orderByAsc(SysPermission::getSort);
List<SysPermission> permissions = sysPermissionMapper.selectList(wrapper);
// 转换为树形结构
@@ -54,46 +73,85 @@ public class SysPermissionServiceImpl implements SysPermissionService {
}
@Override
// @Cacheable(
// cacheNames = "sys:permission:menus",
// key = "T(org.springframework.security.core.context.SecurityContextHolder).getContext().getAuthentication().getName()"
// )
public List<PermissionTreeVO> getMenus() {
// TODO: 实现从 SecurityContext 获取用户权限
// 当前返回全部菜单(需要根据用户权限过滤)
LambdaQueryWrapper<SysPermission> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysPermission::getDeleted, 0)
.eq(SysPermission::getType, "MENU")
.orderByAsc(SysPermission::getParentId)
.orderByAsc(SysPermission::getSort);
Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
String username;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}
List<SysPermission> permissions = sysPermissionMapper.selectList(wrapper);
return buildTree(permissions);
SysUser user = sysUserMapper.selectByUsername(username);
if (user == null) {
throw new BusinessException("用户不存在");
}
List<SysRole> roles = sysUserMapper.selectRolesByUserId(user.getId());
if (roles == null || roles.isEmpty()) {
return new ArrayList<>();
}
List<Long> roleIds = roles.stream()
.map(SysRole::getId)
.collect(Collectors.toList());
List<SysPermission> permissions = sysPermissionMapper.selectPermissionsByRoleIds(roleIds);
if (permissions == null || permissions.isEmpty()) {
return new ArrayList<>();
}
List<SysPermission> menuPermissions = permissions.stream()
.filter(p -> "MENU".equals(p.getType()) )
.collect(Collectors.toList());
return buildTree(menuPermissions);
}
@Override
@Transactional(rollbackFor = Exception.class)
@Caching(evict = {
@CacheEvict(cacheNames = "sys:permission:tree", key = "'all'"),
@CacheEvict(cacheNames = "sys:permission:menus", allEntries = true),
@CacheEvict(cacheNames = "sys:role:byId", allEntries = true)
})
public void createPermission(SysPermission permission) {
// 设置默认值
if (permission.getSort() == null) {
permission.setSort(0);
}
sysPermissionMapper.insert(permission);
log.info("权限创建成功: permissionId={}, permissionName={}", permission.getId(), permission.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@Caching(evict = {
@CacheEvict(cacheNames = "sys:permission:tree", key = "'all'"),
@CacheEvict(cacheNames = "sys:permission:menus", allEntries = true),
@CacheEvict(cacheNames = "sys:role:byId", allEntries = true)
})
public void updatePermission(SysPermission permission) {
// 检查权限是否存在
SysPermission existPermission = sysPermissionMapper.selectById(permission.getId());
if (existPermission == null) {
throw new RuntimeException("权限不存在");
}
sysPermissionMapper.updateById(permission);
log.info("权限更新成功: permissionId={}, permissionName={}", permission.getId(), permission.getName());
}
@Override
@Transactional(rollbackFor = Exception.class)
@Caching(evict = {
@CacheEvict(cacheNames = "sys:permission:tree", key = "'all'"),
@CacheEvict(cacheNames = "sys:permission:menus", allEntries = true),
@CacheEvict(cacheNames = "sys:role:byId", allEntries = true)
})
public void deletePermission(Long id) {
// 检查权限是否存在
SysPermission permission = sysPermissionMapper.selectById(id);
@@ -105,15 +163,13 @@ public class SysPermissionServiceImpl implements SysPermissionService {
LambdaQueryWrapper<SysPermission> childWrapper = new LambdaQueryWrapper<>();
childWrapper.eq(SysPermission::getParentId, id);
long childCount = sysPermissionMapper.selectCount(childWrapper);
if (childCount > 0) {
throw new RuntimeException("该权限下存在子权限,无法删除");
}
// 逻辑删除
permission.setDeleted(1);
// permission.setDeleted(1);
sysPermissionMapper.updateById(permission);
log.info("权限删除成功: permissionId={}, permissionName={}", id, permission.getName());
}
@@ -155,7 +211,6 @@ public class SysPermissionServiceImpl implements SysPermissionService {
if (b.getSort() == null) return -1;
return a.getSort().compareTo(b.getSort());
});
return roots;
}

View File

@@ -33,6 +33,7 @@ public class SysRoleServiceImpl implements SysRoleService {
private final SysRolePermissionMapper sysRolePermissionMapper;
@Override
//@Cacheable(cacheNames = "sys:role:byId", key = "#roleId")
public RoleVO getRoleById(Long roleId) {
SysRole role = sysRoleMapper.selectById(roleId);
if (role == null) {
@@ -45,8 +46,7 @@ public class SysRoleServiceImpl implements SysRoleService {
public IPage<RoleVO> getRoleList(Integer pageNum, Integer pageSize) {
Page<SysRole> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysRole> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysRole::getDeleted, 0)
.orderByDesc(SysRole::getCreatedAt);
wrapper.orderByDesc(SysRole::getCreatedAt);
IPage<SysRole> rolePage = sysRoleMapper.selectPage(page, wrapper);
return rolePage.convert(this::convertToVO);
@@ -54,12 +54,12 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override
@Transactional(rollbackFor = Exception.class)
//@CacheEvict(cacheNames = "sys:permission:menus", allEntries = true)
public void createRole(SysRole role) {
// 检查角色编码是否已存在
SysRole existRole = sysRoleMapper.selectOne(
new LambdaQueryWrapper<SysRole>()
.eq(SysRole::getCode, role.getCode())
.eq(SysRole::getDeleted, 0)
);
if (existRole != null) {
throw new RuntimeException("角色编码已存在");
@@ -71,19 +71,21 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override
@Transactional(rollbackFor = Exception.class)
// @Caching(evict = {
// @CacheEvict(cacheNames = "sys:role:byId", key = "#role.id"),
// @CacheEvict(cacheNames = "sys:permission:menus", allEntries = true)
// })
public void updateRole(SysRole role) {
// 检查角色是否存在
SysRole existRole = sysRoleMapper.selectById(role.getId());
if (existRole == null) {
throw new RuntimeException("角色不存在");
}
// 检查角色编码是否被其他角色使用
SysRole duplicateRole = sysRoleMapper.selectOne(
new LambdaQueryWrapper<SysRole>()
.eq(SysRole::getCode, role.getCode())
.ne(SysRole::getId, role.getId())
.eq(SysRole::getDeleted, 0)
);
if (duplicateRole != null) {
throw new RuntimeException("角色编码已存在");
@@ -95,17 +97,19 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override
@Transactional(rollbackFor = Exception.class)
// @Caching(evict = {
// @CacheEvict(cacheNames = "sys:role:byId", key = "#roleId"),
// @CacheEvict(cacheNames = "sys:permission:menus", allEntries = true)
// })
public void deleteRole(Long roleId) {
SysRole role = sysRoleMapper.selectById(roleId);
if (role == null) {
throw new RuntimeException("角色不存在");
}
// 删除角色关联的权限
sysRolePermissionMapper.deleteByRoleId(roleId);
// 逻辑删除角色
role.setDeleted(1);
// role.setDeleted(1);
sysRoleMapper.updateById(role);
log.info("角色删除成功: roleId={}, roleName={}", roleId, role.getName());
@@ -113,17 +117,18 @@ public class SysRoleServiceImpl implements SysRoleService {
@Override
@Transactional(rollbackFor = Exception.class)
// @Caching(evict = {
// @CacheEvict(cacheNames = "sys:role:byId", key = "#roleId"),
// @CacheEvict(cacheNames = "sys:permission:menus", allEntries = true)
// })
public void assignPermissions(Long roleId, List<Long> permissionIds) {
// 检查角色是否存在
SysRole role = sysRoleMapper.selectById(roleId);
if (role == null) {
throw new RuntimeException("角色不存在");
}
// 清空角色现有权限
sysRolePermissionMapper.deleteByRoleId(roleId);
// 批量插入新权限
if (permissionIds != null && !permissionIds.isEmpty()) {
for (Long permissionId : permissionIds) {
SysRolePermission rp = new SysRolePermission();
@@ -136,14 +141,10 @@ public class SysRoleServiceImpl implements SysRoleService {
log.info("角色权限分配成功: roleId={}, permissionIds={}", roleId, permissionIds);
}
/**
* 将实体转换为 VO
*/
private RoleVO convertToVO(SysRole role) {
RoleVO vo = new RoleVO();
BeanUtils.copyProperties(role, vo);
// 查询角色的权限列表
List<SysPermission> permissions = sysPermissionMapper.selectPermissionsByRoleId(role.getId());
vo.setPermissions(permissions.stream()
.map(p -> p.getCode())

View File

@@ -1,16 +1,26 @@
package com.leiyuwei.mhxy.system.service.impl;
import cn.hutool.crypto.digest.DigestUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leiyuwei.mhxy.common.exception.BusinessException;
import com.leiyuwei.mhxy.system.mapper.SysRoleMapper;
import com.leiyuwei.mhxy.system.mapper.SysUserMapper;
import com.leiyuwei.mhxy.system.mapper.SysUserRoleMapper;
import com.leiyuwei.mhxy.system.model.dto.UserRegisterDTO;
import com.leiyuwei.mhxy.system.model.entity.SysRole;
import com.leiyuwei.mhxy.system.model.entity.SysUser;
import com.leiyuwei.mhxy.system.model.entity.SysUserRole;
import com.leiyuwei.mhxy.system.model.vo.UserVO;
import com.leiyuwei.mhxy.system.service.SysUserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
/**
* 系统用户服务实现
@@ -21,6 +31,8 @@ import org.springframework.stereotype.Service;
public class SysUserServiceImpl implements SysUserService {
private final SysUserMapper sysUserMapper;
private final SysUserRoleMapper sysUserRoleMapper;
private final SysRoleMapper sysRoleMapper;
@Override
public UserVO getUserById(Long userId) {
@@ -35,13 +47,120 @@ public class SysUserServiceImpl implements SysUserService {
public IPage<UserVO> getUserList(Integer pageNum, Integer pageSize) {
Page<SysUser> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysUser::getDeleted, 0)
.orderByDesc(SysUser::getCreatedAt);
wrapper.orderByDesc(SysUser::getCreatedAt);
IPage<SysUser> userPage = sysUserMapper.selectPage(page, wrapper);
return userPage.convert(this::convertToVO);
}
@Override
@Transactional(rollbackFor = Exception.class)
public UserVO createUser(UserRegisterDTO dto) {
// 检查用户名是否已存在
SysUser existUser = sysUserMapper.selectByUsername(dto.getUsername());
if (existUser != null) {
throw new BusinessException("用户名已存在");
}
// 加密密码
String encryptedPassword = DigestUtil.md5Hex(dto.getPassword());
// 创建用户
SysUser user = new SysUser();
user.setUsername(dto.getUsername());
user.setPassword(encryptedPassword);
user.setNickname(dto.getUsername());
user.setEnabled(true);
sysUserMapper.insert(user);
log.info("用户创建成功: userId={}, username={}", user.getId(), user.getUsername());
return convertToVO(user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateUser(Long id, UserRegisterDTO dto) {
SysUser user = sysUserMapper.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
user.setUsername(dto.getUsername());
user.setNickname(dto.getUsername());
sysUserMapper.updateById(user);
log.info("用户更新成功: userId={}", id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteUser(Long id) {
SysUser user = sysUserMapper.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 逻辑删除
// user.setDeleted(1);
sysUserMapper.updateById(user);
log.info("用户删除成功: userId={}", id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void resetPassword(Long id, String newPassword) {
SysUser user = sysUserMapper.selectById(id);
if (user == null) {
throw new BusinessException("用户不存在");
}
user.setPassword(DigestUtil.md5Hex(newPassword));
sysUserMapper.updateById(user);
log.info("密码重置成功: userId={}", id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void assignRoles(Long userId, List<Long> roleIds) {
// 检查用户是否存在
SysUser user = sysUserMapper.selectById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 清空用户现有角色
sysUserRoleMapper.deleteByUserId(userId);
// 分配新角色
if (roleIds != null && !roleIds.isEmpty()) {
for (Long roleId : roleIds) {
SysRole role = sysRoleMapper.selectById(roleId);
if (role == null) {
throw new BusinessException("角色不存在: " + roleId);
}
SysUserRole userRole = new SysUserRole();
userRole.setUserId(userId);
userRole.setRoleId(roleId);
sysUserRoleMapper.insert(userRole);
}
}
log.info("用户角色分配成功: userId={}, roleIds={}", userId, roleIds);
}
@Override
public UserVO getUserByUsername(String username) {
SysUser user = sysUserMapper.selectByUsername(username);
if (user == null) {
return null;
}
return convertToVO(user);
}
/**
* 将实体转换为 VO
*/
@@ -50,4 +169,4 @@ public class SysUserServiceImpl implements SysUserService {
BeanUtils.copyProperties(user, vo);
return vo;
}
}
}

View File

@@ -0,0 +1,84 @@
package com.leiyuwei.mhxy.system.service.impl;
import com.leiyuwei.mhxy.system.mapper.SysUserMapper;
import com.leiyuwei.mhxy.system.model.entity.SysUser;
import com.leiyuwei.mhxy.system.model.vo.UserPermissionsVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Spring Security 用户详情服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
private final SysUserMapper sysUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 查询用户
SysUser user = sysUserMapper.selectByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("用户不存在: " + username);
}
// 检查用户是否启用
if (!user.getEnabled()) {
throw new UsernameNotFoundException("用户已被禁用: " + username);
}
// 查询用户权限信息
List<UserPermissionsVO> userPermissions = sysUserMapper.selectUserPermissionsById(user.getId());
if (userPermissions.isEmpty()) {
log.warn("用户 {} 未分配任何权限", username);
// 返回空权限列表,认证会失败
return User.builder()
.username(username)
.password(user.getPassword())
.disabled(false)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.authorities(List.of())
.build();
}
List<String> authorities = userPermissions.stream()
.flatMap(permissions -> Stream.of(permissions.getRoleCode(), permissions.getPermissionCode()))
.filter(StringUtils::hasText)
.distinct()
.collect(Collectors.toList());
// 转换为 GrantedAuthority
List<GrantedAuthority> grantedAuthorities = authorities.stream()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
log.debug("用户 {} 权限: {}", username, authorities);
return User.builder()
.username(username)
.password(user.getPassword())
.disabled(false)
.accountExpired(false)
.accountLocked(false)
.credentialsExpired(false)
.authorities(grantedAuthorities)
.build();
}
}

View File

@@ -0,0 +1,109 @@
package com.leiyuwei.mhxy.system.service.impl;
import com.leiyuwei.mhxy.system.mapper.SysUserMapper;
import com.leiyuwei.mhxy.system.model.entity.SysRole;
import com.leiyuwei.mhxy.system.model.vo.UserPermissionsVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 用户权限服务(使用 Caffeine 缓存)
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class UserPermissionServiceImpl {
private final SysUserMapper sysUserMapper;
private final PermissionCacheServiceImpl permissionCacheService;
/**
* 获取用户完整权限信息(带缓存)
*/
public Map<String, Object> getUserPermissionInfo(Long userId) {
log.debug("获取用户完整权限信息userId: {}", userId);
List<UserPermissionsVO> permissions = sysUserMapper.selectUserPermissionsById(userId);
if (permissions.isEmpty()) {
return Map.of();
}
// 提取角色和权限
List<String> roleCodes = permissions.stream()
.map(UserPermissionsVO::getRoleCode)
.filter(roleCode -> roleCode != null)
.distinct()
.collect(Collectors.toList());
List<String> permissionCodes = permissions.stream()
.map(UserPermissionsVO::getPermissionCode)
.filter(permissionCode -> permissionCode != null)
.distinct()
.collect(Collectors.toList());
// 判断是否为超级管理员
boolean isAdmin = roleCodes.contains("ROLE_ADMIN") ||
permissions.stream().anyMatch(p -> Boolean.TRUE.equals(p.getIsAdmin()));
return Map.of(
"userId", userId,
"roleCodes", roleCodes,
"permissionCodes", permissionCodes,
"isAdmin", isAdmin
);
}
/**
* 获取用户角色列表
*/
public List<String> getUserRoles(Long userId) {
List<String> roles = sysUserMapper.selectRolesByUserId(userId).stream()
.map(SysRole::getCode).collect(Collectors.toList());
return roles;
}
/**
* 获取用户权限列表
*/
public List<String> getUserPermissions(Long userId) {
List<String> permissionCodes = sysUserMapper.selectUserPermissionsById(userId).stream()
.map(permission -> permission.getPermissionCode()).collect(Collectors.toList());
return permissionCodes;
}
/**
* 检查用户是否有指定权限
*/
// public boolean hasPermission(Long userId, String permissionCode) {
// return permissionCacheService.hasPermission(userId, permissionCode);
// }
/**
* 检查用户是否有指定角色
*/
public boolean hasRole(Long userId, String roleCode) {
List<String> roles = sysUserMapper.selectRolesByUserId(userId).stream()
.map(SysRole::getCode).collect(Collectors.toList()) ;
return roles.contains(roleCode);
}
/**
* 检查用户是否为超级管理员
*/
// public boolean isAdmin(Long userId) {
// return permissionCacheService.isAdmin(userId);
// }
/**
* 清除用户缓存(可在用户角色/权限变更后调用)
*/
// public void evictUserCache(Long userId) {
// permissionCacheService.evictUserCache(userId);
// }
}

View File

@@ -1,5 +1,11 @@
server:
port: 18080
spring:
application:
name: mhxy-tools
# 数据库配置
@@ -54,6 +60,20 @@ spring:
# max-idle: 8
# min-idle: 0
# Cache 配置
cache:
type: caffeine
caffeine:
spec: maximumSize=10000,expireAfterWrite=30m
# cache:
# type: redis
# redis:
# time-to-live: 1800000 # 30分钟
# cache-null-values: false
# key-prefix: cache:
# use-key-prefix: true
# 文件上传配置
servlet:
multipart:
@@ -67,18 +87,25 @@ spring:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
default-property-inclusion: non_null
output:
ansi:
enabled: always
# MyBatis Plus 配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
default-enum-type-handler: com.baomidou.mybatisplus.core.handlers.MybatisEnumTypeHandler
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
page:
max-limit: 1000
global-config:
field-strategy: 0
db-config:
id-type: auto
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
# logic-delete-field: isDeleted
id-type: assign_id
# logic-delete-field: deleted
# logic-delete-value: 1
# logic-not-delete-value: 0
mapper-locations: classpath:mapper/**/*.xml
# Knife4j 配置
@@ -108,8 +135,14 @@ jwt:
# 日志配置
logging:
level:
com.mhxy: debug
root: info
com.leiyuwei.mhxy.mapper: debug # mapper 包路径
com.baomidou.mybatisplus: debug # MP 框架
org.springframework.security: debug
org.springframework.security.web.access.intercept: debug
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{50} - %msg%n"
console: "%d{yyyy-MM-dd HH:mm:ss} \u001B[32m[%thread]\u001B[0m \u001B[36m%-5level\u001B[0m \u001B[33m%logger{36}\u001B[0m - \u001B[0m%msg%n"
console:
enabled: true

View File

@@ -0,0 +1,7 @@
/*
Add missing columns to server table: enabled (boolean) and sort (int)
*/
ALTER TABLE `server`
ADD COLUMN `enabled` tinyint(1) NOT NULL DEFAULT 1 COMMENT '是否启用',
ADD COLUMN `sort` int NOT NULL DEFAULT 0 COMMENT '排序顺序';

View File

@@ -0,0 +1,19 @@
<?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.leiyuwei.mhxy.business.mapper.FileHashReferenceMapper">
<select id="selectByObjectName" resultType="com.leiyuwei.mhxy.game.model.entity.FileHashReference">
SELECT id,
created_at,
updated_at,
file_hash,
object_name,
file_size,
content_type,
reference_count
FROM file_hash_reference
WHERE object_name = #{objectName}
</select>
</mapper>

View File

@@ -0,0 +1,77 @@
<?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.leiyuwei.mhxy.game.mapper.ServerMapper">
<!-- <select id="SELECT_ALL" resultType="com.leiyuwei.mhxy.game.model.entity.Server">-->
<!-- SELECT id,-->
<!-- server_name AS serverName,-->
<!-- description,-->
<!-- enabled,-->
<!-- sort,-->
<!-- created_at AS createdAt,-->
<!-- updated_at AS updatedAt,-->
<!-- deleted-->
<!-- FROM server-->
<!-- WHERE deleted = 0-->
<!-- </select>-->
<!-- <select id="SELECT_PAGE" resultType="com.leiyuwei.mhxy.game.model.entity.Server">-->
<!-- SELECT id,-->
<!-- server_name AS serverName,-->
<!-- description,-->
<!-- enabled,-->
<!-- sort,-->
<!-- created_at AS createdAt,-->
<!-- updated_at AS updatedAt,-->
<!-- deleted-->
<!-- FROM server-->
<!-- WHERE deleted = 0-->
<!-- <if test="serverName != null and serverName != ''">-->
<!-- AND server_name LIKE CONCAT('%', #{serverName}, '%')-->
<!-- </if>-->
<!-- <if test="enabled != null">-->
<!-- AND enabled = #{enabled}-->
<!-- </if>-->
<!-- ORDER BY id DESC-->
<!-- LIMIT #{pageSize} OFFSET #{offset}-->
<!-- </select>-->
<!-- <select id="SELECT_BY_ID" resultType="com.leiyuwei.mhxy.game.model.entity.Server">-->
<!-- SELECT id,-->
<!-- server_name AS serverName,-->
<!-- description,-->
<!-- enabled,-->
<!-- sort,-->
<!-- created_at AS createdAt,-->
<!-- updated_at AS updatedAt,-->
<!-- deleted-->
<!-- FROM server-->
<!-- WHERE id = #{id}-->
<!-- AND deleted = 0-->
<!-- </select>-->
<!-- <insert id="INSERT" useGeneratedKeys="true" keyProperty="id">-->
<!-- INSERT INTO server (server_name, description, enabled, sort, deleted, created_at, updated_at)-->
<!-- VALUES (#{serverName}, #{description}, #{enabled}, #{sort}, 0, NOW(), NOW())-->
<!-- </insert>-->
<!-- <update id="UPDATE">-->
<!-- UPDATE server-->
<!-- SET server_name = #{serverName},-->
<!-- description = #{description},-->
<!-- enabled = #{enabled},-->
<!-- sort = #{sort},-->
<!-- updated_at = NOW()-->
<!-- WHERE id = #{id}-->
<!-- AND deleted = 0-->
<!-- </update>-->
<!-- <update id="LOGICAL_DELETE">-->
<!-- UPDATE server-->
<!-- SET deleted = 1,-->
<!-- updated_at = NOW()-->
<!-- WHERE id = #{id}-->
<!-- AND deleted = 0-->
<!-- </update>-->
</mapper>

View File

@@ -16,7 +16,28 @@
FROM sys_permission p
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
WHERE rp.role_id = #{roleId}
AND p.deleted = 0
ORDER BY p.sort ASC
</select>
<!-- 批量查询角色的权限列表 -->
<select id="selectPermissionsByRoleIds" resultType="com.leiyuwei.mhxy.system.model.entity.SysPermission">
SELECT DISTINCT
p.id,
p.code,
p.name,
p.type,
p.resource,
p.parent_id as parentId,
p.icon,
p.sort
FROM sys_permission p
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
WHERE rp.role_id IN
<foreach item="roleId" collection="#{roleIds}" open="(" separator="," close=")">
#{roleId}
</foreach>
ORDER BY p.sort ASC
</select>

View File

@@ -0,0 +1,40 @@
<?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.leiyuwei.mhxy.system.mapper.SysRoleMapper">
<select id="selectPermissionsByRoleId" resultType="com.leiyuwei.mhxy.system.model.entity.SysPermission">
SELECT p.id,
p.created_at,
p.updated_at,
p.parent_id,
p.code,
p.name,
p.type,
p.path,
p.component,
p.icon,
p.sort,
p.visible,
p.enabled
FROM sys_permission p
INNER JOIN sys_role_permission rp ON p.id = rp.permission_id
WHERE rp.role_id = #{roleId}
</select>
<select id="selectByCode" resultType="com.leiyuwei.mhxy.system.model.entity.SysRole">
SELECT id,
created_at,
updated_at,
code,
name,
description,
sort,
enabled
FROM sys_role
WHERE code = #{code}
AND enabled = 1
</select>
</mapper>

View File

@@ -0,0 +1,7 @@
<?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.leiyuwei.mhxy.system.mapper.SysRolePermissionMapper">
</mapper>

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