diff --git a/pom.xml b/pom.xml index c75be93..1bc34b4 100644 --- a/pom.xml +++ b/pom.xml @@ -96,6 +96,12 @@ org.apache.commons commons-pool2 + + + org.apache.shardingsphere + shardingsphere-jdbc-core-spring-boot-starter + 5.2.0 + org.springframework.boot spring-boot-configuration-processor diff --git a/src/main/java/edu/whut/smilepicturebackend/SmilePictureBackendApplication.java b/src/main/java/edu/whut/smilepicturebackend/SmilePictureBackendApplication.java index 3fbbff2..364a0ca 100644 --- a/src/main/java/edu/whut/smilepicturebackend/SmilePictureBackendApplication.java +++ b/src/main/java/edu/whut/smilepicturebackend/SmilePictureBackendApplication.java @@ -1,11 +1,11 @@ package edu.whut.smilepicturebackend; +import org.apache.shardingsphere.spring.boot.ShardingSphereAutoConfiguration; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableAsync; - -@SpringBootApplication +@SpringBootApplication(exclude = {ShardingSphereAutoConfiguration.class}) @EnableAsync @MapperScan("edu.whut.smilepicturebackend.mapper") public class SmilePictureBackendApplication { diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java index 98a9c62..7bd4d9f 100644 --- a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java +++ b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java @@ -15,6 +15,7 @@ import edu.whut.smilepicturebackend.constant.UserConstant; import edu.whut.smilepicturebackend.exception.BusinessException; import edu.whut.smilepicturebackend.exception.ErrorCode; 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.annotation.SaSpaceCheckPermission; import edu.whut.smilepicturebackend.manager.auth.model.SpaceUserPermissionConstant; @@ -49,6 +50,7 @@ public class PictureController { private final PictureService pictureService; private final SpaceService spaceService; 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); // 空间权限校验 Long spaceId = picture.getSpaceId(); + Space space = null; + // 权限判断 —— 只对“团队空间”才要求登录 & 权限 if (spaceId != null) { boolean hasPermission = StpKit.SPACE.hasPermission(SpaceUserPermissionConstant.PICTURE_VIEW); //编程式鉴权 ThrowUtils.throwIf(!hasPermission, ErrorCode.NO_AUTH_ERROR); //User loginUser = userService.getLoginUser(request); - //改为使用注解鉴权 + //已经改为使用sa-token鉴权 //pictureService.checkPictureAuth(loginUser, picture); } + // 获取权限列表 + User loginUser = userService.getLoginUser(request); + List 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); } /** diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/SpaceAnalyzeController.java b/src/main/java/edu/whut/smilepicturebackend/controller/SpaceAnalyzeController.java index fbef838..bf94828 100644 --- a/src/main/java/edu/whut/smilepicturebackend/controller/SpaceAnalyzeController.java +++ b/src/main/java/edu/whut/smilepicturebackend/controller/SpaceAnalyzeController.java @@ -6,7 +6,6 @@ import edu.whut.smilepicturebackend.exception.ThrowUtils; import edu.whut.smilepicturebackend.model.dto.space.analyze.*; import edu.whut.smilepicturebackend.model.entity.Space; 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.service.SpaceAnalyzeService; import edu.whut.smilepicturebackend.service.UserService; diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java b/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java index 7c0900b..d2f27fe 100644 --- a/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java +++ b/src/main/java/edu/whut/smilepicturebackend/controller/SpaceController.java @@ -9,6 +9,7 @@ import edu.whut.smilepicturebackend.constant.UserConstant; import edu.whut.smilepicturebackend.exception.BusinessException; import edu.whut.smilepicturebackend.exception.ErrorCode; 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.entity.Space; import edu.whut.smilepicturebackend.model.entity.User; @@ -41,6 +42,7 @@ public class SpaceController { private final SpaceService spaceService; + private final SpaceUserAuthManager spaceUserAuthManager; @PostMapping("/add") public BaseResponse addSpace(@RequestBody SpaceAddRequest spaceAddRequest, HttpServletRequest request) { @@ -123,7 +125,11 @@ public class SpaceController { // 查询数据库 Space space = spaceService.getById(id); 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 permissionList = spaceUserAuthManager.getPermissionList(space, loginUser); + spaceVO.setPermissionList(permissionList); + return ResultUtils.success(spaceVO); } /** diff --git a/src/main/java/edu/whut/smilepicturebackend/exception/GlobalExceptionHandler.java b/src/main/java/edu/whut/smilepicturebackend/exception/GlobalExceptionHandler.java index dc14c1f..0b632e3 100644 --- a/src/main/java/edu/whut/smilepicturebackend/exception/GlobalExceptionHandler.java +++ b/src/main/java/edu/whut/smilepicturebackend/exception/GlobalExceptionHandler.java @@ -1,5 +1,7 @@ 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.ResultUtils; import lombok.extern.slf4j.Slf4j; @@ -12,6 +14,17 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; @RestControllerAdvice @Slf4j 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) public BaseResponse businessExceptionHandler(BusinessException e) { diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/sharding/DynamicShardingManager.java b/src/main/java/edu/whut/smilepicturebackend/manager/sharding/DynamicShardingManager.java new file mode 100644 index 0000000..dbf97d3 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/manager/sharding/DynamicShardingManager.java @@ -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 fetchAllPictureTableNames() { + // 为了测试方便,直接对所有团队空间分表(实际上线改为仅对团队空间的旗舰版生效) + Set spaceIds = spaceService.lambdaQuery() + .eq(Space::getSpaceType, SpaceTypeEnum.TEAM.getValue()) + .list() + .stream() + .map(Space::getId) + .collect(Collectors.toSet()); + Set 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 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 = ruleMetaData.findSingleRule(ShardingRule.class); + if (shardingRule.isPresent()) { + ShardingRuleConfiguration ruleConfig = (ShardingRuleConfiguration) shardingRule.get().getConfiguration(); + List 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); + } + } +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/sharding/PictureShardingAlgorithm.java b/src/main/java/edu/whut/smilepicturebackend/manager/sharding/PictureShardingAlgorithm.java new file mode 100644 index 0000000..0b50bf9 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/manager/sharding/PictureShardingAlgorithm.java @@ -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 { + //availableTargetNames指实际表名集合 , preciseShardingValue这里指spaceid + @Override + public String doSharding(Collection availableTargetNames, PreciseShardingValue 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 doSharding(Collection collection, RangeShardingValue rangeShardingValue) { + return new ArrayList<>(); + } + + @Override + public Properties getProps() { + return null; + } + + @Override + public void init(Properties properties) { + + } +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java index f965b54..658ff0f 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java @@ -10,6 +10,7 @@ import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 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.service.impl.ServiceImpl; import edu.whut.smilepicturebackend.api.aliyunai.AliYunAiApi; @@ -72,13 +73,21 @@ import java.util.stream.Collectors; public class PictureServiceImpl extends ServiceImpl implements PictureService { private final FileManager fileManager; + private final UserService userService; + private final FilePictureUpload filePictureUpload; + private final UrlPictureUpload urlPictureUpload; + private final MyCacheManager cacheManager; + private final CosManager cosManager; + private final SpaceService spaceService; + private final TransactionTemplate transactionTemplate; + private final AliYunAiApi aliYunAiApi; @Override @@ -180,7 +189,6 @@ public class PictureServiceImpl extends ServiceImpl picture.setUserId(loginUser.getId()); picture.setSpaceId(spaceId); // 转换为标准颜色 - //TODO:不知道为什么没有正确设置到数据库 log.info("颜色"+uploadPictureResult.getPicColor()); picture.setPicColor(ColorTransformUtils.getStandardColor(uploadPictureResult.getPicColor())); // 补充审核参数 @@ -198,21 +206,34 @@ public class PictureServiceImpl extends ServiceImpl // 开启事务,图片上传成功和修改额度一定要同时成功或失败 Long finalSpaceId = spaceId; transactionTemplate.execute(status -> { - // 插入数据 - boolean result = this.saveOrUpdate(picture); - ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败"); - if (finalSpaceId != null) { - // 更新空间的使用额度 - boolean update = spaceService.lambdaUpdate() - .eq(Space::getId, finalSpaceId) - // 更新 total_size - .apply(sizeDelta != 0, "total_size = total_size + {0}", sizeDelta) // 占位符安全绑定[1][2] - // 更新 total_count(只有新增才加 1) - .apply(countDelta != 0, "total_count = total_count + {0}", countDelta) - .update(); - ThrowUtils.throwIf(!update, ErrorCode.OPERATION_ERROR, "额度更新失败"); + log.info("uploadPicture | spaceId={}, sizeDelta={}, countDelta={}", + finalSpaceId, sizeDelta, countDelta); + + /* ---------- 1. 保存 / 更新图片记录 ---------- */ + boolean saved = this.saveOrUpdate(picture); + ThrowUtils.throwIf(!saved, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败"); + + /* ---------- 2. 更新空间额度(只有有变化时才执行 UPDATE) ---------- */ + if (finalSpaceId != null && (sizeDelta != 0 || countDelta != 0)) { + LambdaUpdateChainWrapper wrapper = spaceService.lambdaUpdate() + .eq(Space::getId, finalSpaceId); + + // 组装可变 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) { diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java index 50cc38b..5e43d3c 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceServiceImpl.java @@ -26,6 +26,7 @@ import edu.whut.smilepicturebackend.model.vo.UserVO; import edu.whut.smilepicturebackend.service.SpaceService; import edu.whut.smilepicturebackend.service.SpaceUserService; import edu.whut.smilepicturebackend.service.UserService; +import org.springframework.context.annotation.Lazy; import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; @@ -51,8 +52,13 @@ public class SpaceServiceImpl extends ServiceImpl private final UserService userService; // 静态锁表,JVM 级别共享 private static final ConcurrentHashMap USER_LOCKS = new ConcurrentHashMap<>(); + private final SpaceUserService spaceUserService; + private final TransactionTemplate transactionTemplate; +// @Resource +// @Lazy +// private DynamicShardingManager dynamicShardingManager; /** * 创建空间 加锁和事务 @@ -110,6 +116,8 @@ public class SpaceServiceImpl extends ServiceImpl result = spaceUserService.save(spaceUser); ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "创建团队成员记录失败"); } + //创建分表(仅对团队空间生效)为方便部署,暂时不使用 +// dynamicShardingManager.createSpacePictureTable(space); return space.getId(); }); return Optional.ofNullable(newSpaceId).orElse(-1L); diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceUserServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceUserServiceImpl.java index 149169e..70a1ea9 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceUserServiceImpl.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/SpaceUserServiceImpl.java @@ -26,6 +26,8 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.BeanUtils; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; + +import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; @@ -43,8 +45,9 @@ public class SpaceUserServiceImpl extends ServiceImpl // 4. 保存用户的登录态 request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user); // 记录用户登录态到 Sa-token,便于空间鉴权时使用,注意保证该用户信息与 SpringSession 中的信息过期时间一致 - StpKit.SPACE.login(user.getId()); StpKit.SPACE.getSession().set(UserConstant.USER_LOGIN_STATE, user); return this.getLoginUserVO(user); diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 63ba4f1..1b2aed1 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -34,7 +34,33 @@ spring: multipart: max-file-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: type-aliases-package: edu.whut.smilepicturebackend.model.entity configuration: