diff --git a/README.md b/README.md
index fcda266b7d97abf5641a43d4cce1307fb8dff29e..c1f56d68a147d760cdb938753b10d4b92dac6687 100644
--- a/README.md
+++ b/README.md
@@ -91,4 +91,166 @@ C:\Users\Administrator\.jdks\openjdk-21.0.1\bin\java -Dfile.encoding=UTF-8 -Dsun
## 在milkbox-service模块下新建模块后需要配置maven的导包依赖
-在项目根目录下的pom.xml的dependencyManagement中定义新模块的版本,然后在milkbox-app模块中引入新模块
\ No newline at end of file
+在项目根目录下的pom.xml的dependencyManagement中定义新模块的版本,然后在milkbox-app模块中引入新模块
+
+# MapStruct
+
+项目建议使用MapStruct来做对象转换。定义一个转换接口与转换方法,MapStruct会根据方法的参数与返回,在启动项目之前的编译阶段值自动生成实现类。
+
+> 注意:如果对接口进行了修改,一定先执行maven的clean命令然后再编译项目。否则新修改的内容可能不会更新编译。
+
+当前其对应接口的实现类生成的源码文件位于`模块.../target/generated-sources/annotations/包路径.../XxxxxxImpl.java`
+
+其实现类的字节码文件将会按照接口的包路径位置生成
+
+写法举例如下:
+
+```java
+package top.milkbox.sys.modular.user.mapStruct;
+
+import org.mapstruct.Mapper;
+import top.milkbox.sys.modular.user.entity.SysUserEntity;
+import top.milkbox.sys.modular.user.param.SysUserAddParam;
+import top.milkbox.sys.modular.user.param.SysUserEditParam;
+import top.milkbox.sys.modular.user.vo.SysUserVo;
+
+/**
+ * SysUser相关的实体类之间的转换
+ * 此接口由MapStruct在编译时生成实现代码,详细解释请看readme
+ *
+ * @author milkbox
+ * @Data 2024-11-19
+ */
+@Mapper // 注意这个Mapper不是String的Mapper
+public interface SysUserMapStruct {
+
+ /**
+ * entity转vo
+ */
+ SysUserVo entityToVo(SysUserEntity entity);
+
+ /**
+ * addParam转entity
+ */
+ SysUserEntity addParamToEntity(SysUserAddParam addParam);
+
+ /**
+ * editParam转entity
+ */
+ SysUserEntity editParamToEntity(SysUserEditParam editParam);
+
+}
+
+```
+
+生成的实现类源码:
+```java
+package top.milkbox.sys.modular.user.mapStruct;
+
+import javax.annotation.processing.Generated;
+import org.springframework.stereotype.Component;
+import top.milkbox.sys.modular.user.entity.SysUserEntity;
+import top.milkbox.sys.modular.user.param.SysUserAddParam;
+import top.milkbox.sys.modular.user.param.SysUserEditParam;
+import top.milkbox.sys.modular.user.vo.SysUserVo;
+
+@Generated(
+ value = "org.mapstruct.ap.MappingProcessor",
+ date = "2024-11-19T09:38:00+0800",
+ comments = "version: 1.5.5.Final, compiler: javac, environment: Java 21.0.1 (Oracle Corporation)"
+)
+@Component
+public class SysUserMapStructImpl implements SysUserMapStruct {
+
+ @Override
+ public SysUserVo entityToVo(SysUserEntity entity) {
+ if ( entity == null ) {
+ return null;
+ }
+
+ SysUserVo sysUserVo = new SysUserVo();
+
+ sysUserVo.setSortCode( entity.getSortCode() );
+ sysUserVo.setCreateUser( entity.getCreateUser() );
+ sysUserVo.setCreateTime( entity.getCreateTime() );
+ sysUserVo.setUpdateUser( entity.getUpdateUser() );
+ sysUserVo.setUpdateTime( entity.getUpdateTime() );
+ sysUserVo.setId( entity.getId() );
+ sysUserVo.setAccount( entity.getAccount() );
+ sysUserVo.setNickname( entity.getNickname() );
+ sysUserVo.setEmail( entity.getEmail() );
+ sysUserVo.setPhone( entity.getPhone() );
+ sysUserVo.setAvatar( entity.getAvatar() );
+ sysUserVo.setGender( entity.getGender() );
+ sysUserVo.setSecretLevel( entity.getSecretLevel() );
+ sysUserVo.setStatus( entity.getStatus() );
+ sysUserVo.setBeforeLoginIp( entity.getBeforeLoginIp() );
+ sysUserVo.setBeforeLoginAddress( entity.getBeforeLoginAddress() );
+ sysUserVo.setBeforeLoginTime( entity.getBeforeLoginTime() );
+ sysUserVo.setBeforeLoginDevice( entity.getBeforeLoginDevice() );
+ sysUserVo.setNewLoginIp( entity.getNewLoginIp() );
+ sysUserVo.setNewLoginAddress( entity.getNewLoginAddress() );
+ sysUserVo.setNewLoginTime( entity.getNewLoginTime() );
+ sysUserVo.setNewLoginDevice( entity.getNewLoginDevice() );
+
+ return sysUserVo;
+ }
+
+ @Override
+ public SysUserEntity addParamToEntity(SysUserAddParam addParam) {
+ if ( addParam == null ) {
+ return null;
+ }
+
+ SysUserEntity sysUserEntity = new SysUserEntity();
+
+ sysUserEntity.setAccount( addParam.getAccount() );
+ sysUserEntity.setNickname( addParam.getNickname() );
+ sysUserEntity.setEmail( addParam.getEmail() );
+ sysUserEntity.setPhone( addParam.getPhone() );
+ sysUserEntity.setAvatar( addParam.getAvatar() );
+ sysUserEntity.setGender( addParam.getGender() );
+ sysUserEntity.setSecretLevel( addParam.getSecretLevel() );
+ sysUserEntity.setStatus( addParam.getStatus() );
+
+ return sysUserEntity;
+ }
+
+ @Override
+ public SysUserEntity editParamToEntity(SysUserEditParam editParam) {
+ if ( editParam == null ) {
+ return null;
+ }
+
+ SysUserEntity sysUserEntity = new SysUserEntity();
+
+ sysUserEntity.setId( editParam.getId() );
+ sysUserEntity.setAccount( editParam.getAccount() );
+ sysUserEntity.setPassword( editParam.getPassword() );
+ sysUserEntity.setNickname( editParam.getNickname() );
+ sysUserEntity.setEmail( editParam.getEmail() );
+ sysUserEntity.setPhone( editParam.getPhone() );
+ sysUserEntity.setAvatar( editParam.getAvatar() );
+ sysUserEntity.setGender( editParam.getGender() );
+ sysUserEntity.setSecretLevel( editParam.getSecretLevel() );
+ sysUserEntity.setStatus( editParam.getStatus() );
+ sysUserEntity.setBeforeLoginIp( editParam.getBeforeLoginIp() );
+ sysUserEntity.setBeforeLoginAddress( editParam.getBeforeLoginAddress() );
+ sysUserEntity.setBeforeLoginTime( editParam.getBeforeLoginTime() );
+ sysUserEntity.setBeforeLoginDevice( editParam.getBeforeLoginDevice() );
+ sysUserEntity.setNewLoginIp( editParam.getNewLoginIp() );
+ sysUserEntity.setNewLoginAddress( editParam.getNewLoginAddress() );
+ sysUserEntity.setNewLoginTime( editParam.getNewLoginTime() );
+ sysUserEntity.setNewLoginDevice( editParam.getNewLoginDevice() );
+
+ return sysUserEntity;
+ }
+}
+
+```
+
+# 有关时区的问题
+
+## 时间的存取过程
+
+在java中使用new Date()方式创建亚洲上海时区时间->mysql数据库中使用datetime数据类型存储无时区时间->java读取时间并转为上海时区时间->springmvc中使用jackson将java时间转为字符串(其会按照application配置文件中的配置进行转换)->返回给前端年月日格式的字符串。
\ No newline at end of file
diff --git a/milkbox-app/src/main/java/top/milkbox/core/config/SaTokenConfiguration.java b/milkbox-app/src/main/java/top/milkbox/core/config/SaTokenConfiguration.java
index ae1b2d35343367098a53ce40bb2a2a1711038dc7..c88a50625034f552fca75a08f37d47fd4768e55f 100644
--- a/milkbox-app/src/main/java/top/milkbox/core/config/SaTokenConfiguration.java
+++ b/milkbox-app/src/main/java/top/milkbox/core/config/SaTokenConfiguration.java
@@ -53,7 +53,7 @@ public class SaTokenConfiguration implements WebMvcConfigurer {
// ---------- 设置跨域响应头 ----------
/*
对于跨域请求,我们分两种情况
- 1. 不使用浏览器的自动管理cookie功。也就是说在前后端分离的项目中(小程序,h5,手机应用等)禁用了cookie功能。
+ 1. 不使用浏览器的自动管理cookie功能。也就是说在前后端分离的项目中(小程序,h5,手机应用等)禁用了cookie功能。
sa-token的登录令牌(token)需要在前端手动控制存储
浏览器无需管理cookie,所以权限比较松,配置参考如下:
// 允许所有域
diff --git a/milkbox-app/src/main/java/top/milkbox/core/handler/GlobalControllerAdvice.java b/milkbox-app/src/main/java/top/milkbox/core/handler/GlobalControllerAdvice.java
index 1ad6efd28e8f0a84fc46e12f1ad274790a9c94ff..2ed6402ca28152c2c1d585826bc306af51de62ee 100644
--- a/milkbox-app/src/main/java/top/milkbox/core/handler/GlobalControllerAdvice.java
+++ b/milkbox-app/src/main/java/top/milkbox/core/handler/GlobalControllerAdvice.java
@@ -1,8 +1,11 @@
package top.milkbox.core.handler;
import cn.dev33.satoken.exception.NotLoginException;
+import cn.dev33.satoken.exception.NotPermissionException;
+import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.ui.Model;
import org.springframework.web.bind.MethodArgumentNotValidException;
@@ -12,6 +15,8 @@ import org.springframework.web.method.annotation.HandlerMethodValidationExceptio
import top.milkbox.common.enums.CommonStatusCodeEnum;
import top.milkbox.common.exceprion.CommonServiceException;
import top.milkbox.common.pojo.CommonResult;
+import top.milkbox.sys.modular.permission.service.SysPermissionService;
+import top.milkbox.sys.modular.permission.vo.SysPermissionVo;
import java.util.Arrays;
@@ -25,6 +30,9 @@ import java.util.Arrays;
@ControllerAdvice
public class GlobalControllerAdvice {
+ @Autowired
+ private SysPermissionService sysPermissionService;
+
/**
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
*/
@@ -72,6 +80,20 @@ public class GlobalControllerAdvice {
notLoginException.getMessage(), notLoginException);
}
+ // 无sa-token权限异常
+ else if (exception instanceof NotPermissionException notPermissionException) {
+ String permissionCode = notPermissionException.getPermission();
+ // 根据权限码,从数据库中查询出这一条权限,用于拼接错误信息
+ SysPermissionVo permissionVo = sysPermissionService.getByValue(permissionCode);
+ StringBuilder errorMessage = new StringBuilder("无此权限:");
+ if (ObjectUtil.isNotEmpty(permissionVo)) {
+ errorMessage.append(permissionVo.getName());
+ }
+ return CommonResult.create()
+ .withCode(CommonStatusCodeEnum.ERROR403.getCode())
+ .withMessage(errorMessage.append("(").append(permissionCode).append(")").toString());
+ }
+
// 后端校验不通过异常
else if (exception instanceof MethodArgumentNotValidException errorResponse) {
StringBuilder errorMessage = new StringBuilder();
@@ -109,6 +131,6 @@ public class GlobalControllerAdvice {
log.error("其他未处理异常:{}", exception.getMessage(), exception);
// exception.printStackTrace(); // 系统控制台堆栈,可能不会存入日志
return new CommonResult<>(CommonStatusCodeEnum.ERROR500.getCode(),
- CommonStatusCodeEnum.ERROR500.getMessage(), exception.getStackTrace());
+ exception.getMessage(), exception.getStackTrace());
}
}
diff --git a/milkbox-app/src/main/resources/application.yml b/milkbox-app/src/main/resources/application.yml
index 340e1942b5dd7fbf2d45e7ff858f68357bb0093f..a412cde3e462a59b6b936a26f59964d639355b0f 100644
--- a/milkbox-app/src/main/resources/application.yml
+++ b/milkbox-app/src/main/resources/application.yml
@@ -7,6 +7,12 @@ springdoc:
path: "/doc.html"
spring:
+ ############### Jackson配置 ##############
+ jackson:
+ time-zone: "Asia/Shanghai" # 时区
+ date-format: "yyyy-MM-dd HH:mm:ss" # 日期格式
+ locale: zh_CN
+
############## 数据库配置 ##############
datasource:
# 主机地址、用户名和密码请配置环境变量
diff --git a/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonParam.java b/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonParam.java
new file mode 100644
index 0000000000000000000000000000000000000000..7808fee3325519f8aeda0cddfb9920802bfe0ca3
--- /dev/null
+++ b/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonParam.java
@@ -0,0 +1,16 @@
+package top.milkbox.common.pojo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+/**
+ * 通用参数对象
+ *
+ * @author milkbox
+ */
+@Data
+public class CommonParam {
+
+ @Schema(title = "排序字段")
+ private Integer sortCode;
+}
diff --git a/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonVo.java b/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonVo.java
index 0570b3ee717618f7c4b8702d69c0659cddedac51..6d09ad1933723281c68c5f0c5ef0f99769312881 100644
--- a/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonVo.java
+++ b/milkbox-common/src/main/java/top/milkbox/common/pojo/CommonVo.java
@@ -20,9 +20,12 @@ public class CommonVo implements Serializable {
/**
* 创建人
*/
- @Schema(title = "创建人")
+ @Schema(title = "创建人ID")
private Integer createUser;
+ @Schema(title = "创建人名称")
+ private String createUserName;
+
/**
* 创建时间
*/
@@ -32,9 +35,12 @@ public class CommonVo implements Serializable {
/**
* 更新人
*/
- @Schema(title = "更新人")
+ @Schema(title = "更新人ID")
private Integer updateUser;
+ @Schema(title = "更新人名称")
+ private String updateUserName;
+
/**
* 更新时间
*/
diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/core/StpInterfaceImpl.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/core/StpInterfaceImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..498f108bb1823f2a252f790d834de0e45a85274b
--- /dev/null
+++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/core/StpInterfaceImpl.java
@@ -0,0 +1,51 @@
+package top.milkbox.sys.core;
+
+import cn.dev33.satoken.session.SaSession;
+import cn.dev33.satoken.stp.StpInterface;
+import cn.dev33.satoken.stp.StpUtil;
+import lombok.AllArgsConstructor;
+import org.springframework.stereotype.Component;
+import top.milkbox.sys.modular.permission.entity.SysPermissionEntity;
+import top.milkbox.sys.modular.permission.service.SysPermissionService;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * 自定义权限加载接口实现类
+ */
+@AllArgsConstructor
+@Component // 保证此类被 SpringBoot 扫描,完成 Sa-Token 的自定义权限验证扩展
+public class StpInterfaceImpl implements StpInterface {
+
+ private SysPermissionService sysPermissionService;
+
+ /**
+ * 返回一个账号所拥有的权限码集合
+ */
+ @Override
+ public List getPermissionList(Object loginUserId, String loginType) {
+ SaSession accountSession = StpUtil.getSessionByLoginId(loginUserId);
+ // 获取不到则返回空集合
+ if (accountSession == null) {
+ return new ArrayList<>();
+ }
+ // 获取权限集合,并转为权限value字段集合。第二个参数表示默认值
+ List permissionList = accountSession.get("permission", new ArrayList<>());
+ return permissionList.stream().map(SysPermissionEntity::getValue).collect(Collectors.toList());
+ }
+
+ /**
+ * 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
+ */
+ @Override
+ public List getRoleList(Object loginId, String loginType) {
+ // 本 list 仅做模拟,实际项目中要根据具体业务逻辑来查询角色
+ List list = new ArrayList<>();
+ list.add("admin");
+ list.add("super-admin");
+ return list;
+ }
+
+}
diff --git a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java
index c4fd28aabf1c2cc30ab866c703c4c3498f789d3f..5455c6eec4d034dcf5deb958678173848cb219fe 100644
--- a/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java
+++ b/milkbox-service/service-sys/src/main/java/top/milkbox/sys/modular/menu/controller/SysMenuController.java
@@ -1,12 +1,11 @@
package top.milkbox.sys.modular.menu.controller;
import cn.dev33.satoken.annotation.SaCheckLogin;
+import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.lang.tree.Tree;
-import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
-import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import org.springframework.validation.annotation.Validated;
@@ -18,7 +17,6 @@ import top.milkbox.sys.core.config.SysConfiguration;
import top.milkbox.sys.modular.menu.param.SysMenuAddParam;
import top.milkbox.sys.modular.menu.param.SysMenuEditParam;
import top.milkbox.sys.modular.menu.param.SysMenuIdParam;
-import top.milkbox.sys.modular.menu.param.SysMenuPageParam;
import top.milkbox.sys.modular.menu.service.SysMenuService;
import top.milkbox.sys.modular.menu.vo.SysMenuVo;
import top.milkbox.common.pojo.CommonResult;
@@ -44,6 +42,7 @@ public class SysMenuController {
@Operation(summary = "添加", description = "添加一条数据")
@CommonLog(value = "添加", description = "添加一条数据",
module = SysConfiguration.MODULE_NAME, type = LogTypeEnum.INSERT)
+ @SaCheckPermission("/sysMenu/add")
public CommonResult