4.26 ShardingSphere分库分表
This commit is contained in:
parent
012e66bfc8
commit
6a5fb45cc2
6
pom.xml
6
pom.xml
@ -96,6 +96,12 @@
|
|||||||
<groupId>org.apache.commons</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-pool2</artifactId>
|
<artifactId>commons-pool2</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- 分库分表 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.shardingsphere</groupId>
|
||||||
|
<artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId>
|
||||||
|
<version>5.2.0</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package edu.whut.smilepicturebackend;
|
package edu.whut.smilepicturebackend;
|
||||||
|
|
||||||
|
import org.apache.shardingsphere.spring.boot.ShardingSphereAutoConfiguration;
|
||||||
import org.mybatis.spring.annotation.MapperScan;
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
|
@SpringBootApplication(exclude = {ShardingSphereAutoConfiguration.class})
|
||||||
@SpringBootApplication
|
|
||||||
@EnableAsync
|
@EnableAsync
|
||||||
@MapperScan("edu.whut.smilepicturebackend.mapper")
|
@MapperScan("edu.whut.smilepicturebackend.mapper")
|
||||||
public class SmilePictureBackendApplication {
|
public class SmilePictureBackendApplication {
|
||||||
|
@ -15,6 +15,7 @@ import edu.whut.smilepicturebackend.constant.UserConstant;
|
|||||||
import edu.whut.smilepicturebackend.exception.BusinessException;
|
import edu.whut.smilepicturebackend.exception.BusinessException;
|
||||||
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
||||||
import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
||||||
|
import edu.whut.smilepicturebackend.manager.auth.SpaceUserAuthManager;
|
||||||
import edu.whut.smilepicturebackend.manager.auth.StpKit;
|
import edu.whut.smilepicturebackend.manager.auth.StpKit;
|
||||||
import edu.whut.smilepicturebackend.manager.auth.annotation.SaSpaceCheckPermission;
|
import edu.whut.smilepicturebackend.manager.auth.annotation.SaSpaceCheckPermission;
|
||||||
import edu.whut.smilepicturebackend.manager.auth.model.SpaceUserPermissionConstant;
|
import edu.whut.smilepicturebackend.manager.auth.model.SpaceUserPermissionConstant;
|
||||||
@ -49,6 +50,7 @@ public class PictureController {
|
|||||||
private final PictureService pictureService;
|
private final PictureService pictureService;
|
||||||
private final SpaceService spaceService;
|
private final SpaceService spaceService;
|
||||||
private final AliYunAiApi aliYunAiApi;
|
private final AliYunAiApi aliYunAiApi;
|
||||||
|
private final SpaceUserAuthManager spaceUserAuthManager;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -176,15 +178,22 @@ public class PictureController {
|
|||||||
ThrowUtils.throwIf(PictureReviewStatusEnum.PASS.getValue()!=picture.getReviewStatus(),ErrorCode.NOT_FOUND_ERROR);
|
ThrowUtils.throwIf(PictureReviewStatusEnum.PASS.getValue()!=picture.getReviewStatus(),ErrorCode.NOT_FOUND_ERROR);
|
||||||
// 空间权限校验
|
// 空间权限校验
|
||||||
Long spaceId = picture.getSpaceId();
|
Long spaceId = picture.getSpaceId();
|
||||||
|
Space space = null;
|
||||||
|
// 权限判断 —— 只对“团队空间”才要求登录 & 权限
|
||||||
if (spaceId != null) {
|
if (spaceId != null) {
|
||||||
boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW); //编程式鉴权
|
boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW); //编程式鉴权
|
||||||
ThrowUtils.throwIf(!hasPermission, ErrorCode.NO_AUTH_ERROR);
|
ThrowUtils.throwIf(!hasPermission, ErrorCode.NO_AUTH_ERROR);
|
||||||
//User loginUser = userService.getLoginUser(request);
|
//User loginUser = userService.getLoginUser(request);
|
||||||
//改为使用注解鉴权
|
//已经改为使用sa-token鉴权
|
||||||
//pictureService.checkPictureAuth(loginUser, picture);
|
//pictureService.checkPictureAuth(loginUser, picture);
|
||||||
}
|
}
|
||||||
|
// 获取权限列表
|
||||||
|
User loginUser = userService.getLoginUser(request);
|
||||||
|
List<String> permissionList = spaceUserAuthManager.getPermissionList(space, loginUser);
|
||||||
|
PictureVO pictureVO = pictureService.getPictureVO(picture, request);
|
||||||
|
pictureVO.setPermissionList(permissionList);
|
||||||
// 获取封装类
|
// 获取封装类
|
||||||
return ResultUtils.success(pictureService.getPictureVO(picture, request));
|
return ResultUtils.success(pictureVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -6,7 +6,6 @@ import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
|||||||
import edu.whut.smilepicturebackend.model.dto.space.analyze.*;
|
import edu.whut.smilepicturebackend.model.dto.space.analyze.*;
|
||||||
import edu.whut.smilepicturebackend.model.entity.Space;
|
import edu.whut.smilepicturebackend.model.entity.Space;
|
||||||
import edu.whut.smilepicturebackend.model.entity.User;
|
import edu.whut.smilepicturebackend.model.entity.User;
|
||||||
import edu.whut.smilepicturebackend.model.vo.analyze.*;
|
|
||||||
import edu.whut.smilepicturebackend.model.vo.space.analyze.*;
|
import edu.whut.smilepicturebackend.model.vo.space.analyze.*;
|
||||||
import edu.whut.smilepicturebackend.service.SpaceAnalyzeService;
|
import edu.whut.smilepicturebackend.service.SpaceAnalyzeService;
|
||||||
import edu.whut.smilepicturebackend.service.UserService;
|
import edu.whut.smilepicturebackend.service.UserService;
|
||||||
|
@ -9,6 +9,7 @@ import edu.whut.smilepicturebackend.constant.UserConstant;
|
|||||||
import edu.whut.smilepicturebackend.exception.BusinessException;
|
import edu.whut.smilepicturebackend.exception.BusinessException;
|
||||||
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
||||||
import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
||||||
|
import edu.whut.smilepicturebackend.manager.auth.SpaceUserAuthManager;
|
||||||
import edu.whut.smilepicturebackend.model.dto.space.*;
|
import edu.whut.smilepicturebackend.model.dto.space.*;
|
||||||
import edu.whut.smilepicturebackend.model.entity.Space;
|
import edu.whut.smilepicturebackend.model.entity.Space;
|
||||||
import edu.whut.smilepicturebackend.model.entity.User;
|
import edu.whut.smilepicturebackend.model.entity.User;
|
||||||
@ -41,6 +42,7 @@ public class SpaceController {
|
|||||||
|
|
||||||
private final SpaceService spaceService;
|
private final SpaceService spaceService;
|
||||||
|
|
||||||
|
private final SpaceUserAuthManager spaceUserAuthManager;
|
||||||
|
|
||||||
@PostMapping("/add")
|
@PostMapping("/add")
|
||||||
public BaseResponse<Long> addSpace(@RequestBody SpaceAddRequest spaceAddRequest, HttpServletRequest request) {
|
public BaseResponse<Long> addSpace(@RequestBody SpaceAddRequest spaceAddRequest, HttpServletRequest request) {
|
||||||
@ -123,7 +125,11 @@ public class SpaceController {
|
|||||||
// 查询数据库
|
// 查询数据库
|
||||||
Space space = spaceService.getById(id);
|
Space space = spaceService.getById(id);
|
||||||
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR);
|
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR);
|
||||||
return ResultUtils.success(spaceService.getSpaceVO(space,request));
|
SpaceVO spaceVO = spaceService.getSpaceVO(space, request);
|
||||||
|
User loginUser = userService.getLoginUser(request);
|
||||||
|
List<String> permissionList = spaceUserAuthManager.getPermissionList(space, loginUser);
|
||||||
|
spaceVO.setPermissionList(permissionList);
|
||||||
|
return ResultUtils.success(spaceVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package edu.whut.smilepicturebackend.exception;
|
package edu.whut.smilepicturebackend.exception;
|
||||||
|
|
||||||
|
import cn.dev33.satoken.exception.NotLoginException;
|
||||||
|
import cn.dev33.satoken.exception.NotPermissionException;
|
||||||
import edu.whut.smilepicturebackend.common.BaseResponse;
|
import edu.whut.smilepicturebackend.common.BaseResponse;
|
||||||
import edu.whut.smilepicturebackend.common.ResultUtils;
|
import edu.whut.smilepicturebackend.common.ResultUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@ -12,6 +14,17 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
@ExceptionHandler(NotLoginException.class)
|
||||||
|
public BaseResponse<?> notLoginException(NotLoginException e) {
|
||||||
|
log.error("NotLoginException", e);
|
||||||
|
return ResultUtils.error(ErrorCode.NOT_LOGIN_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NotPermissionException.class)
|
||||||
|
public BaseResponse<?> notPermissionExceptionHandler(NotPermissionException e) {
|
||||||
|
log.error("NotPermissionException", e);
|
||||||
|
return ResultUtils.error(ErrorCode.NO_AUTH_ERROR, e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
|
public BaseResponse<?> businessExceptionHandler(BusinessException e) {
|
||||||
|
@ -0,0 +1,142 @@
|
|||||||
|
package edu.whut.smilepicturebackend.manager.sharding;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.toolkit.SqlRunner;
|
||||||
|
import edu.whut.smilepicturebackend.model.entity.Space;
|
||||||
|
import edu.whut.smilepicturebackend.model.enums.SpaceLevelEnum;
|
||||||
|
import edu.whut.smilepicturebackend.model.enums.SpaceTypeEnum;
|
||||||
|
import edu.whut.smilepicturebackend.service.SpaceService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.shardingsphere.driver.jdbc.core.connection.ShardingSphereConnection;
|
||||||
|
import org.apache.shardingsphere.infra.metadata.database.rule.ShardingSphereRuleMetaData;
|
||||||
|
import org.apache.shardingsphere.mode.manager.ContextManager;
|
||||||
|
import org.apache.shardingsphere.sharding.api.config.ShardingRuleConfiguration;
|
||||||
|
import org.apache.shardingsphere.sharding.api.config.rule.ShardingTableRuleConfiguration;
|
||||||
|
import org.apache.shardingsphere.sharding.rule.ShardingRule;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
//@Component
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DynamicShardingManager {
|
||||||
|
|
||||||
|
private final DataSource dataSource;
|
||||||
|
|
||||||
|
private final SpaceService spaceService;
|
||||||
|
|
||||||
|
private static final String LOGIC_TABLE_NAME = "picture";
|
||||||
|
|
||||||
|
private static final String DATABASE_NAME = "logic_db"; // 配置文件中的数据库名称
|
||||||
|
|
||||||
|
@PostConstruct
|
||||||
|
public void initialize() {
|
||||||
|
log.info("初始化动态分表配置...");
|
||||||
|
updateShardingTableNodes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有动态表名,包括初始表 picture 和分表 picture_{spaceId}
|
||||||
|
*/
|
||||||
|
private Set<String> fetchAllPictureTableNames() {
|
||||||
|
// 为了测试方便,直接对所有团队空间分表(实际上线改为仅对团队空间的旗舰版生效)
|
||||||
|
Set<Long> spaceIds = spaceService.lambdaQuery()
|
||||||
|
.eq(Space::getSpaceType, SpaceTypeEnum.TEAM.getValue())
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(Space::getId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
Set<String> tableNames = spaceIds.stream()
|
||||||
|
.map(spaceId -> LOGIC_TABLE_NAME + "_" + spaceId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
tableNames.add(LOGIC_TABLE_NAME); // 添加初始逻辑表
|
||||||
|
return tableNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新 ShardingSphere 的 actual-data-nodes 动态表名配置
|
||||||
|
*/
|
||||||
|
private void updateShardingTableNodes() {
|
||||||
|
Set<String> tableNames = fetchAllPictureTableNames();
|
||||||
|
// smile-picture.picture_112321321,smile-picture.picture_1123213123
|
||||||
|
String newActualDataNodes = tableNames.stream()
|
||||||
|
.map(tableName -> "smile-picture." + tableName) // 确保前缀合法
|
||||||
|
.collect(Collectors.joining(","));
|
||||||
|
log.info("动态分表 actual-data-nodes 配置: {}", newActualDataNodes);
|
||||||
|
|
||||||
|
ContextManager contextManager = getContextManager();
|
||||||
|
ShardingSphereRuleMetaData ruleMetaData = contextManager.getMetaDataContexts()
|
||||||
|
.getMetaData()
|
||||||
|
.getDatabases()
|
||||||
|
.get(DATABASE_NAME)
|
||||||
|
.getRuleMetaData();
|
||||||
|
|
||||||
|
Optional<ShardingRule> shardingRule = ruleMetaData.findSingleRule(ShardingRule.class);
|
||||||
|
if (shardingRule.isPresent()) {
|
||||||
|
ShardingRuleConfiguration ruleConfig = (ShardingRuleConfiguration) shardingRule.get().getConfiguration();
|
||||||
|
List<ShardingTableRuleConfiguration> updatedRules = ruleConfig.getTables()
|
||||||
|
.stream()
|
||||||
|
.map(oldTableRule -> {
|
||||||
|
if (LOGIC_TABLE_NAME.equals(oldTableRule.getLogicTable())) {
|
||||||
|
ShardingTableRuleConfiguration newTableRuleConfig = new ShardingTableRuleConfiguration(LOGIC_TABLE_NAME, newActualDataNodes);
|
||||||
|
newTableRuleConfig.setDatabaseShardingStrategy(oldTableRule.getDatabaseShardingStrategy());
|
||||||
|
newTableRuleConfig.setTableShardingStrategy(oldTableRule.getTableShardingStrategy());
|
||||||
|
newTableRuleConfig.setKeyGenerateStrategy(oldTableRule.getKeyGenerateStrategy());
|
||||||
|
newTableRuleConfig.setAuditStrategy(oldTableRule.getAuditStrategy());
|
||||||
|
return newTableRuleConfig;
|
||||||
|
}
|
||||||
|
return oldTableRule;
|
||||||
|
})
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
ruleConfig.setTables(updatedRules);
|
||||||
|
contextManager.alterRuleConfiguration(DATABASE_NAME, Collections.singleton(ruleConfig));
|
||||||
|
contextManager.reloadDatabase(DATABASE_NAME);
|
||||||
|
log.info("动态分表规则更新成功!");
|
||||||
|
} else {
|
||||||
|
log.error("未找到 ShardingSphere 的分片规则配置,动态分表更新失败。");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 动态创建空间图片分表
|
||||||
|
*
|
||||||
|
* @param space
|
||||||
|
*/
|
||||||
|
public void createSpacePictureTable(Space space) {
|
||||||
|
// 仅为旗舰版团队空间创建分表
|
||||||
|
if (space.getSpaceType() == SpaceTypeEnum.TEAM.getValue() && space.getSpaceLevel() == SpaceLevelEnum.FLAGSHIP.getValue()) {
|
||||||
|
Long spaceId = space.getId();
|
||||||
|
String tableName = LOGIC_TABLE_NAME + "_" + spaceId;
|
||||||
|
// 创建新表
|
||||||
|
String createTableSql = "CREATE TABLE " + tableName + " LIKE " + LOGIC_TABLE_NAME; //like创建与逻辑表一模一样的表
|
||||||
|
try {
|
||||||
|
SqlRunner.db().update(createTableSql);
|
||||||
|
// 更新分表
|
||||||
|
updateShardingTableNodes();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
log.error("创建图片空间分表失败,空间 id = {}", space.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 ShardingSphere ContextManager
|
||||||
|
*/
|
||||||
|
private ContextManager getContextManager() {
|
||||||
|
try (ShardingSphereConnection connection = dataSource.getConnection().unwrap(ShardingSphereConnection.class)) {
|
||||||
|
return connection.getContextManager();
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("获取 ShardingSphere ContextManager 失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package edu.whut.smilepicturebackend.manager.sharding;
|
||||||
|
|
||||||
|
import org.apache.shardingsphere.sharding.api.sharding.standard.PreciseShardingValue;
|
||||||
|
import org.apache.shardingsphere.sharding.api.sharding.standard.RangeShardingValue;
|
||||||
|
import org.apache.shardingsphere.sharding.api.sharding.standard.StandardShardingAlgorithm;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片分表算法
|
||||||
|
*/
|
||||||
|
public class PictureShardingAlgorithm implements StandardShardingAlgorithm<Long> {
|
||||||
|
//availableTargetNames指实际表名集合 , preciseShardingValue这里指spaceid
|
||||||
|
@Override
|
||||||
|
public String doSharding(Collection<String> availableTargetNames, PreciseShardingValue<Long> preciseShardingValue) {
|
||||||
|
Long spaceId = preciseShardingValue.getValue();
|
||||||
|
String logicTableName = preciseShardingValue.getLogicTableName();
|
||||||
|
// spaceId 为 null 表示查询所有图片
|
||||||
|
if (spaceId == null) {
|
||||||
|
return logicTableName;
|
||||||
|
}
|
||||||
|
// 根据 spaceId 动态生成分表名
|
||||||
|
String realTableName = "picture_" + spaceId;
|
||||||
|
if (availableTargetNames.contains(realTableName)) {
|
||||||
|
return realTableName;
|
||||||
|
} else {
|
||||||
|
return logicTableName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<String> doSharding(Collection<String> collection, RangeShardingValue<Long> rangeShardingValue) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Properties getProps() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void init(Properties properties) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ import cn.hutool.json.JSONObject;
|
|||||||
import cn.hutool.json.JSONUtil;
|
import cn.hutool.json.JSONUtil;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
|
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import edu.whut.smilepicturebackend.api.aliyunai.AliYunAiApi;
|
import edu.whut.smilepicturebackend.api.aliyunai.AliYunAiApi;
|
||||||
@ -72,13 +73,21 @@ import java.util.stream.Collectors;
|
|||||||
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
||||||
implements PictureService {
|
implements PictureService {
|
||||||
private final FileManager fileManager;
|
private final FileManager fileManager;
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
private final FilePictureUpload filePictureUpload;
|
private final FilePictureUpload filePictureUpload;
|
||||||
|
|
||||||
private final UrlPictureUpload urlPictureUpload;
|
private final UrlPictureUpload urlPictureUpload;
|
||||||
|
|
||||||
private final MyCacheManager cacheManager;
|
private final MyCacheManager cacheManager;
|
||||||
|
|
||||||
private final CosManager cosManager;
|
private final CosManager cosManager;
|
||||||
|
|
||||||
private final SpaceService spaceService;
|
private final SpaceService spaceService;
|
||||||
|
|
||||||
private final TransactionTemplate transactionTemplate;
|
private final TransactionTemplate transactionTemplate;
|
||||||
|
|
||||||
private final AliYunAiApi aliYunAiApi;
|
private final AliYunAiApi aliYunAiApi;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -180,7 +189,6 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
picture.setUserId(loginUser.getId());
|
picture.setUserId(loginUser.getId());
|
||||||
picture.setSpaceId(spaceId);
|
picture.setSpaceId(spaceId);
|
||||||
// 转换为标准颜色
|
// 转换为标准颜色
|
||||||
//TODO:不知道为什么没有正确设置到数据库
|
|
||||||
log.info("颜色"+uploadPictureResult.getPicColor());
|
log.info("颜色"+uploadPictureResult.getPicColor());
|
||||||
picture.setPicColor(ColorTransformUtils.getStandardColor(uploadPictureResult.getPicColor()));
|
picture.setPicColor(ColorTransformUtils.getStandardColor(uploadPictureResult.getPicColor()));
|
||||||
// 补充审核参数
|
// 补充审核参数
|
||||||
@ -198,21 +206,34 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
// 开启事务,图片上传成功和修改额度一定要同时成功或失败
|
// 开启事务,图片上传成功和修改额度一定要同时成功或失败
|
||||||
Long finalSpaceId = spaceId;
|
Long finalSpaceId = spaceId;
|
||||||
transactionTemplate.execute(status -> {
|
transactionTemplate.execute(status -> {
|
||||||
// 插入数据
|
log.info("uploadPicture | spaceId={}, sizeDelta={}, countDelta={}",
|
||||||
boolean result = this.saveOrUpdate(picture);
|
finalSpaceId, sizeDelta, countDelta);
|
||||||
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败");
|
|
||||||
if (finalSpaceId != null) {
|
/* ---------- 1. 保存 / 更新图片记录 ---------- */
|
||||||
// 更新空间的使用额度
|
boolean saved = this.saveOrUpdate(picture);
|
||||||
boolean update = spaceService.lambdaUpdate()
|
ThrowUtils.throwIf(!saved, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败");
|
||||||
.eq(Space::getId, finalSpaceId)
|
|
||||||
// 更新 total_size
|
/* ---------- 2. 更新空间额度(只有有变化时才执行 UPDATE) ---------- */
|
||||||
.apply(sizeDelta != 0, "total_size = total_size + {0}", sizeDelta) // 占位符安全绑定[1][2]
|
if (finalSpaceId != null && (sizeDelta != 0 || countDelta != 0)) {
|
||||||
// 更新 total_count(只有新增才加 1)
|
LambdaUpdateChainWrapper<Space> wrapper = spaceService.lambdaUpdate()
|
||||||
.apply(countDelta != 0, "total_count = total_count + {0}", countDelta)
|
.eq(Space::getId, finalSpaceId);
|
||||||
.update();
|
|
||||||
ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "额度更新失败");
|
// 组装可变 SQL,不生成空 SET
|
||||||
|
StringBuilder setSql = new StringBuilder();
|
||||||
|
if (sizeDelta != 0) {
|
||||||
|
setSql.append("total_size = total_size + ").append(sizeDelta);
|
||||||
|
}
|
||||||
|
if (countDelta != 0) {
|
||||||
|
if (setSql.length() > 0) setSql.append(", ");
|
||||||
|
setSql.append("total_count = total_count + ").append(countDelta);
|
||||||
|
}
|
||||||
|
wrapper.setSql(setSql.toString());
|
||||||
|
|
||||||
|
boolean updated = wrapper.update();
|
||||||
|
ThrowUtils.throwIf(!updated, ErrorCode.OPERATION_ERROR, "额度更新失败");
|
||||||
}
|
}
|
||||||
return picture;
|
|
||||||
|
return picture; // 事务块返回结果
|
||||||
});
|
});
|
||||||
//如果是更新,清理旧的图片
|
//如果是更新,清理旧的图片
|
||||||
if (oldPicture != null) {
|
if (oldPicture != null) {
|
||||||
|
@ -26,6 +26,7 @@ import edu.whut.smilepicturebackend.model.vo.UserVO;
|
|||||||
import edu.whut.smilepicturebackend.service.SpaceService;
|
import edu.whut.smilepicturebackend.service.SpaceService;
|
||||||
import edu.whut.smilepicturebackend.service.SpaceUserService;
|
import edu.whut.smilepicturebackend.service.SpaceUserService;
|
||||||
import edu.whut.smilepicturebackend.service.UserService;
|
import edu.whut.smilepicturebackend.service.UserService;
|
||||||
|
import org.springframework.context.annotation.Lazy;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@ -51,8 +52,13 @@ public class SpaceServiceImpl extends ServiceImpl<SpaceMapper, Space>
|
|||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
// 静态锁表,JVM 级别共享
|
// 静态锁表,JVM 级别共享
|
||||||
private static final ConcurrentHashMap<Long, Object> USER_LOCKS = new ConcurrentHashMap<>();
|
private static final ConcurrentHashMap<Long, Object> USER_LOCKS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final SpaceUserService spaceUserService;
|
private final SpaceUserService spaceUserService;
|
||||||
|
|
||||||
private final TransactionTemplate transactionTemplate;
|
private final TransactionTemplate transactionTemplate;
|
||||||
|
// @Resource
|
||||||
|
// @Lazy
|
||||||
|
// private DynamicShardingManager dynamicShardingManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建空间 加锁和事务
|
* 创建空间 加锁和事务
|
||||||
@ -110,6 +116,8 @@ public class SpaceServiceImpl extends ServiceImpl<SpaceMapper, Space>
|
|||||||
result = spaceUserService.save(spaceUser);
|
result = spaceUserService.save(spaceUser);
|
||||||
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "创建团队成员记录失败");
|
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "创建团队成员记录失败");
|
||||||
}
|
}
|
||||||
|
//创建分表(仅对团队空间生效)为方便部署,暂时不使用
|
||||||
|
// dynamicShardingManager.createSpacePictureTable(space);
|
||||||
return space.getId();
|
return space.getId();
|
||||||
});
|
});
|
||||||
return Optional.ofNullable(newSpaceId).orElse(-1L);
|
return Optional.ofNullable(newSpaceId).orElse(-1L);
|
||||||
|
@ -26,6 +26,8 @@ import lombok.RequiredArgsConstructor;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.context.annotation.Lazy;
|
import org.springframework.context.annotation.Lazy;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -43,8 +45,9 @@ public class SpaceUserServiceImpl extends ServiceImpl<SpaceUserMapper, SpaceUser
|
|||||||
implements SpaceUserService {
|
implements SpaceUserService {
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
@Lazy
|
@Lazy
|
||||||
private final SpaceService spaceService;
|
private SpaceService spaceService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long addSpaceUser(SpaceUserAddRequest spaceUserAddRequest) {
|
public long addSpaceUser(SpaceUserAddRequest spaceUserAddRequest) {
|
||||||
|
@ -129,7 +129,6 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
|
|||||||
// 4. 保存用户的登录态
|
// 4. 保存用户的登录态
|
||||||
request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
|
request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user);
|
||||||
// 记录用户登录态到 Sa-token,便于空间鉴权时使用,注意保证该用户信息与 SpringSession 中的信息过期时间一致
|
// 记录用户登录态到 Sa-token,便于空间鉴权时使用,注意保证该用户信息与 SpringSession 中的信息过期时间一致
|
||||||
|
|
||||||
StpKit.SPACE.login(user.getId());
|
StpKit.SPACE.login(user.getId());
|
||||||
StpKit.SPACE.getSession().set(UserConstant.USER_LOGIN_STATE, user);
|
StpKit.SPACE.getSession().set(UserConstant.USER_LOGIN_STATE, user);
|
||||||
return this.getLoginUserVO(user);
|
return this.getLoginUserVO(user);
|
||||||
|
@ -34,7 +34,33 @@ spring:
|
|||||||
multipart:
|
multipart:
|
||||||
max-file-size: 10MB
|
max-file-size: 10MB
|
||||||
max-request-size: 10MB
|
max-request-size: 10MB
|
||||||
|
# 空间图片分表
|
||||||
|
shardingsphere:
|
||||||
|
datasource:
|
||||||
|
names: smile-picture
|
||||||
|
smile-picture:
|
||||||
|
type: com.zaxxer.hikari.HikariDataSource
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://localhost:3306/smile-picture
|
||||||
|
username: root
|
||||||
|
password: 123456
|
||||||
|
rules:
|
||||||
|
sharding:
|
||||||
|
tables:
|
||||||
|
picture:
|
||||||
|
actual-data-nodes: smile-picture.picture # 动态分表
|
||||||
|
table-strategy:
|
||||||
|
standard:
|
||||||
|
sharding-column: space_id
|
||||||
|
sharding-algorithm-name: picture_sharding_algorithm # 使用自定义分片算法
|
||||||
|
sharding-algorithms:
|
||||||
|
picture_sharding_algorithm:
|
||||||
|
type: CLASS_BASED
|
||||||
|
props:
|
||||||
|
strategy: standard
|
||||||
|
algorithmClassName: edu.whut.smilepicturebackend.manager.sharding.PictureShardingAlgorithm
|
||||||
|
props:
|
||||||
|
sql-show: true #打印实际执行的sql
|
||||||
mybatis-plus:
|
mybatis-plus:
|
||||||
type-aliases-package: edu.whut.smilepicturebackend.model.entity
|
type-aliases-package: edu.whut.smilepicturebackend.model.entity
|
||||||
configuration:
|
configuration:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user