33 Commits

Author SHA1 Message Date
cuijiawang
ccfdd18516 岗位与通知增删改查 2025-09-23 16:55:39 +08:00
Hzm
c724387a16 satoken配置类测试还原 2025-09-22 15:09:56 +08:00
Hzm
ced025e292 通知公告的crud+分页查询 2025-09-22 15:08:53 +08:00
Hzm
37e264491f 岗位管理的crud+分页查询 2025-09-22 12:05:11 +08:00
Hzm
be4adbe00b 优化了LogAspect的代码 2025-09-21 19:57:31 +08:00
wol
33fc749363 uuid cost 2025-09-21 19:46:30 +08:00
Hzm
eb5978eb9d 定义了controller下的请求日志 2025-09-21 19:27:50 +08:00
cuijiawang
db7dbcc97e 菜单增删改查 2025-09-20 18:14:51 +08:00
cuijiawang
cba659010a fix 2025-09-19 17:57:19 +08:00
wol
0a8c05e1df addExclude /auth/register 2025-09-18 19:51:26 +08:00
cuijiawang
7a961d63a6 增加clientId 2025-09-18 18:00:32 +08:00
cuijiawang
16779413a6 登录 2025-09-18 11:42:29 +08:00
cuijiawang
c0c2e492b8 fix 2025-09-18 10:27:31 +08:00
cuijiawang
023d35fecd fix 2025-09-18 09:51:47 +08:00
wol
895ae7383a init 2025-09-14 00:54:48 +08:00
cuijiawang
0d12345ed4 yaml 配置数据库和redis链接 2025-08-28 14:37:34 +08:00
wol
e6c1041202 role part 2025-08-26 23:27:08 +08:00
wol
d7b36d9f59 user part 2025-08-26 00:05:22 +08:00
wol
fb81670776 menu 2025-08-25 00:09:18 +08:00
wol
f22189bc00 fix 2025-08-20 23:39:52 +08:00
cuijiawang
74d030a97e 1 2025-08-20 17:40:27 +08:00
cuijiawang
609bef8a87 gateway依赖 2025-08-20 09:37:52 +08:00
wol
0fa91a472d api 2025-08-19 22:47:54 +08:00
cuijiawang
bdd3f251a2 1 2025-08-19 18:02:47 +08:00
wol
4a0b1499c7 auth 2025-08-17 22:20:34 +08:00
wol
74064a4dc4 fix 2025-08-15 23:34:59 +08:00
wol
4c4f1acf32 1 2025-08-15 00:24:30 +08:00
cuijiawang
92246a6767 1 2025-08-14 18:02:58 +08:00
cuijiawang
6419f88c63 fix 2025-08-14 14:31:10 +08:00
cuijiawang
eee546fcc4 fix 2025-08-14 10:05:51 +08:00
wol
714b759050 config 2025-08-14 00:39:03 +08:00
cuijiawang
2d3024b3e7 1 2025-08-13 18:06:33 +08:00
cuijiawang
8b79ae515d mybatis yml 2025-08-13 11:40:21 +08:00
216 changed files with 8666 additions and 599 deletions

View File

@@ -14,11 +14,20 @@
<artifactId>wol-common-web</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-system-base</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-module-ai</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-satoken</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,70 @@
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
druid:
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: agileboot
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
dynamic:
primary: master
strict: false
druid:
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
datasource:
master:
url: jdbc:mysql://121.41.64.98:3306/agileboot?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8&sslMode=REQUIRED
username: agileboot
password: 123456
data:
redis:
database: 3
host: 121.41.64.98
port: 6379
password: 'Wyy123123'
sa-token:
# 是否输出操作日志
is-log: true
token-name: Authorization
jasypt:
encryptor:
password: ${JASYPT_ENCRYPTOR_PASSWORD:}

View File

@@ -1,4 +1,13 @@
server:
port: 8080
port: 8088
servlet:
context-path: /api
context-path: /api
tomcat:
uri-encoding: UTF-8 # tomcat的URI编码
accept-count: 1000 # 连接数满后的排队数默认为100
threads:
max: 800 # tomcat最大线程数默认为200
min-spare: 100 # Tomcat启动初始化的线程数默认值10
spring:
profiles:
active: dev

View File

@@ -17,6 +17,7 @@
<module>wol-common-mybatis</module>
<module>wol-common-redis</module>
<module>wol-common-json</module>
<module>wol-common-satoken</module>
</modules>
<packaging>pom</packaging>

View File

@@ -47,6 +47,11 @@
<artifactId>wol-common-json</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-satoken</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>

View File

@@ -85,7 +85,16 @@
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
<!--ENC加密-->
<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
<version>2.1.1</version>
</dependency>
<!-- swagger注解 -->
<dependency>

View File

@@ -45,11 +45,13 @@ public interface Constants {
* 通用成功标识
*/
String SUCCESS = "0";
String NORMAL = "0"; // 正常状态
/**
* 通用失败标识
*/
String FAIL = "1";
String DISABLE = "1"; // 异常/停用状态
/**
* 登录成功
@@ -80,6 +82,45 @@ public interface Constants {
* 顶级部门id
*/
Long TOP_PARENT_ID = 0L;
/**
* 超级管理员ID
*/
Long SUPER_ADMIN_ID = 1L;
/**
* 超级管理员角色 roleKey
*/
String SUPER_ADMIN_ROLE_KEY = "superadmin";
/**
* 租户管理员角色 roleKey
*/
String TENANT_ADMIN_ROLE_KEY = "admin";
/**
* 租户管理员角色名称
*/
String TENANT_ADMIN_ROLE_NAME = "管理员";
/**
* 默认租户ID
*/
String DEFAULT_TENANT_ID = "000000";
interface Cache {
/**
* 全局 redis key (业务无关的key)
*/
String GLOBAL_REDIS_KEY = "global:";
/**
* 登录账户密码错误次数 redis key
*/
String PWD_ERR_CNT_KEY = "pwd_err_cnt:";
/**
* 验证码 redis key
*/
String CAPTCHA_CODE_KEY = GLOBAL_REDIS_KEY + "captcha_codes:";
}
}

View File

@@ -18,7 +18,7 @@ public interface BasicEnum<T>{
* 获取枚举的描述
* @return 描述
*/
String description();
String getDesc();
}

View File

@@ -3,7 +3,6 @@ package com.agileboot.common.core.enums;
import cn.hutool.core.convert.Convert;
import com.agileboot.common.core.exception.ApiException;
import com.agileboot.common.core.exception.error.ErrorCode;
import com.agileboot.common.core.enums.BasicEnum;
import java.util.Objects;
@@ -55,7 +54,7 @@ public class BasicEnumUtil {
public static <E extends Enum<E>> String getDescriptionByValue(Class<E> enumClass, Object value) {
E basicEnum = fromValueSafely(enumClass, value);
if (basicEnum != null) {
return ((BasicEnum<?>) basicEnum).description();
return ((BasicEnum<?>) basicEnum).getDesc();
}
return UNKNOWN;
}

View File

@@ -10,6 +10,6 @@ public interface DictionaryEnum<T> extends BasicEnum<T> {
* 获取css标签
* @return css标签
*/
String cssTag();
String getCssTag();
}

View File

@@ -0,0 +1,44 @@
package com.agileboot.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 登录类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum LoginType {
/**
* 密码登录
*/
PASSWORD("user.password.retry.limit.exceed", "user.password.retry.limit.count"),
/**
* 短信登录
*/
SMS("sms.code.retry.limit.exceed", "sms.code.retry.limit.count"),
/**
* 邮箱登录
*/
EMAIL("email.code.retry.limit.exceed", "email.code.retry.limit.count"),
/**
* 小程序登录
*/
XCX("", "");
/**
* 登录重试超出限制提示
*/
final String retryLimitExceed;
/**
* 登录重试限制计数提示
*/
final String retryLimitCount;
}

View File

@@ -0,0 +1,39 @@
package com.agileboot.common.core.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.apache.commons.lang3.StringUtils;
/**
* 用户类型
*
* @author Lion Li
*/
@Getter
@AllArgsConstructor
public enum UserType {
/**
* 后台系统用户
*/
SYS_USER("sys_user"),
/**
* 移动客户端用户
*/
APP_USER("app_user");
/**
* 用户类型标识(用于 token、权限识别等
*/
private final String userType;
public static UserType getUserType(String str) {
for (UserType value : values()) {
if (StringUtils.contains(str, value.getUserType())) {
return value;
}
}
throw new RuntimeException("'UserType' not found By " + str);
}
}

View File

@@ -3,12 +3,16 @@ package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_operation_log的business_type
*
* @author valarchie
*/
@Getter
@AllArgsConstructor
@Dictionary(name = "sysOperationLog.businessType")
public enum BusinessTypeEnum implements DictionaryEnum<Integer> {
@@ -26,29 +30,7 @@ public enum BusinessTypeEnum implements DictionaryEnum<Integer> {
CLEAN(8, "清空", CssTag.DANGER),
;
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
BusinessTypeEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -1,14 +1,18 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_user的sex字段
*
* @author valarchie
*/
@Getter
@AllArgsConstructor
@Dictionary(name = "sysUser.sex")
public enum GenderEnum implements DictionaryEnum<Integer> {
@@ -19,29 +23,7 @@ public enum GenderEnum implements DictionaryEnum<Integer> {
FEMALE(2, "", CssTag.PRIMARY),
UNKNOWN(0, "未知", CssTag.PRIMARY);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
GenderEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -1,15 +1,20 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 用户状态
*
* @author valarchie
*/
// TODO 表记得改成LoginLog
@Dictionary(name = "sysLoginLog.status")
@Getter
@AllArgsConstructor
public enum LoginStatusEnum implements DictionaryEnum<Integer> {
/**
* status of user
@@ -19,28 +24,7 @@ public enum LoginStatusEnum implements DictionaryEnum<Integer> {
REGISTER(3, "注册", CssTag.PRIMARY),
LOGIN_FAIL(0, "登录失败", CssTag.DANGER);
private final int value;
private final String msg;
private final Integer value;
private final String desc;
private final String cssTag;
LoginStatusEnum(int status, String msg, String cssTag) {
this.value = status;
this.msg = msg;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return msg;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -1,36 +1,25 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.BasicEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
* @author valarchie
*/
@Deprecated
@Getter
@AllArgsConstructor
public enum MenuComponentEnum implements BasicEnum<Integer> {
/**
* 菜单组件类型
*/
LAYOUT(1,"Layout"),
PARENT_VIEW(2,"ParentView"),
INNER_LINK(3,"InnerLink");
LAYOUT(1, "Layout"),
PARENT_VIEW(2, "ParentView"),
INNER_LINK(3, "InnerLink");
private final int value;
private final String description;
private final Integer value;
private final String desc;
MenuComponentEnum(int value, String description) {
this.value = value;
this.description = description;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
}

View File

@@ -1,11 +1,15 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.BasicEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* @author valarchie
* 对应 sys_menu表的menu_type字段
*/
@Getter
@AllArgsConstructor
public enum MenuTypeEnum implements BasicEnum<Integer> {
/**
@@ -16,23 +20,7 @@ public enum MenuTypeEnum implements BasicEnum<Integer> {
IFRAME(3, "内嵌Iframe"),
OUTSIDE_LINK_REDIRECT(4, "外链跳转");
private final int value;
private final String description;
MenuTypeEnum(int value, String description) {
this.value = value;
this.description = description;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
private final Integer value;
private final String desc;
}

View File

@@ -1,14 +1,19 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_notice的 status字段
*
* @author valarchie
*/
@Dictionary(name = "sysNotice.status")
@Getter
@AllArgsConstructor
public enum NoticeStatusEnum implements DictionaryEnum<Integer> {
/**
@@ -17,29 +22,8 @@ public enum NoticeStatusEnum implements DictionaryEnum<Integer> {
OPEN(1, "正常", CssTag.PRIMARY),
CLOSE(0, "关闭", CssTag.DANGER);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
NoticeStatusEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -1,16 +1,21 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_notice的 notice_type字段
* 名称一般由对应的表名.字段构成
* 全局的话使用common作为表名
*
* @author valarchie
*/
@Dictionary(name = "sysNotice.noticeType")
@Getter
@AllArgsConstructor
public enum NoticeTypeEnum implements DictionaryEnum<Integer> {
/**
@@ -19,29 +24,16 @@ public enum NoticeTypeEnum implements DictionaryEnum<Integer> {
NOTIFICATION(1, "通知", CssTag.WARNING),
ANNOUNCEMENT(2, "公告", CssTag.SUCCESS);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
NoticeTypeEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
public static Integer getDescByValue(Integer value) {
for (NoticeTypeEnum item : values()) {
if (item.value.equals(value)) {
return item.value;
}
}
return null;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -1,14 +1,19 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_operation_log的status字段
*
* @author valarchie
*/
@Dictionary(name = "sysOperationLog.status")
@Getter
@AllArgsConstructor
public enum OperationStatusEnum implements DictionaryEnum<Integer> {
/**
@@ -17,29 +22,8 @@ public enum OperationStatusEnum implements DictionaryEnum<Integer> {
SUCCESS(1, "成功", CssTag.PRIMARY),
FAIL(0, "失败", CssTag.DANGER);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
OperationStatusEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -1,12 +1,17 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.BasicEnum;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 操作者类型
*
* @author valarchie
*/
@Getter
@AllArgsConstructor
@Dictionary(name = "sysOperationLog.operatorType")
public enum OperatorTypeEnum implements BasicEnum<Integer> {
@@ -17,23 +22,8 @@ public enum OperatorTypeEnum implements BasicEnum<Integer> {
WEB(2, "Web用户"),
MOBILE(3, "手机端用户");
private final int value;
private final String description;
OperatorTypeEnum(int value, String description) {
this.value = value;
this.description = description;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
private final Integer value;
private final String desc;
}

View File

@@ -1,11 +1,16 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.BasicEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* Http Method
*
* @author valarchie
*/
@Getter
@AllArgsConstructor
public enum RequestMethodEnum implements BasicEnum<Integer> {
/**
@@ -17,23 +22,8 @@ public enum RequestMethodEnum implements BasicEnum<Integer> {
DELETE(4, "DELETE"),
UNKNOWN(-1, "UNKNOWN");
private final int value;
private final String description;
RequestMethodEnum(int value, String description) {
this.value = value;
this.description = description;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
private final Integer value;
private final String desc;
}

View File

@@ -1,14 +1,19 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 除非表有特殊指明的话,一般用这个枚举代表 status字段
*
* @author valarchie
*/
@Dictionary(name = "common.status")
@Getter
@AllArgsConstructor
public enum StatusEnum implements DictionaryEnum<Integer> {
/**
* 开关状态
@@ -16,29 +21,17 @@ public enum StatusEnum implements DictionaryEnum<Integer> {
ENABLE(1, "正常", CssTag.PRIMARY),
DISABLE(0, "停用", CssTag.DANGER);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
StatusEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
public static Integer getDescByValue(Integer value) {
for (StatusEnum item : StatusEnum.values()) {
if (item.value.equals(value)) {
return item.value;
}
}
return null;
}
}

View File

@@ -1,13 +1,18 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_user的status字段
*
* @author valarchie
*/
@AllArgsConstructor
@Getter
@Dictionary(name = "sysUser.status")
public enum UserStatusEnum implements DictionaryEnum<Integer> {
@@ -18,32 +23,7 @@ public enum UserStatusEnum implements DictionaryEnum<Integer> {
DISABLED(2, "禁用", CssTag.DANGER),
FROZEN(3, "冻结", CssTag.WARNING);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
UserStatusEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
public Integer getValue() {
return value;
}
@Override
public String description() {
return this.description;
}
public String getDescription() {
return description;
}
@Override
public String cssTag() {
return null;
}
}

View File

@@ -1,15 +1,20 @@
package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import com.agileboot.common.core.enums.DictionaryEnum;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 对应sys_menu表的is_visible字段
*
* @author valarchie
*/
@Deprecated
@Dictionary(name = "sysMenu.isVisible")
@Getter
@AllArgsConstructor
public enum VisibleStatusEnum implements DictionaryEnum<Integer> {
/**
@@ -18,29 +23,8 @@ public enum VisibleStatusEnum implements DictionaryEnum<Integer> {
SHOW(1, "显示", CssTag.PRIMARY),
HIDE(0, "隐藏", CssTag.DANGER);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
VisibleStatusEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -3,12 +3,17 @@ package com.agileboot.common.core.enums.common;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.dictionary.CssTag;
import com.agileboot.common.core.enums.dictionary.Dictionary;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
* 系统内代表是与否的枚举
*
* @author valarchie
*/
@Dictionary(name = "common.yesOrNo")
@Getter
@AllArgsConstructor
public enum YesOrNoEnum implements DictionaryEnum<Integer> {
/**
* 是与否
@@ -16,31 +21,8 @@ public enum YesOrNoEnum implements DictionaryEnum<Integer> {
YES(1, "", CssTag.PRIMARY),
NO(0, "", CssTag.DANGER);
private final int value;
private final String description;
private final Integer value;
private final String desc;
private final String cssTag;
YesOrNoEnum(int value, String description, String cssTag) {
this.value = value;
this.description = description;
this.cssTag = cssTag;
}
@Override
public Integer getValue() {
return value;
}
@Override
public String description() {
return description;
}
@Override
public String cssTag() {
return cssTag;
}
}

View File

@@ -0,0 +1,72 @@
package com.agileboot.common.core.enums.dictionary;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import com.agileboot.common.core.enums.DictionaryEnum;
import com.agileboot.common.core.enums.common.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 本地一级缓存 使用Map
*
* @author valarchie
*/
public class DictCache {
private static final Map<String, List<DictionaryData>> DICTIONARY_CACHE = MapUtil.newHashMap(128);
private DictCache() {
}
static {
initDictionaryCache();
}
private static void initDictionaryCache() {
// TODO 这个可以做成自动扫描
loadInCache(BusinessTypeEnum.values());
loadInCache(YesOrNoEnum.values());
loadInCache(StatusEnum.values());
loadInCache(GenderEnum.values());
loadInCache(NoticeStatusEnum.values());
loadInCache(NoticeTypeEnum.values());
loadInCache(OperationStatusEnum.values());
loadInCache(LoginStatusEnum.values());
loadInCache(VisibleStatusEnum.values());
loadInCache(UserStatusEnum.values());
}
public static Map<String, List<DictionaryData>> dictionaryCache() {
return DICTIONARY_CACHE;
}
private static void loadInCache(DictionaryEnum[] dictionaryEnums) {
DICTIONARY_CACHE.put(getDictionaryName(dictionaryEnums[0].getClass()), arrayToList(dictionaryEnums));
}
private static String getDictionaryName(Class<?> clazz) {
Objects.requireNonNull(clazz);
Dictionary annotation = clazz.getAnnotation(Dictionary.class);
Objects.requireNonNull(annotation);
return annotation.name();
}
@SuppressWarnings("rawtypes")
private static List<DictionaryData> arrayToList(DictionaryEnum[] dictionaryEnums) {
if(ArrayUtil.isEmpty(dictionaryEnums)) {
return ListUtil.empty();
}
return Arrays.stream(dictionaryEnums).map(DictionaryData::new).collect(Collectors.toList());
}
}

View File

@@ -17,9 +17,9 @@ public class DictionaryData {
@SuppressWarnings("rawtypes")
public DictionaryData(DictionaryEnum enumType) {
if (enumType != null) {
this.label = enumType.description();
this.label = enumType.getDesc();
this.value = (Integer) enumType.getValue();
this.cssTag = enumType.cssTag();
this.cssTag = enumType.getCssTag();
}
}

View File

@@ -1,6 +1,7 @@
package com.agileboot.common.core.exception;
import cn.hutool.core.text.StrFormatter;
import com.agileboot.common.core.exception.error.ErrorCodeInterface;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
@@ -34,6 +35,11 @@ public class BizException extends RuntimeException {
this.message = message;
}
public BizException(ErrorCodeInterface errorCode) {
this.code = errorCode.code();
this.message = errorCode.message();
}
public BizException(String message, Integer code) {
this.message = message;
this.code = code;
@@ -42,4 +48,8 @@ public class BizException extends RuntimeException {
public BizException(String message, Object... args) {
this.message = StrFormatter.format(message, args);
}
public BizException(ErrorCodeInterface errorCode, Object... args) {
this.message = StrFormatter.format(errorCode.message(), args);
}
}

View File

@@ -166,7 +166,7 @@ public enum ErrorCode implements ErrorCodeInterface {
ROLE_DATA_SCOPE_DUPLICATED_DEPT(11003, "重复的部门id", "Business.ROLE_DATA_SCOPE_DUPLICATED_DEPT"),
ROLE_ALREADY_ASSIGN_TO_USER(11004, "角色已分配给用户,请先取消分配,再删除角色", "Business.ROLE_ALREADY_ASSIGN_TO_USER"),
ROLE_ALREADY_ASSIGN_TO_USER(11004, "角色{} 已分配给用户,请先取消分配,再删除角色", "Business.ROLE_ALREADY_ASSIGN_TO_USER"),
ROLE_IS_NOT_AVAILABLE(11005, "角色:{} 已禁用,无法分配给用户", "Business.ROLE_IS_NOT_AVAILABLE"),

View File

@@ -0,0 +1,36 @@
package com.agileboot.common.core.utils;
import cn.hutool.extra.spring.SpringUtil;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ConstraintViolationException;
import jakarta.validation.Validator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import java.util.Set;
/**
* Validator 校验框架工具
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class ValidatorUtils {
private static final Validator VALID = SpringUtil.getBean(Validator.class);
/**
* 对给定对象进行参数校验,并根据指定的校验组进行校验
*
* @param object 要进行校验的对象
* @param groups 校验组
* @throws ConstraintViolationException 如果校验不通过,则抛出参数校验异常
*/
public static <T> void validate(T object, Class<?>... groups) {
Set<ConstraintViolation<T>> validate = VALID.validate(object, groups);
if (!validate.isEmpty()) {
throw new ConstraintViolationException("参数校验异常", validate);
}
}
}

View File

@@ -20,6 +20,29 @@
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-jsqlparser</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot3-starter</artifactId>
<version>${dynamic-ds.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.20</version>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-satoken</artifactId>
</dependency>
</dependencies>
</project>
</project>

View File

@@ -1,8 +1,12 @@
package com.agileboot.common.mybatis.config;
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
import com.agileboot.common.mybatis.handler.InjectionMetaObjectHandler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.BlockAttackInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
@@ -16,9 +20,23 @@ public class MybatisPlusConfiguration {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件 自动识别数据库类型
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setOverflow(true); // 超出总页数后回到首页
interceptor.addInnerInterceptor(paginationInnerInterceptor);
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 阻止恶意的全表更新删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
/**
* 元对象字段填充控制器
*/
@Bean
public MetaObjectHandler metaObjectHandler() {
return new InjectionMetaObjectHandler();
}
}

View File

@@ -1,7 +1,6 @@
package com.agileboot.common.mybatis.core.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.*;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@@ -33,8 +32,8 @@ public class BaseEntity implements Serializable {
/**
* 创建部门
*/
@TableField(fill = FieldFill.INSERT)
private Long createDept;
// @TableField(fill = FieldFill.INSERT)
// private Long createDept;
/**
* 创建者
@@ -60,6 +59,10 @@ public class BaseEntity implements Serializable {
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
@TableLogic
@TableField(fill = FieldFill.INSERT)
private Integer deleted;
/**
* 请求参数
*/

View File

@@ -1,15 +1,17 @@
package com.agileboot.common.mybatis.core.page;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.exception.ServiceException;
import com.agileboot.common.core.utils.sql.SqlUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.OrderItem;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.Max;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import java.io.Serial;
@@ -24,45 +26,42 @@ import java.util.List;
*/
@Data
@NoArgsConstructor
public class PageQuery implements Serializable {
public abstract class PageQuery<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
public static final int MAX_PAGE_NUM = 200; // 最大分页页数
public static final int MAX_PAGE_SIZE = 500; // 单页最大大小
public static final int DEFAULT_PAGE_NUM = 1; // 默认分页页数
public static final int DEFAULT_PAGE_SIZE = 10; // 默认分页大小
/**
* 分页大小
*/
private Integer pageSize;
@Max(MAX_PAGE_SIZE)
protected Integer pageSize;
/**
* 当前页数
*/
private Integer pageNum;
@Max(MAX_PAGE_NUM)
protected Integer pageNum;
/**
* 排序列
*/
private String orderByColumn;
protected String orderByColumn;
/**
* 排序的方向desc或者asc
*/
private String isAsc;
/**
* 当前记录起始索引 默认值
*/
public static final int DEFAULT_PAGE_NUM = 1;
/**
* 每页显示记录数 默认值 默认查全部
*/
public static final int DEFAULT_PAGE_SIZE = Integer.MAX_VALUE;
protected String isAsc = "desc";
/**
* 构建分页对象
*/
public <T> Page<T> build() {
public <T> Page<T> toPage() {
Integer pageNum = ObjectUtil.defaultIfNull(getPageNum(), DEFAULT_PAGE_NUM);
Integer pageSize = ObjectUtil.defaultIfNull(getPageSize(), DEFAULT_PAGE_SIZE);
if (pageNum <= 0) {
@@ -70,7 +69,7 @@ public class PageQuery implements Serializable {
}
Page<T> page = new Page<>(pageNum, pageSize);
List<OrderItem> orderItems = buildOrderItem();
if (CollUtil.isNotEmpty(orderItems)) {
if (CollectionUtils.isNotEmpty(orderItems)) {
page.addOrder(orderItems);
}
return page;
@@ -127,4 +126,5 @@ public class PageQuery implements Serializable {
this.pageNum = pageNum;
}
public abstract LambdaQueryWrapper<T> toQueryWrapper();
}

View File

@@ -0,0 +1,69 @@
package com.agileboot.common.mybatis.core.page;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import org.apache.commons.collections4.CollectionUtils;
import java.util.List;
/**
* 分页模型类
*
* @author valarchie
*/
@Data
public class PageR<T> {
private List<T> rows; // 列表数据
private Long total; // 总记录数
private Long current; // 当前页数
private Long size; // 每页记录数
private Long pages; // 总页数
private int code;
private String msg;
public PageR() {
this.code = HttpStatus.HTTP_OK;
this.msg = "查询成功";
this.total = 0L;
this.current = 0L;
this.size = 0L;
this.pages = 0L;
}
public PageR(List<T> list) {
this.code = HttpStatus.HTTP_OK;
this.rows = list;
this.total = (long) list.size();
this.msg = "查询成功";
}
public PageR(IPage<T> page) {
this.code = HttpStatus.HTTP_OK;
this.msg = "查询成功";
this.rows = page.getRecords();
this.total = page.getTotal();
this.current = page.getCurrent();
this.size = page.getSize();
this.pages = page.getPages();
}
public PageR(IPage<?> page, List<T> list) {
this.code = HttpStatus.HTTP_OK;
this.msg = "查询成功";
if (CollectionUtils.isEmpty(list)) {
this.total = 0L;
this.current = 0L;
this.size = 0L;
this.pages = 0L;
} else {
this.rows = list;
this.total = page.getTotal();
this.current = page.getCurrent();
this.size = page.getSize();
this.pages = page.getPages();
}
}
}

View File

@@ -1,107 +0,0 @@
package com.agileboot.common.mybatis.core.page;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
/**
* 表格分页数据对象
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class TableDataInfo<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 总记录数
*/
private long total;
/**
* 列表数据
*/
private List<T> rows;
/**
* 消息状态码
*/
private int code;
/**
* 消息内容
*/
private String msg;
/**
* 分页
*
* @param list 列表数据
* @param total 总记录数
*/
public TableDataInfo(List<T> list, long total) {
this.rows = list;
this.total = total;
this.code = HttpStatus.HTTP_OK;
this.msg = "查询成功";
}
/**
* 根据分页对象构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build(IPage<T> page) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(page.getRecords());
rspData.setTotal(page.getTotal());
return rspData;
}
/**
* 根据数据列表构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build(List<T> list) {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
rspData.setRows(list);
rspData.setTotal(list.size());
return rspData;
}
/**
* 构建表格分页数据对象
*/
public static <T> TableDataInfo<T> build() {
TableDataInfo<T> rspData = new TableDataInfo<>();
rspData.setCode(HttpStatus.HTTP_OK);
rspData.setMsg("查询成功");
return rspData;
}
/**
* 根据原始数据列表和分页参数,构建表格分页数据对象(用于假分页)
*
* @param list 原始数据列表(全部数据)
* @param page 分页参数对象(包含当前页码、每页大小等)
* @return 构造好的分页结果 TableDataInfo<T>
*/
public static <T> TableDataInfo<T> build(List<T> list, IPage<T> page) {
if (CollUtil.isEmpty(list)) {
return TableDataInfo.build();
}
List<T> pageList = CollUtil.page((int) page.getCurrent() - 1, (int) page.getSize(), list);
return new TableDataInfo<>(pageList, list.size());
}
}

View File

@@ -0,0 +1,110 @@
package com.agileboot.common.mybatis.handler;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpStatus;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.mybatis.core.domain.BaseEntity;
import com.agileboot.common.satoken.pojo.LoginUser;
import com.agileboot.common.satoken.utils.LoginHelper;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.ibatis.reflection.MetaObject;
import java.util.Date;
/**
* MP注入处理器
*
* @author Lion Li
*/
@Slf4j
public class InjectionMetaObjectHandler implements MetaObjectHandler {
/**
* 如果用户不存在默认注入-1代表无用户
*/
private static final Long DEFAULT_USER_ID = -1L;
/**
* 插入填充方法,用于在插入数据时自动填充实体对象中的创建时间、更新时间、创建人、更新人等信息
*
* @param metaObject 元对象,用于获取原始对象并进行填充
*/
@Override
public void insertFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
Date current = ObjectUtils.defaultIfNull(baseEntity.getCreateTime(), new Date());
baseEntity.setCreateTime(current);
baseEntity.setUpdateTime(current);
baseEntity.setDeleted(0);
// 如果创建人为空,则填充当前登录用户的信息
if (ObjectUtil.isNull(baseEntity.getCreateBy())) {
LoginUser loginUser = getLoginUser();
if (ObjectUtil.isNotNull(loginUser)) {
Long userId = loginUser.getUserId();
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(userId);
baseEntity.setUpdateBy(userId);
} else {
// 填充创建人、更新人和创建部门信息
baseEntity.setCreateBy(DEFAULT_USER_ID);
baseEntity.setUpdateBy(DEFAULT_USER_ID);
}
}
} else {
Date date = new Date();
this.strictInsertFill(metaObject, "createTime", Date.class, date);
this.strictInsertFill(metaObject, "updateTime", Date.class, date);
}
} catch (Exception e) {
throw new BizException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
}
}
/**
* 更新填充方法,用于在更新数据时自动填充实体对象中的更新时间和更新人信息
*
* @param metaObject 元对象,用于获取原始对象并进行填充
*/
@Override
public void updateFill(MetaObject metaObject) {
try {
if (ObjectUtil.isNotNull(metaObject) && metaObject.getOriginalObject() instanceof BaseEntity baseEntity) {
// 获取当前时间作为更新时间,无论原始对象中的更新时间是否为空都填充
Date current = new Date();
baseEntity.setUpdateTime(current);
// 获取当前登录用户的ID并填充更新人信息
Long userId = LoginHelper.getUserId();
if (ObjectUtil.isNotNull(userId)) {
baseEntity.setUpdateBy(userId);
} else {
baseEntity.setUpdateBy(DEFAULT_USER_ID);
}
} else {
this.strictUpdateFill(metaObject, "updateTime", Date.class, new Date());
}
} catch (Exception e) {
throw new BizException("自动注入异常 => " + e.getMessage(), HttpStatus.HTTP_UNAUTHORIZED);
}
}
/**
* 获取当前登录用户信息
*
* @return 当前登录用户的信息,如果用户未登录则返回 null
*/
private LoginUser getLoginUser() {
LoginUser loginUser;
try {
loginUser = LoginHelper.getLoginUser();
} catch (Exception e) {
return null;
}
return loginUser;
}
}

View File

@@ -2,6 +2,8 @@
# MyBatisPlus配置
# https://baomidou.com/config/
mybatis-plus:
mapper-locations: classpath*:com/agileboot/**/mapper/xml/*Mapper.xml
mapperPackage: com.agileboot.**.mapper*
# 启动时是否检查 MyBatis XML 文件的存在,默认不检查
checkConfigLocation: false
configuration:
@@ -16,11 +18,13 @@ mybatis-plus:
# 更详细的日志输出 会有性能损耗 org.apache.ibatis.logging.stdout.StdOutImpl
# 关闭日志记录 (可单纯使用 p6spy 分析) org.apache.ibatis.logging.nologging.NoLoggingImpl
# 默认日志输出 org.apache.ibatis.logging.slf4j.Slf4jImpl
logImpl: org.apache.ibatis.logging.nologging.NoLoggingImpl
logImpl: org.apache.ibatis.logging.stdout.StdOutImpl
global-config:
banner: true # 是否打印 Logo banner
dbConfig:
idType: ASSIGN_ID # 主键类型: AUTO 自增 NONE 空 INPUT 用户输入 ASSIGN_ID 雪花 ASSIGN_UUID 唯一 UUID
table-underline: true # 默认数据库表下划线命名
logic-delete-field: deleted # 全局逻辑删除字段名
logicDeleteValue: 1 # 逻辑已删除值(框架表均使用此值 禁止随意修改)
logicNotDeleteValue: 0 # 逻辑未删除值
insertStrategy: NOT_NULL

View File

@@ -27,7 +27,10 @@
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redisson-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>

View File

@@ -42,7 +42,7 @@ public class RedissonConfiguration {
@Autowired
private RedissonProperties redissonProperties;
@Bean
// @Bean
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
return config -> {
JavaTimeModule javaTimeModule = new JavaTimeModule();

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-common</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>wol-common-satoken</artifactId>
<dependencies>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-core</artifactId>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>${satoken.version}</version>
</dependency>
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>${satoken.version}</version>
</dependency>
<!-- Sa-Token 整合 RedisTemplate -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-redis-template</artifactId>
<version>${satoken.version}</version>
</dependency>
<!-- 提供 Redis 连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,52 @@
package com.agileboot.common.satoken.config;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import com.agileboot.common.core.factory.YmlPropertySourceFactory;
import com.agileboot.common.satoken.handler.SaTokenExceptionHandler;
import com.agileboot.common.satoken.service.SaPermissionImpl;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
/**
* Sa-Token 配置
*
* @author Lion Li
*/
@AutoConfiguration
@PropertySource(value = "classpath:common-satoken.yml", factory = YmlPropertySourceFactory.class)
public class SaTokenConfiguration {
// Sa-Token 整合 jwt (Simple 简单模式)
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForSimple();
}
/**
* 权限接口实现(使用bean注入方便用户替换)
*/
@Bean
public StpInterface stpInterface() {
return new SaPermissionImpl();
}
/**
* 自定义dao层存储
*/
// @Bean
// public SaTokenDao saTokenDao() {
// return new PlusSaTokenDao();
// }
/**
* 异常处理器
*/
@Bean
public SaTokenExceptionHandler saTokenExceptionHandler() {
return new SaTokenExceptionHandler();
}
}

View File

@@ -0,0 +1,82 @@
package com.agileboot.common.satoken.config;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.router.SaRouter;
import cn.dev33.satoken.same.SaSameUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
import com.agileboot.common.core.constant.HttpStatus;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 权限安全配置
*
* @author Lion Li
*/
@AutoConfiguration
public class SaTokenMvcConfiguration implements WebMvcConfigurer {
/**
* 注册sa-token的拦截器
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义验证规则
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
}
/**
* 注册 [Sa-Token全局过滤器]
*/
@Bean
@ConditionalOnMissingClass("cn.dev33.satoken.reactor.spring.SaTokenContextRegister")
public SaServletFilter getGlobleSaServletFilter() {
return new SaServletFilter()
.addInclude("/**").addExclude("/favicon.ico")
.addExclude("/auth/getConfig", "/captcha/code", "/auth/register")
.setAuth(obj -> {
SaRouter.match("/**", "/auth/login", StpUtil::checkLogin);
})
.setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}
/**
* 校验是否从网关转发
*/
@Bean
@ConditionalOnMissingBean(SaServletFilter.class)
public SaServletFilter getSaServletFilter() {
return new SaServletFilter()
.addInclude("/**")
.addExclude("/actuator", "/actuator/**")
.setAuth(obj -> {
if (SaManager.getConfig().getCheckSameToken()) {
SaSameUtil.checkCurrentRequestToken();
}
})
.setError(e -> SaResult.error("认证失败,无法访问系统资源").setCode(HttpStatus.UNAUTHORIZED));
}
/**
* 对 actuator 健康检查接口 做账号密码鉴权
*/
// @Bean
// public SaServletFilter actuatorFilter() {
// String username = SpringUtil.getProperty("spring.cloud.nacos.discovery.metadata.username");
// String password = SpringUtil.getProperty("spring.cloud.nacos.discovery.metadata.userpassword");
// return new SaServletFilter()
// .addInclude("/actuator", "/actuator/**")
// .setAuth(obj -> {
// SaHttpBasicUtil.check(username + ":" + password);
// })
// .setError(e -> SaResult.error(e.getMessage()).setCode(HttpStatus.UNAUTHORIZED));
// }
}

View File

@@ -0,0 +1,52 @@
package com.agileboot.common.satoken.handler;
import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.http.HttpStatus;
import com.agileboot.common.core.core.R;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* SaToken异常处理器
*
* @author Lion Li
*/
@Slf4j
@RestControllerAdvice
public class SaTokenExceptionHandler {
/**
* 权限码异常
*/
@ExceptionHandler(NotPermissionException.class)
public R<Void> handleNotPermissionException(NotPermissionException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',权限码校验失败'{}'", requestURI, e.getMessage());
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 角色权限异常
*/
@ExceptionHandler(NotRoleException.class)
public R<Void> handleNotRoleException(NotRoleException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',角色权限校验失败'{}'", requestURI, e.getMessage());
return R.fail(HttpStatus.HTTP_FORBIDDEN, "没有访问权限,请联系管理员授权");
}
/**
* 认证失败
*/
@ExceptionHandler(NotLoginException.class)
public R<Void> handleNotLoginException(NotLoginException e, HttpServletRequest request) {
String requestURI = request.getRequestURI();
log.error("请求地址'{}',认证失败'{}',无法访问系统资源", requestURI, e.getMessage());
return R.fail(HttpStatus.HTTP_UNAUTHORIZED, "2认证失败无法访问系统资源");
}
}

View File

@@ -0,0 +1,157 @@
package com.agileboot.common.satoken.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
/**
* 用户信息
*
* @author ruoyi
*/
@Data
@NoArgsConstructor
public class LoginUser implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 租户ID
*/
private String tenantId;
/**
* 用户ID
*/
private Long userId;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门类别编码
*/
private String deptCategory;
/**
* 部门名
*/
private String deptName;
/**
* 用户唯一标识
*/
private String token;
/**
* 用户类型
*/
private String userType;
/**
* 登录时间
*/
private Long loginTime;
/**
* 过期时间
*/
private Long expireTime;
/**
* 登录IP地址
*/
private String ipaddr;
/**
* 登录地点
*/
private String loginLocation;
/**
* 浏览器类型
*/
private String browser;
/**
* 操作系统
*/
private String os;
/**
* 菜单权限
*/
private Set<String> menuPermission;
/**
* 角色权限
*/
private Set<String> rolePermission;
/**
* 用户名
*/
private String username;
/**
* 用户昵称
*/
private String nickname;
/**
* 密码
*/
private String password;
/**
* 角色对象
*/
private List<RoleDTO> roles;
/**
* 岗位对象
*/
private List<PostDTO> posts;
/**
* 数据权限 当前角色ID
*/
private Long roleId;
/**
* 客户端
*/
private String clientKey;
/**
* 设备类型
*/
private String deviceType;
/**
* 是否是超级管理员
*/
private Integer isAdmin;
private String clientId;
/**
* 获取登录id
*/
public String getLoginId() {
if (userType == null) {
throw new IllegalArgumentException("用户类型不能为空");
}
if (userId == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
return userType + ":" + userId;
}
}

View File

@@ -0,0 +1,46 @@
package com.agileboot.common.satoken.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 岗位
*
* @author AprilWind
*/
@Data
@NoArgsConstructor
public class PostDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 岗位ID
*/
private Long postId;
/**
* 部门id
*/
private Long deptId;
/**
* 岗位编码
*/
private String postCode;
/**
* 岗位名称
*/
private String postName;
/**
* 岗位类别编码
*/
private String postCategory;
}

View File

@@ -0,0 +1,42 @@
package com.agileboot.common.satoken.pojo;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
/**
* 角色
*
* @author Lion Li
*/
@Data
@NoArgsConstructor
public class RoleDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 角色ID
*/
private Long roleId;
/**
* 角色名称
*/
private String roleName;
/**
* 角色权限
*/
private String roleKey;
/**
* 数据范围1全部数据权限 2自定数据权限 3本部门数据权限 4本部门及以下数据权限 5仅本人数据权限 6部门及以下或本人数据权限
*/
private String dataScope;
}

View File

@@ -0,0 +1,30 @@
package com.agileboot.common.satoken.service;
import cn.dev33.satoken.stp.StpInterface;
import java.util.ArrayList;
import java.util.List;
/**
* sa-token 权限管理实现类
*
* @author Lion Li
*/
public class SaPermissionImpl implements StpInterface {
/**
* 获取菜单权限列表
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return new ArrayList<>();
}
/**
* 获取角色权限列表
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return new ArrayList<>();
}
}

View File

@@ -0,0 +1,216 @@
package com.agileboot.common.satoken.utils;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.stp.parameter.SaLoginParameter;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import com.agileboot.common.core.constant.Constants;
import com.agileboot.common.core.enums.UserType;
import com.agileboot.common.satoken.pojo.LoginUser;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import java.util.Set;
/**
* 登录鉴权助手
* <p>
* user_type 为 用户类型 同一个用户表 可以有多种用户类型 例如 pc,app
* deivce 为 设备类型 同一个用户类型 可以有 多种设备类型 例如 web,ios
* 可以组成 用户类型与设备类型多对多的 权限灵活控制
* <p>
* 多用户体系 针对 多种用户类型 但权限控制不一致
* 可以组成 多用户类型表与多设备类型 分别控制权限
*
* @author Lion Li
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class LoginHelper {
public static final String LOGIN_USER_KEY = "loginUser";
public static final String TENANT_KEY = "tenantId";
public static final String USER_KEY = "userId";
public static final String USER_NAME_KEY = "userName";
public static final String DEPT_KEY = "deptId";
public static final String DEPT_NAME_KEY = "deptName";
public static final String DEPT_CATEGORY_KEY = "deptCategory";
public static final String CLIENT_KEY = "clientid";
/**
* 登录系统 基于 设备类型
* 针对相同用户体系不同设备
*
* @param loginUser 登录用户信息
* @param model 配置参数
*/
public static void login(LoginUser loginUser, SaLoginParameter model) {
model = ObjectUtil.defaultIfNull(model, new SaLoginParameter());
StpUtil.login(loginUser.getLoginId(),
model.setExtra(TENANT_KEY, loginUser.getTenantId())
.setExtra(USER_KEY, loginUser.getUserId())
.setExtra(USER_NAME_KEY, loginUser.getUsername())
.setExtra(DEPT_KEY, loginUser.getDeptId())
.setExtra(DEPT_NAME_KEY, loginUser.getDeptName())
.setExtra(DEPT_CATEGORY_KEY, loginUser.getDeptCategory())
);
StpUtil.getTokenSession().set(LOGIN_USER_KEY, loginUser);
}
/**
* 获取用户(多级缓存)
*/
@SuppressWarnings("unchecked cast")
public static <T extends LoginUser> T getLoginUser() {
SaSession session = StpUtil.getTokenSession();
if (ObjectUtil.isNull(session)) {
return null;
}
return (T) session.get(LOGIN_USER_KEY);
}
/**
* 获取用户基于token
*/
@SuppressWarnings("unchecked cast")
public static <T extends LoginUser> T getLoginUser(String token) {
SaSession session = StpUtil.getTokenSessionByToken(token);
if (ObjectUtil.isNull(session)) {
return null;
}
return (T) session.get(LOGIN_USER_KEY);
}
/**
* 获取用户id
*/
public static Long getUserId() {
return Convert.toLong(getExtra(USER_KEY));
}
/**
* 获取用户id
*/
public static String getUserIdStr() {
return Convert.toStr(getExtra(USER_KEY));
}
/**
* 获取用户账户
*/
public static String getUsername() {
return Convert.toStr(getExtra(USER_NAME_KEY));
}
/**
* 获取租户ID
*/
public static String getTenantId() {
return Convert.toStr(getExtra(TENANT_KEY));
}
/**
* 获取部门ID
*/
public static Long getDeptId() {
return Convert.toLong(getExtra(DEPT_KEY));
}
/**
* 获取部门名
*/
public static String getDeptName() {
return Convert.toStr(getExtra(DEPT_NAME_KEY));
}
/**
* 获取部门类别编码
*/
public static String getDeptCategory() {
return Convert.toStr(getExtra(DEPT_CATEGORY_KEY));
}
/**
* 获取当前 Token 的扩展信息
*
* @param key 键值
* @return 对应的扩展数据
*/
private static Object getExtra(String key) {
try {
return StpUtil.getExtra(key);
} catch (Exception e) {
return null;
}
}
/**
* 获取用户类型
*/
public static UserType getUserType() {
String loginType = StpUtil.getLoginIdAsString();
return UserType.getUserType(loginType);
}
/**
* 是否为超级管理员
*
* @param userId 用户ID
* @return 结果
*/
public static boolean isSuperAdmin(Long userId) {
return Constants.SUPER_ADMIN_ID.equals(userId);
}
/**
* 是否为超级管理员
*
* @return 结果
*/
public static boolean isSuperAdmin() {
return isSuperAdmin(getUserId());
}
/**
* 是否为租户管理员
*
* @param rolePermission 角色权限标识组
* @return 结果
*/
public static boolean isTenantAdmin(Set<String> rolePermission) {
if (CollectionUtils.isEmpty(rolePermission)) {
return false;
}
return rolePermission.contains(Constants.TENANT_ADMIN_ROLE_KEY);
}
/**
* 是否为租户管理员
*
* @return 结果
*/
public static boolean isTenantAdmin() {
LoginUser loginUser = getLoginUser();
if (loginUser == null) {
return false;
}
return Convert.toBool(isTenantAdmin(loginUser.getRolePermission()));
}
/**
* 检查当前用户是否已登录
*
* @return 结果
*/
public static boolean isLogin() {
try {
StpUtil.checkLogin();
return true;
} catch (Exception e) {
return false;
}
}
}

View File

@@ -0,0 +1,2 @@
com.agileboot.common.satoken.config.SaTokenConfiguration
com.agileboot.common.satoken.config.SaTokenMvcConfiguration

View File

@@ -0,0 +1,15 @@
# 内置配置 不允许修改 如需修改请在 nacos 上写相同配置覆盖
# Sa-Token配置
sa-token:
# 允许动态设置 token 有效期
dynamic-active-timeout: true
# 允许从 请求参数 读取 token
is-read-body: true
# 允许从 header 读取 token
is-read-header: true
# 关闭 cookie 鉴权 从根源杜绝 csrf 漏洞风险
is-read-cookie: false
# 开启内网服务调用鉴权(不允许越过gateway访问内网服务 保障服务安全)
check-same-token: false
# token前缀
token-prefix: "Bearer"

View File

@@ -0,0 +1,84 @@
package com.agileboot.common.web.aspect;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;
import java.util.Arrays;
import java.util.stream.Collectors;
@Aspect
@Slf4j
@Component
public class LogAspect {
@Value("${agileboot.traceRequestIdKey:W-RequestId}")
private String requestIdKey;
@Pointcut("execution(* *..controller..*.*(..))")
public void logPointCut() {
}
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (servletRequestAttributes == null) {
log.warn("无法获取请求上下文信息");
return point.proceed();
}
HttpServletRequest request = servletRequestAttributes.getRequest();
HttpServletResponse response = servletRequestAttributes.getResponse();
String url = request.getRequestURI().toString();
String uuid = response != null ? response.getHeader(requestIdKey) : null;
Object[] args = point.getArgs();
String reqParam = filterAndConvertArgsToString(args);
log.info("request start, path: {}, uuid: {}, params: {}", url, uuid, reqParam);
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
Object res = point.proceed();
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("request end, path: {}, uuid: {}, cost: {}ms", url, uuid, totalTimeMillis);
return res;
} catch (Throwable throwable) {
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.error("request error, path: {}, uuid: {}, cost: {}ms", url, uuid, totalTimeMillis);
throw throwable;
}
}
private String filterAndConvertArgsToString(Object[] args) {
if (args == null || args.length == 0) {
return "";
}
return Arrays.stream(args)
.filter(arg -> !(arg instanceof HttpServletRequest ||
arg instanceof HttpServletResponse ||
arg instanceof MultipartFile ||
(arg != null && arg.getClass().isArray() && arg.getClass().getComponentType() != null &&
arg.getClass().getComponentType().isAssignableFrom(MultipartFile.class))))
.map(String::valueOf)
.collect(Collectors.joining(", ", "[", "]"));
}
}

View File

@@ -13,7 +13,7 @@ import org.springframework.web.filter.CorsFilter;
@AutoConfiguration
public class FilterConfig {
@Value("${agileboot.traceRequestIdKey:WOl-RequestId}")
@Value("${agileboot.traceRequestIdKey:W-RequestId}")
private String requestIdKey;
@Bean

View File

@@ -5,6 +5,7 @@ import com.agileboot.common.web.handler.GlobalExceptionHandler;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
@@ -14,10 +15,12 @@ import java.util.Date;
/**
* 通用配置
* 注解EnableAspectJAutoProxy 表示通过aop框架暴露该代理对象,AopContext能够访问
*
* @author Lion Li
*/
@AutoConfiguration
@EnableAspectJAutoProxy(exposeProxy = true)
public class ResourcesConfig implements WebMvcConfigurer {
@Override

View File

@@ -77,7 +77,8 @@ public class GlobalExceptionHandler {
@ExceptionHandler(BizException.class)
public R<Void> handleBaseException(BizException e, HttpServletRequest request) {
log.error(e.getMessage());
return R.fail(e.getMessage());
Integer code = e.getCode();
return ObjectUtil.isNotNull(code) ? R.fail(code, e.getMessage()) : R.fail(e.getMessage());
}
/**

View File

@@ -1,23 +0,0 @@
package com.agileboot.domain.system.config.db;
import com.baomidou.mybatisplus.extension.service.IService;
/**
* <p>
* 参数配置表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
public interface SysConfigService extends IService<SysConfigEntity> {
/**
* 通过key获取配置
*
* @param key 配置对应的key
* @return 配置
*/
String getConfigValueByKey(String key);
}

View File

@@ -1,35 +0,0 @@
package com.agileboot.domain.system.config.db;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
/**
* <p>
* 参数配置表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfigEntity> implements
SysConfigService {
@Override
public String getConfigValueByKey(String key) {
if (StrUtil.isBlank(key)) {
return StrUtil.EMPTY;
}
QueryWrapper<SysConfigEntity> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("config_key", key);
SysConfigEntity one = this.getOne(queryWrapper);
if (one == null || one.getConfigValue() == null) {
return StrUtil.EMPTY;
}
return one.getConfigValue();
}
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.agileboot</groupId>
<artifactId>agileboot-system</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>agileboot-system-base</artifactId>
<dependencies>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-web</artifactId>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-common-mybatis</artifactId>
</dependency>
<dependency>
<groupId>com.agileboot</groupId>
<artifactId>wol-domain</artifactId>
</dependency>
<!-- 获取系统信息 -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,8 @@
package com.agileboot.system.client.mapper;
import com.agileboot.system.client.entity.SysClient;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysClientMapper extends BaseMapper<SysClient> {
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.client.mapper.SysClientMapper">
</mapper>

View File

@@ -0,0 +1,7 @@
package com.agileboot.system.client.service;
import com.agileboot.system.client.vo.SysClientVO;
public interface ISysClientService {
SysClientVO queryByClientId(String clientId);
}

View File

@@ -0,0 +1,19 @@
package com.agileboot.system.client.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.system.client.entity.SysClient;
import com.agileboot.system.client.mapper.SysClientMapper;
import com.agileboot.system.client.service.ISysClientService;
import com.agileboot.system.client.vo.SysClientVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class SysClientServiceImpl extends ServiceImpl<SysClientMapper, SysClient> implements ISysClientService {
@Override
public SysClientVO queryByClientId(String clientId) {
SysClient client = super.baseMapper.selectOne(new LambdaQueryWrapper<SysClient>().eq(SysClient::getClientId, clientId));
return BeanUtil.copyProperties(client, SysClientVO.class);
}
}

View File

@@ -0,0 +1,57 @@
package com.agileboot.system.config.controller;
import com.agileboot.common.core.core.R;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.config.dto.ConfigQuery;
import com.agileboot.system.config.dto.ConfigUpdate;
import com.agileboot.system.config.service.ISysConfigService;
import com.agileboot.system.config.vo.ConfigVO;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
/**
* @Author cuiJiaWang
* @Create 2025-08-13 11:49
*/
@RestController
@RequestMapping("/system")
@Validated
@RequiredArgsConstructor
public class SysConfigController {
private final ISysConfigService sysConfigService;
@GetMapping("/test")
@Deprecated
public R<String> test(@RequestParam("key") String key) {
return R.ok(sysConfigService.getConfigValueByKey(key));
}
/**
* 获取参数配置列表
*/
@GetMapping("/configs")
public PageR<ConfigVO> list(ConfigQuery query) {
return sysConfigService.getConfigPage(query);
}
/**
* 根据参数编号获取详细信息
*/
@GetMapping(value = "/config/{configId}")
public R<ConfigVO> getInfo(@NotNull @Positive @PathVariable Long configId) {
return R.ok(sysConfigService.getConfigInfo(configId));
}
/**
* 修改参数配置
*/
@PostMapping(value = "/config/{id}")
public R<Void> edit(@NotNull @Positive @PathVariable Long id, @RequestBody ConfigUpdate config) {
sysConfigService.updateConfig(id, config);
return R.ok();
}
}

View File

@@ -1,5 +1,6 @@
package com.agileboot.domain.system.config.db;
package com.agileboot.system.config.mapper;
import com.agileboot.system.config.entity.SysConfig;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
@@ -10,6 +11,6 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
* @author valarchie
* @since 2022-06-09
*/
public interface SysConfigMapper extends BaseMapper<SysConfigEntity> {
public interface SysConfigMapper extends BaseMapper<SysConfig> {
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.config.mapper.SysConfigMapper">
</mapper>

View File

@@ -0,0 +1,31 @@
package com.agileboot.system.config.service;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.config.dto.ConfigQuery;
import com.agileboot.system.config.dto.ConfigUpdate;
import com.agileboot.system.config.vo.ConfigVO;
/**
* <p>
* 参数配置表 服务类
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
public interface ISysConfigService {
/**
* 通过key获取配置
*
* @param key 配置对应的key
* @return 配置
*/
String getConfigValueByKey(String key);
PageR<ConfigVO> getConfigPage(ConfigQuery query);
ConfigVO getConfigInfo(Long id);
void updateConfig(Long id, ConfigUpdate update);
}

View File

@@ -0,0 +1,63 @@
package com.agileboot.system.config.service.impl;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.config.dto.ConfigQuery;
import com.agileboot.system.config.dto.ConfigUpdate;
import com.agileboot.system.config.entity.SysConfig;
import com.agileboot.system.config.mapper.SysConfigMapper;
import com.agileboot.system.config.service.ISysConfigService;
import com.agileboot.system.config.vo.ConfigVO;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
/**
* <p>
* 参数配置表 服务实现类
* </p>
*
* @author valarchie
* @since 2022-06-09
*/
@Service
public class SysConfigServiceImpl extends ServiceImpl<SysConfigMapper, SysConfig> implements ISysConfigService {
@Override
public String getConfigValueByKey(String key) {
if (StringUtils.isBlank(key)) {
return "";
}
SysConfig one = this.lambdaQuery().eq(SysConfig::getConfigKey, key).one();
if (one == null || one.getConfigValue() == null) {
return "";
}
return one.getConfigValue();
}
@Override
public PageR<ConfigVO> getConfigPage(ConfigQuery query) {
Page<SysConfig> page = this.page(query.toPage(), query.toQueryWrapper());
IPage<ConfigVO> convert = page.convert(ConfigVO::new);
return new PageR<>(convert);
}
@Override
public ConfigVO getConfigInfo(Long id) {
return new ConfigVO(this.getById(id));
}
@Override
public void updateConfig(Long id, ConfigUpdate update) {
if (StringUtils.isBlank(update.getConfigValue())) {
throw new BizException("");
}
SysConfig entity = new SysConfig();
entity.setId(id);
entity.setConfigValue(update.getConfigValue());
this.updateById(entity);
}
}

View File

@@ -0,0 +1,82 @@
package com.agileboot.system.dept.controller;
import cn.hutool.core.lang.tree.Tree;
import com.agileboot.common.core.core.R;
import com.agileboot.system.dept.dto.AddDeptDTO;
import com.agileboot.system.dept.dto.DeptQuery;
import com.agileboot.system.dept.dto.UpdateDeptDTO;
import com.agileboot.system.dept.service.ISysDeptService;
import com.agileboot.system.dept.vo.DeptVO;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 部门信息
*/
@RestController
@RequestMapping("/system")
@Validated
@RequiredArgsConstructor
public class SysDeptController {
private final ISysDeptService sysDeptService;
/**
* 获取部门列表
*/
@GetMapping("/depts")
public R<List<DeptVO>> list(DeptQuery query) {
List<DeptVO> deptList = sysDeptService.getDeptList(query);
return R.ok(deptList);
}
/**
* 根据部门编号获取详细信息
*/
@GetMapping(value = "/dept/{deptId}")
public R<DeptVO> getInfo(@PathVariable Long deptId) {
DeptVO dept = sysDeptService.getDeptInfo(deptId);
return R.ok(dept);
}
/**
* 获取部门下拉树列表
*/
@GetMapping("/depts/dropdown")
public R<List<Tree<Long>>> dropdownList() {
List<Tree<Long>> deptTree = sysDeptService.getDeptTree();
return R.ok(deptTree);
}
/**
* 新增部门
*/
@PostMapping("/dept")
public R<Void> add(@RequestBody AddDeptDTO addCommand) {
sysDeptService.addDept(addCommand);
return R.ok();
}
/**
* 修改部门
*/
@PutMapping("/dept/{deptId}")
public R<Void> edit(@PathVariable("deptId") Long deptId, @RequestBody UpdateDeptDTO updateCommand) {
updateCommand.setDeptId(deptId);
sysDeptService.updateDept(updateCommand);
return R.ok();
}
/**
* 删除部门
*/
@DeleteMapping("/dept/{deptId}")
public R<Void> remove(@PathVariable @NotNull Long deptId) {
sysDeptService.removeDept(deptId);
return R.ok();
}
}

View File

@@ -0,0 +1,7 @@
package com.agileboot.system.dept.mapper;
import com.agileboot.system.dept.entity.SysDept;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
public interface SysDeptMapper extends BaseMapper<SysDept> {
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.dept.mapper.SysDeptMapper">
</mapper>

View File

@@ -0,0 +1,24 @@
package com.agileboot.system.dept.service;
import cn.hutool.core.lang.tree.Tree;
import com.agileboot.system.dept.dto.AddDeptDTO;
import com.agileboot.system.dept.dto.DeptQuery;
import com.agileboot.system.dept.dto.UpdateDeptDTO;
import com.agileboot.system.dept.vo.DeptVO;
import jakarta.validation.constraints.NotNull;
import java.util.List;
public interface ISysDeptService {
List<DeptVO> getDeptList(DeptQuery query);
DeptVO getDeptInfo(Long deptId);
List<Tree<Long>> getDeptTree();
void addDept(AddDeptDTO addCommand);
void updateDept(UpdateDeptDTO updateCommand);
void removeDept(@NotNull Long deptId);
}

View File

@@ -0,0 +1,153 @@
package com.agileboot.system.dept.service.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.common.core.enums.BasicEnumUtil;
import com.agileboot.common.core.enums.common.StatusEnum;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.core.exception.error.ErrorCode;
import com.agileboot.system.dept.dto.AddDeptDTO;
import com.agileboot.system.dept.dto.DeptQuery;
import com.agileboot.system.dept.dto.UpdateDeptDTO;
import com.agileboot.system.dept.entity.SysDept;
import com.agileboot.system.dept.mapper.SysDeptMapper;
import com.agileboot.system.dept.service.ISysDeptService;
import com.agileboot.system.dept.vo.DeptVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Service
public class SysDeptServiceImpl extends ServiceImpl<SysDeptMapper, SysDept> implements ISysDeptService {
@Override
public List<DeptVO> getDeptList(DeptQuery query) {
List<SysDept> list = super.list(query.toQueryWrapper());
return list.stream().map(DeptVO::new).collect(Collectors.toList());
}
@Override
public DeptVO getDeptInfo(Long deptId) {
SysDept one = super.getById(deptId);
return new DeptVO(one);
}
@Override
public List<Tree<Long>> getDeptTree() {
List<SysDept> list = super.list();
return TreeUtil.build(list, 0L, (dept, tree) -> {
tree.setId(dept.getDeptId());
tree.setParentId(dept.getParentId());
tree.putExtra("label", dept.getDeptName());
});
}
@Override
public void addDept(AddDeptDTO addDTO) {
Long parentId = addDTO.getParentId();
SysDept entity = new SysDept();
entity.setParentId(parentId);
entity.setDeptName(addDTO.getDeptName());
entity.setOrderNum(addDTO.getOrderNum());
entity.setLeaderName(addDTO.getLeaderName());
entity.setPhone(addDTO.getPhone());
entity.setEmail(addDTO.getEmail());
entity.setStatus(addDTO.getStatus());
LambdaQueryWrapper<SysDept> queryWrapper = Wrappers.lambdaQuery(SysDept.class)
.eq(SysDept::getDeptName, addDTO.getDeptName())
.eq(parentId != null, SysDept::getDeptId, parentId);
if (super.exists(queryWrapper)) {
throw new BizException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE.message(), addDTO.getDeptName());
}
SysDept sysDept = generateAncestors(entity);
super.save(sysDept);
}
@Override
public void updateDept(UpdateDeptDTO updateDTO) {
Long deptId = updateDTO.getDeptId();
Long parentId = updateDTO.getParentId();
if (Objects.equals(parentId, deptId)) {
throw new BizException(ErrorCode.Business.DEPT_PARENT_ID_IS_NOT_ALLOWED_SELF);
}
Integer status = updateDTO.getStatus();
SysDept entity = super.getById(deptId);
if (entity == null) {
throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND.message(), deptId, "部门");
}
entity.setParentId(parentId);
entity.setDeptName(updateDTO.getDeptName());
entity.setOrderNum(updateDTO.getOrderNum());
entity.setLeaderName(updateDTO.getLeaderName());
entity.setPhone(updateDTO.getPhone());
entity.setEmail(updateDTO.getEmail());
entity.setStatus(Convert.toInt(status, 0));
LambdaQueryWrapper<SysDept> queryWrapper = Wrappers.lambdaQuery(SysDept.class)
.eq(SysDept::getDeptName, updateDTO.getDeptName())
.ne(deptId != null, SysDept::getDeptId, deptId)
.eq(parentId != null, SysDept::getDeptId, parentId);
if (super.exists(queryWrapper)) {
throw new BizException(ErrorCode.Business.DEPT_NAME_IS_NOT_UNIQUE.message(), updateDTO.getDeptName());
}
if (StatusEnum.DISABLE.getValue().equals(entity.getStatus()) && this.hasChildrenDept(deptId, true)) {
throw new BizException(ErrorCode.Business.DEPT_STATUS_ID_IS_NOT_ALLOWED_CHANGE);
}
SysDept sysDept = generateAncestors(entity);
super.updateById(sysDept);
}
@Override
public void removeDept(Long deptId) {
SysDept entity = super.getById(deptId);
if (entity == null) {
throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND.message(), deptId, "部门");
}
if (this.hasChildrenDept(entity.getDeptId(), null)) {
throw new BizException(ErrorCode.Business.DEPT_EXIST_CHILD_DEPT_NOT_ALLOW_DELETE);
}
if (this.isDeptAssignedToUsers(entity.getDeptId())) {
throw new BizException(ErrorCode.Business.DEPT_EXIST_LINK_USER_NOT_ALLOW_DELETE);
}
super.removeById(deptId);
}
private SysDept generateAncestors(SysDept entity) {
Long parentId = entity.getParentId();
if (parentId == null || parentId == 0) {
entity.setAncestors(String.valueOf(parentId == null ? 0 : parentId));
} else {
SysDept parentDept = super.getById(parentId);
// 检查 parentDept 是否为 null 或者状态为禁用
if (parentDept == null || StatusEnum.DISABLE.equals(
BasicEnumUtil.fromValue(StatusEnum.class, parentDept.getStatus()))) {
throw new BizException(ErrorCode.Business.DEPT_PARENT_DEPT_NO_EXIST_OR_DISABLED);
}
// 处理 parentDept.getAncestors() 可能为 null 的情况
String ancestors = parentDept.getAncestors() == null ? "" : parentDept.getAncestors();
entity.setAncestors(ancestors + "," + parentId);
}
return entity;
}
private boolean hasChildrenDept(Long deptId, Boolean enabled) {
LambdaQueryWrapper<SysDept> queryWrapper = Wrappers.lambdaQuery(SysDept.class)
.eq(enabled != null, SysDept::getStatus, 1)
.and(o -> o.eq(SysDept::getParentId, deptId).or()
.apply("FIND_IN_SET (" + deptId + " , ancestors)")
);
return super.exists(queryWrapper);
}
private boolean isDeptAssignedToUsers(Long deptId) {
LambdaQueryWrapper<SysDept> queryWrapper = Wrappers.lambdaQuery(SysDept.class)
.eq(SysDept::getDeptId, deptId);
return super.exists(queryWrapper);
}
}

View File

@@ -0,0 +1,85 @@
package com.agileboot.system.log.controller;
import com.agileboot.common.core.core.R;
import com.agileboot.common.core.utils.poi.CustomExcelUtil;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.log.dto.LoginLogQuery;
import com.agileboot.system.log.dto.OperationLogQuery;
import com.agileboot.system.log.service.ILogService;
import com.agileboot.system.log.vo.LoginLogVO;
import com.agileboot.system.log.vo.OperationLogVO;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 系统访问记录
*/
@RestController
@RequestMapping("/logs")
@Validated
@RequiredArgsConstructor
public class SysLogsController {
private final ILogService logService;
/**
* 登录日志列表
*/
@GetMapping("/loginLogs")
public R<PageR<LoginLogVO>> loginInfoList(LoginLogQuery query) {
PageR<LoginLogVO> page = logService.getLoginInfoList(query);
return R.ok(page);
}
/**
* 将登录日志导出到excel
*/
@GetMapping("/loginLogs/excel")
public void loginInfosExcel(HttpServletResponse response, LoginLogQuery query) {
PageR<LoginLogVO> page = logService.getLoginInfoList(query);
CustomExcelUtil.writeToResponse(page.getRows(), LoginLogVO.class, response);
}
/**
* 删除登录日志
*/
@DeleteMapping("/loginLogs")
public R<Void> removeLoginInfos(@RequestParam @NotNull @NotEmpty List<Long> ids) {
logService.deleteLoginInfo(ids);
return R.ok();
}
/**
* 操作日志列表
*/
@GetMapping("/operationLogs")
public R<PageR<OperationLogVO>> operationLogs(OperationLogQuery query) {
PageR<OperationLogVO> page = logService.getOperationLogList(query);
return R.ok(page);
}
/**
* 操作日志导出
*/
@GetMapping("/operationLogs/excel")
public void operationLogsExcel(HttpServletResponse response, OperationLogQuery query) {
PageR<OperationLogVO> page = logService.getOperationLogList(query);
CustomExcelUtil.writeToResponse(page.getRows(), OperationLogVO.class, response);
}
/**
* 删除操作日志
*/
@DeleteMapping("/operationLogs")
public R<Void> removeOperationLogs(@RequestParam List<Long> operationIds) {
logService.deleteOperationLog(operationIds);
return R.ok();
}
}

View File

@@ -0,0 +1,16 @@
package com.agileboot.system.log.mapper;
import com.agileboot.system.log.entity.SysLoginInfo;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 系统访问记录 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-06
*/
public interface SysLoginInfoMapper extends BaseMapper<SysLoginInfo> {
}

View File

@@ -0,0 +1,16 @@
package com.agileboot.system.log.mapper;
import com.agileboot.system.log.entity.SysOperationLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 操作日志记录 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-08
*/
public interface SysOperationLogMapper extends BaseMapper<SysOperationLog> {
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.log.mapper.SysLoginInfoMapper">
</mapper>

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.log.mapper.SysOperationLogMapper">
</mapper>

View File

@@ -0,0 +1,21 @@
package com.agileboot.system.log.service;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.log.dto.LoginLogQuery;
import com.agileboot.system.log.dto.OperationLogQuery;
import com.agileboot.system.log.vo.LoginLogVO;
import com.agileboot.system.log.vo.OperationLogVO;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.List;
public interface ILogService {
PageR<LoginLogVO> getLoginInfoList(LoginLogQuery query);
void deleteLoginInfo(@NotNull @NotEmpty List<Long> ids);
PageR<OperationLogVO> getOperationLogList(OperationLogQuery query);
void deleteOperationLog(List<Long> operationIds);
}

View File

@@ -0,0 +1,55 @@
package com.agileboot.system.log.service.impl;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.log.dto.LoginLogQuery;
import com.agileboot.system.log.dto.OperationLogQuery;
import com.agileboot.system.log.entity.SysLoginInfo;
import com.agileboot.system.log.entity.SysOperationLog;
import com.agileboot.system.log.mapper.SysLoginInfoMapper;
import com.agileboot.system.log.mapper.SysOperationLogMapper;
import com.agileboot.system.log.service.ILogService;
import com.agileboot.system.log.vo.LoginLogVO;
import com.agileboot.system.log.vo.OperationLogVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class LogServiceImpl implements ILogService {
@Resource
private SysLoginInfoMapper sysLoginInfoMapper;
@Resource
private SysOperationLogMapper sysOperationLogMapper;
@Override
public PageR<LoginLogVO> getLoginInfoList(LoginLogQuery query) {
Page<SysLoginInfo> page = sysLoginInfoMapper.selectPage(query.toPage(), query.toQueryWrapper());
IPage<LoginLogVO> pageVO = page.convert(LoginLogVO::new);
return new PageR<>(pageVO);
}
@Override
public void deleteLoginInfo(List<Long> ids) {
LambdaQueryWrapper<SysLoginInfo> wrapper = Wrappers.lambdaQuery(SysLoginInfo.class)
.in(SysLoginInfo::getInfoId, ids);
sysLoginInfoMapper.delete(wrapper);
}
@Override
public PageR<OperationLogVO> getOperationLogList(OperationLogQuery query) {
Page<SysOperationLog> page = sysOperationLogMapper.selectPage(query.toPage(), query.toQueryWrapper());
IPage<OperationLogVO> pageVO = page.convert(OperationLogVO::new);
return new PageR<>(pageVO);
}
@Override
public void deleteOperationLog(List<Long> operationIds) {
sysOperationLogMapper.deleteByIds(operationIds);
}
}

View File

@@ -0,0 +1,86 @@
package com.agileboot.system.menu.controller;
import cn.hutool.core.lang.tree.Tree;
import com.agileboot.common.core.core.R;
import com.agileboot.common.satoken.pojo.LoginUser;
import com.agileboot.common.satoken.utils.LoginHelper;
import com.agileboot.system.menu.dto.*;
import com.agileboot.system.menu.service.ISysMenuService;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 菜单信息
*/
@RestController
@RequestMapping("/system/menus")
@Validated
@RequiredArgsConstructor
public class SysMenuController {
private final ISysMenuService sysMenuService;
/**
* 获取菜单列表
*/
@GetMapping
public R<List<MenuDTO>> menuList(MenuQuery menuQuery) {
List<MenuDTO> menuList = sysMenuService.getMenuList(menuQuery);
return R.ok(menuList);
}
/**
* 根据菜单编号获取详细信息
*/
@GetMapping(value = "/{menuId}")
public R<MenuDetailDTO> menuInfo(@PathVariable("menuId") @NotNull Long menuId) {
MenuDetailDTO menu = sysMenuService.getMenuInfo(menuId);
return R.ok(menu);
}
/**
* 获取菜单下拉树列表
*/
@GetMapping("/dropdown")
public R<List<Tree<Long>>> dropdownList() {
LoginUser loginUser = LoginHelper.getLoginUser();
List<Tree<Long>> dropdownList = sysMenuService.getDropdownList(loginUser);
return R.ok(dropdownList);
}
/**
* 新增菜单
* 需支持一级菜单以及 多级菜单 子菜单为一个 或者 多个的情况
* 隐藏菜单不显示 以及rank排序
* 内链 和 外链
*/
@PostMapping
public R<Void> add(@RequestBody AddMenuDTO addCommand) {
sysMenuService.addMenu(addCommand);
return R.ok();
}
/**
* 修改菜单
*/
@PostMapping("/{menuId}")
public R<Void> edit(@PathVariable("menuId") Long menuId, @RequestBody UpdateMenuDTO updateCommand) {
updateCommand.setMenuId(menuId);
sysMenuService.updateMenu(updateCommand);
return R.ok();
}
/**
* 删除菜单
*/
@PostMapping("/del/{menuId}")
public R<Void> remove(@PathVariable("menuId") Long menuId) {
sysMenuService.remove(menuId);
return R.ok();
}
}

View File

@@ -0,0 +1,51 @@
package com.agileboot.system.menu.mapper;
import com.agileboot.system.menu.entity.SysMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
/**
* <p>
* 菜单权限表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysMenuMapper extends BaseMapper<SysMenu> {
/**
* 根据用户查询出所有菜单
*
* @param userId 用户id
* @return 菜单列表
*/
@Select("SELECT DISTINCT m.* "
+ "FROM sys_menu m "
+ " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id "
+ " LEFT JOIN sys_user u ON rm.role_id = u.role_id "
+ "WHERE u.user_id = #{userId} "
+ " AND m.status = 1 "
+ " AND m.deleted = 0 "
+ "ORDER BY m.parent_id")
List<SysMenu> selectMenuListByUserId(@Param("userId") Long userId);
/**
* 根据角色ID查询菜单树信息
*
* @param roleId 角色ID
* @return 选中菜单列表
*/
@Select("SELECT DISTINCT m.menu_id "
+ "FROM sys_menu m "
+ " LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id "
+ "WHERE rm.role_id = #{roleId} "
+ " AND m.deleted = 0 "
+ "GROUP BY m.menu_id ")
List<Long> selectMenuIdsByRoleId(@Param("roleId") Long roleId);
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.menu.mapper.SysMenuMapper">
</mapper>

View File

@@ -0,0 +1,26 @@
package com.agileboot.system.menu.service;
import cn.hutool.core.lang.tree.Tree;
import com.agileboot.common.satoken.pojo.LoginUser;
import com.agileboot.system.menu.dto.*;
import com.agileboot.system.menu.vo.RouterVO;
import java.util.List;
public interface ISysMenuService {
List<MenuDTO> getMenuList(MenuQuery menuQuery);
MenuDetailDTO getMenuInfo(Long menuId);
List<Tree<Long>> getDropdownList(LoginUser loginUser);
void addMenu(AddMenuDTO addCommand);
void updateMenu(UpdateMenuDTO updateCommand);
void remove(Long menuId);
List<Long> getMenuIdsByRoleId(Long roleId);
List<RouterVO> getRouterTree(LoginUser loginUser);
}

View File

@@ -0,0 +1,238 @@
package com.agileboot.system.menu.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import com.agileboot.common.core.enums.common.MenuTypeEnum;
import com.agileboot.common.core.enums.common.StatusEnum;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.core.exception.error.ErrorCode;
import com.agileboot.common.core.utils.jackson.JacksonUtil;
import com.agileboot.common.satoken.pojo.LoginUser;
import com.agileboot.system.menu.dto.*;
import com.agileboot.system.menu.entity.SysMenu;
import com.agileboot.system.menu.mapper.SysMenuMapper;
import com.agileboot.system.menu.service.ISysMenuService;
import com.agileboot.system.menu.vo.RouterVO;
import com.agileboot.system.role.mapper.SysRoleMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* 菜单应用服务
*
* @author valarchie
*/
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements ISysMenuService {
private final SysRoleMapper roleMapper;
@Override
public List<MenuDTO> getMenuList(MenuQuery query) {
List<SysMenu> list = super.list(query.toQueryWrapper());
return list.stream().map(MenuDTO::new)
.sorted(Comparator.comparing(MenuDTO::getRank, Comparator.nullsLast(Integer::compareTo)))
.collect(Collectors.toList());
}
@Override
public MenuDetailDTO getMenuInfo(Long menuId) {
SysMenu sysMenu = super.getById(menuId);
return new MenuDetailDTO(sysMenu);
}
@Override
public List<Tree<Long>> getDropdownList(LoginUser loginUser) {
List<SysMenu> menuEntityList =
// loginUser.isAdmin() ?
super.list();
// :
// this.baseMapper.selectMenuListByUserId(loginUser.getUserId());
return buildMenuTreeSelect(menuEntityList);
}
@Override
public void addMenu(AddMenuDTO addCommand) {
SysMenu entity = new SysMenu();
BeanUtils.copyProperties(addCommand, entity, "menuId");
String metaInfo = JacksonUtil.to(addCommand.getMeta());
entity.setMetaInfo(metaInfo);
this.checkMenuNameUnique(entity.getMenuName(), entity.getMenuId(), entity.getParentId());
this.checkAddButtonInIframeOrOutLink(entity.getIsButton(), entity.getParentId());
this.checkAddMenuNotInCatalog(entity.getIsButton(), entity.getParentId());
super.save(entity);
}
@Override
public void updateMenu(UpdateMenuDTO dto) {
Long menuId = dto.getMenuId();
SysMenu byId = super.getById(menuId);
if (byId == null) {
throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, menuId, "菜单");
}
if (!Objects.equals(byId.getMenuType(), dto.getMenuType()) && !byId.getIsButton()) {
throw new BizException(ErrorCode.Business.MENU_CAN_NOT_CHANGE_MENU_TYPE);
}
BeanUtil.copyProperties(dto, byId, "menuId");
String metaInfo = JacksonUtil.to(dto.getMeta());
byId.setMetaInfo(metaInfo);
this.checkMenuNameUnique(byId.getMenuName(), byId.getMenuId(), byId.getParentId());
this.checkAddButtonInIframeOrOutLink(byId.getIsButton(), byId.getParentId());
this.checkAddMenuNotInCatalog(byId.getIsButton(), byId.getParentId());
this.checkParentIdConflict(byId.getMenuId(), byId.getParentId());
super.updateById(byId);
}
@Override
public void remove(Long menuId) {
// 是否存在菜单子节点
if (super.lambdaQuery().eq(SysMenu::getParentId, menuId).exists()) {
throw new BizException(ErrorCode.Business.MENU_EXIST_CHILD_MENU_NOT_ALLOW_DELETE);
}
// 查询菜单是否存在角色
if (roleMapper.isMenuAssignToRoles(menuId)) {
throw new BizException(ErrorCode.Business.MENU_ALREADY_ASSIGN_TO_ROLE_NOT_ALLOW_DELETE);
}
super.removeById(menuId);
}
@Override
public List<Long> getMenuIdsByRoleId(Long roleId) {
return this.baseMapper.selectMenuIdsByRoleId(roleId);
}
@Override
public List<RouterVO> getRouterTree(LoginUser loginUser) {
List<Tree<Long>> trees = buildMenuEntityTree(loginUser);
return buildRouterTree(trees);
}
public List<Tree<Long>> buildMenuEntityTree(LoginUser loginUser) {
List<SysMenu> allMenus;
if (loginUser.getIsAdmin() == 1) {
allMenus = super.list();
} else {
allMenus = this.baseMapper.selectMenuListByUserId(loginUser.getUserId());
}
// 传给前端的路由排除掉按钮和停用的菜单
List<SysMenu> noButtonMenus = allMenus.stream()
.filter(menu -> !menu.getIsButton())
.filter(menu -> StatusEnum.ENABLE.getValue().equals(menu.getStatus()))
.collect(Collectors.toList());
TreeNodeConfig config = new TreeNodeConfig();
// 默认为id 可以不设置
config.setIdKey("menuId");
return TreeUtil.build(noButtonMenus, 0L, config, (menu, tree) -> {
// 也可以使用 tree.setId(dept.getId());等一些默认值
tree.setId(menu.getMenuId());
tree.setParentId(menu.getParentId());
// TODO 可以取meta中的rank来排序
// tree.setWeight(menu.getRank());
tree.putExtra("entity", menu);
});
}
public List<RouterVO> buildRouterTree(List<Tree<Long>> trees) {
List<RouterVO> routers = new LinkedList<>();
if (CollUtil.isNotEmpty(trees)) {
for (Tree<Long> tree : trees) {
Object entity = tree.get("entity");
if (entity != null) {
RouterVO routerDTO = new RouterVO((SysMenu) entity);
List<Tree<Long>> children = tree.getChildren();
if (CollUtil.isNotEmpty(children)) {
routerDTO.setChildren(buildRouterTree(children));
}
routers.add(routerDTO);
}
}
}
return routers;
}
/**
* 构建前端所需要树结构
*
* @param menus 菜单列表
* @return 树结构列表
*/
private List<Tree<Long>> buildMenuTreeSelect(List<SysMenu> menus) {
TreeNodeConfig config = new TreeNodeConfig();
//默认为id可以不设置
config.setIdKey("menuId");
return TreeUtil.build(menus, 0L, config, (menu, tree) -> {
// 也可以使用 tree.setId(dept.getId());等一些默认值
tree.setId(menu.getMenuId());
tree.setParentId(menu.getParentId());
tree.putExtra("label", menu.getMenuName());
});
}
private void checkMenuNameUnique(String menuName, Long menuId, Long parentId) {
LambdaQueryWrapper<SysMenu> queryWrapper = Wrappers.lambdaQuery(SysMenu.class)
.eq(SysMenu::getMenuName, menuName)
.ne(menuId != null, SysMenu::getMenuId, menuId)
.eq(parentId != null, SysMenu::getParentId, parentId);
if (this.baseMapper.exists(queryWrapper)) {
throw new BizException(ErrorCode.Business.MENU_NAME_IS_NOT_UNIQUE, menuName);
}
}
private void checkParentIdConflict(Long menuId, Long parentId) {
if (menuId.equals(parentId)) {
throw new BizException(ErrorCode.Business.MENU_PARENT_ID_NOT_ALLOW_SELF);
}
}
/**
* Iframe和外链跳转类型 不允许添加按钮
*/
private void checkAddButtonInIframeOrOutLink(Boolean isButton, Long parentId) {
SysMenu parentMenu = super.getById(parentId);
if (parentMenu != null && isButton
&& (Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.IFRAME.getValue())
|| Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.OUTSIDE_LINK_REDIRECT.getValue())
)) {
throw new BizException(ErrorCode.Business.MENU_NOT_ALLOWED_TO_CREATE_BUTTON_ON_IFRAME_OR_OUT_LINK);
}
}
/**
* 只允许在目录菜单类型底下 添加子菜单
*/
private void checkAddMenuNotInCatalog(Boolean isButton, Long parentId) {
SysMenu parentMenu = super.getById(parentId);
if (parentMenu != null && !isButton
&& (!Objects.equals(parentMenu.getMenuType(), MenuTypeEnum.CATALOG.getValue())
)) {
throw new BizException(ErrorCode.Business.MENU_ONLY_ALLOWED_TO_CREATE_SUB_MENU_IN_CATALOG);
}
}
}

View File

@@ -0,0 +1,64 @@
package com.agileboot.system.monitor.controller;
import com.agileboot.common.core.core.R;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.monitor.service.ISysMonitorService;
import com.agileboot.system.monitor.vo.OnlineUserVO;
import com.agileboot.system.monitor.vo.RedisCacheInfoVO;
import com.agileboot.system.monitor.vo.ServerInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 缓存监控
*/
@RestController
@RequestMapping("/monitor")
@RequiredArgsConstructor
public class MonitorController {
private final ISysMonitorService sysMonitorService;
/**
* Redis信息
*/
@GetMapping("/cacheInfo")
public R<RedisCacheInfoVO> getRedisCacheInfo() {
RedisCacheInfoVO redisCacheInfo = sysMonitorService.getRedisCacheInfo();
return R.ok(redisCacheInfo);
}
/**
* 服务器信息
*/
@GetMapping("/serverInfo")
public R<ServerInfo> getServerInfo() {
ServerInfo serverInfo = sysMonitorService.getServerInfo();
return R.ok(serverInfo);
}
/**
* 获取在线用户列表
*
* @param ipAddress ip地址
* @param username 用户名
* @return 分页处理后的在线用户信息
*/
@GetMapping("/onlineUsers")
public PageR<OnlineUserVO> onlineUsers(@RequestParam(name = "ipAddress", required = false) String ipAddress, @RequestParam(name = "username", required = false) String username) {
List<OnlineUserVO> onlineUserList = sysMonitorService.getOnlineUserList(username, ipAddress);
return new PageR<>(onlineUserList);
}
/**
* 强退用户
*/
@DeleteMapping("/onlineUser/{tokenId}")
public R<Void> logoutOnlineUser(@PathVariable String tokenId) {
return R.ok();
}
}

View File

@@ -0,0 +1,15 @@
package com.agileboot.system.monitor.service;
import com.agileboot.system.monitor.vo.OnlineUserVO;
import com.agileboot.system.monitor.vo.RedisCacheInfoVO;
import com.agileboot.system.monitor.vo.ServerInfo;
import java.util.List;
public interface ISysMonitorService {
RedisCacheInfoVO getRedisCacheInfo();
ServerInfo getServerInfo();
List<OnlineUserVO> getOnlineUserList(String username, String ipAddress);
}

View File

@@ -0,0 +1,28 @@
package com.agileboot.system.monitor.service.impl;
import com.agileboot.system.monitor.service.ISysMonitorService;
import com.agileboot.system.monitor.vo.OnlineUserVO;
import com.agileboot.system.monitor.vo.RedisCacheInfoVO;
import com.agileboot.system.monitor.vo.ServerInfo;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SysMonitorServiceImpl implements ISysMonitorService {
@Override
public RedisCacheInfoVO getRedisCacheInfo() {
return null;
}
@Override
public ServerInfo getServerInfo() {
return ServerInfo.fillInfo();
}
@Override
public List<OnlineUserVO> getOnlineUserList(String username, String ipAddress) {
return List.of();
}
}

View File

@@ -0,0 +1,84 @@
package com.agileboot.system.notice.controller;
import com.agileboot.common.core.core.R;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.notice.dto.NoticeAddDTO;
import com.agileboot.system.notice.dto.NoticeQuery;
import com.agileboot.system.notice.dto.NoticeUpdateDTO;
import com.agileboot.system.notice.dto.NoticeVO;
import com.agileboot.system.notice.service.ISysNoticeService;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 公告 信息操作处理
*/
@RestController
@RequestMapping("/system/notices")
@Validated
@RequiredArgsConstructor
public class SysNoticeController {
private final ISysNoticeService sysNoticeService;
/**
* 获取通知公告列表
*/
@GetMapping("/list")
public PageR<NoticeVO> list(NoticeQuery query) {
PageR<NoticeVO> page = sysNoticeService.getNoticeList(query);
return page;
}
/**
* 获取通知公告列表
* 从从库获取数据 例子 仅供参考
*/
@GetMapping("/database/slave")
public R<PageR<NoticeVO>> listFromSlave(NoticeQuery query) {
PageR<NoticeVO> page = sysNoticeService.getNoticeList(query);
return R.ok(page);
}
/**
* 根据通知公告编号获取详细信息
*/
@GetMapping(value = "/{noticeId}")
public R<NoticeVO> getInfo(@PathVariable @NotNull @Positive Long noticeId) {
NoticeVO vo = sysNoticeService.getNoticeInfo(noticeId);
return R.ok(vo);
}
/**
* 新增通知公告
*/
@PostMapping("/create")
public R<Void> add(@RequestBody NoticeAddDTO addDTO) {
sysNoticeService.addNotice(addDTO);
return R.ok();
}
/**
* 修改通知公告
*/
@PostMapping("/update/{noticeId}")
public R<Void> edit(@PathVariable("noticeId") Long noticeId, @RequestBody NoticeUpdateDTO updateDTO) {
updateDTO.setNoticeId(noticeId);
sysNoticeService.updateNotice(updateDTO);
return R.ok();
}
/**
* 删除通知公告
*/
@PostMapping("/del")
public R<Void> remove(@RequestBody List<Integer> noticeIds) {
sysNoticeService.deleteNotice(noticeIds);
return R.ok();
}
}

View File

@@ -0,0 +1,33 @@
package com.agileboot.system.notice.mapper;
import com.agileboot.system.notice.entity.SysNotice;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
/**
* <p>
* 菜单权限表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysNoticeMapper extends BaseMapper<SysNotice> {
/**
* 根据条件分页查询角色关联的用户列表
*
* @param page 分页对象
* @param queryWrapper 条件选择器
* @return 分页处理后的公告列表
*/
@Select("SELECT n.* "
+ "FROM sys_notice n "
+ "LEFT JOIN sys_user u ON n.create_by = u.user_id"
+ " ${ew.customSqlSegment}")
Page<SysNotice> getNoticeList(Page<SysNotice> page, @Param(Constants.WRAPPER) Wrapper<SysNotice> queryWrapper);
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.notice.mapper.SysNoticeMapper">
</mapper>

View File

@@ -0,0 +1,23 @@
package com.agileboot.system.notice.service;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.notice.dto.NoticeAddDTO;
import com.agileboot.system.notice.dto.NoticeQuery;
import com.agileboot.system.notice.dto.NoticeUpdateDTO;
import com.agileboot.system.notice.dto.NoticeVO;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import java.util.List;
public interface ISysNoticeService {
PageR<NoticeVO> getNoticeList(NoticeQuery query);
NoticeVO getNoticeInfo(@NotNull @Positive Long noticeId);
void addNotice(NoticeAddDTO addCommand);
void updateNotice(NoticeUpdateDTO updateCommand);
void deleteNotice(List<Integer> noticeIds);
}

View File

@@ -0,0 +1,95 @@
package com.agileboot.system.notice.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.agileboot.common.core.enums.BasicEnumUtil;
import com.agileboot.common.core.enums.common.NoticeTypeEnum;
import com.agileboot.common.core.enums.common.StatusEnum;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.core.exception.error.ErrorCode;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.notice.dto.NoticeAddDTO;
import com.agileboot.system.notice.dto.NoticeQuery;
import com.agileboot.system.notice.dto.NoticeUpdateDTO;
import com.agileboot.system.notice.dto.NoticeVO;
import com.agileboot.system.notice.entity.SysNotice;
import com.agileboot.system.notice.mapper.SysNoticeMapper;
import com.agileboot.system.notice.service.ISysNoticeService;
import com.agileboot.system.user.service.ISysUserService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class SysNoticeServiceImpl extends ServiceImpl<SysNoticeMapper, SysNotice> implements ISysNoticeService {
private final ISysUserService userService;
@Override
public PageR<NoticeVO> getNoticeList(NoticeQuery query) {
QueryWrapper<SysNotice> queryWrapper = new QueryWrapper<SysNotice>()
.like(StrUtil.isNotEmpty(query.getNoticeTitle()), "notice_title", query.getNoticeTitle())
.eq(StrUtil.isNotEmpty(query.getNoticeType()), "notice_type", query.getNoticeType())
.eq("n.deleted", 0)
.like(StrUtil.isNotEmpty(query.getCreatorName()), "u.username", query.getCreatorName());
Page<SysNotice> page = this.baseMapper.getNoticeList(query.toPage(), queryWrapper);
Set<Long> userIds = page.getRecords().stream().map(SysNotice::getCreateBy).collect(Collectors.toSet());
Map<Long, String> idNameMap = userService.geIdNameByIds(userIds);
page.getRecords().forEach(sysNotice -> {
Long creatorId = sysNotice.getCreateBy();
String creatorName = idNameMap.get(creatorId);
sysNotice.setSearchValue(creatorName);
});
IPage<NoticeVO> convert = page.convert(NoticeVO::new);
return new PageR<>(convert);
}
@Override
public NoticeVO getNoticeInfo(Long noticeId) {
SysNotice byId = super.getById(noticeId);
if (byId == null) {
throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND.message(), noticeId, "通知公告");
}
return new NoticeVO(byId);
}
@Override
public void addNotice(NoticeAddDTO addDTO) {
BasicEnumUtil.fromValue(NoticeTypeEnum.class, addDTO.getNoticeType());
BasicEnumUtil.fromValue(StatusEnum.class, addDTO.getStatus());
SysNotice sysNotice = new SysNotice();
BeanUtil.copyProperties(addDTO, sysNotice);
super.save(sysNotice);
}
@Override
public void updateNotice(NoticeUpdateDTO updateDTO) {
BasicEnumUtil.fromValue(NoticeTypeEnum.class, updateDTO.getNoticeType());
BasicEnumUtil.fromValue(StatusEnum.class, updateDTO.getStatus());
Long noticeId = updateDTO.getNoticeId();
SysNotice byId = super.getById(noticeId);
if (byId == null) {
throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, noticeId, "通知公告");
}
BeanUtil.copyProperties(updateDTO, byId, "noticeId");
super.updateById(byId);
}
@Override
public void deleteNotice(List<Integer> noticeIds) {
super.removeBatchByIds(noticeIds);
}
}

View File

@@ -0,0 +1,90 @@
package com.agileboot.system.post.controller;
import com.agileboot.common.core.core.R;
import com.agileboot.common.core.utils.poi.CustomExcelUtil;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.post.dto.AddPostDTO;
import com.agileboot.system.post.dto.PostQuery;
import com.agileboot.system.post.dto.PostVO;
import com.agileboot.system.post.dto.UpdatePostDTO;
import com.agileboot.system.post.service.ISysPostService;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 岗位信息操作处理
*/
@RestController
@RequestMapping("/system/post")
@Validated
@RequiredArgsConstructor
public class SysPostController {
private final ISysPostService sysPostService;
/**
* 获取岗位列表
*/
@GetMapping("/list")
public R<PageR<PostVO>> list(PostQuery query) {
PageR<PostVO> page = sysPostService.getPostList(query);
return R.ok(page);
}
/**
* 导出查询到的所有岗位信息到excel文件
*
* @param response http响应
* @param query 查询参数
* @author Kevin Zhang
* @date 2023-10-02
*/
@GetMapping("/excel")
public void export(HttpServletResponse response, PostQuery query) {
List<PostVO> all = sysPostService.getPostListAll(query);
CustomExcelUtil.writeToResponse(all, PostVO.class, response);
}
/**
* 根据岗位编号获取详细信息
*/
@GetMapping(value = "/{postId}")
public R<PostVO> getInfo(@PathVariable Long postId) {
PostVO post = sysPostService.getPostInfo(postId);
return R.ok(post);
}
/**
* 新增岗位
*/
@PostMapping
public R<Void> add(@RequestBody AddPostDTO addDTO) {
sysPostService.addPost(addDTO);
return R.ok();
}
/**
* 修改岗位
*/
@PostMapping("/update")
public R<Void> edit(@RequestBody UpdatePostDTO updateCommand) {
sysPostService.updatePost(updateCommand);
return R.ok();
}
/**
* 删除岗位
*/
@PostMapping("/del")
public R<Void> remove(@RequestBody @NotNull @NotEmpty List<Long> ids) {
sysPostService.deletePost(ids);
return R.ok();
}
}

View File

@@ -0,0 +1,16 @@
package com.agileboot.system.post.mapper;
import com.agileboot.system.post.entity.SysPost;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* <p>
* 菜单权限表 Mapper 接口
* </p>
*
* @author valarchie
* @since 2022-06-16
*/
public interface SysPostMapper extends BaseMapper<SysPost> {
}

View File

@@ -0,0 +1,6 @@
<?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.agileboot.system.post.mapper.SysPostMapper">
</mapper>

View File

@@ -0,0 +1,24 @@
package com.agileboot.system.post.service;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.post.dto.AddPostDTO;
import com.agileboot.system.post.dto.PostQuery;
import com.agileboot.system.post.dto.PostVO;
import com.agileboot.system.post.dto.UpdatePostDTO;
import java.util.List;
public interface ISysPostService {
PostVO getPostInfo(Long postId);
PageR<PostVO> getPostList(PostQuery query);
List<PostVO> getPostListAll(PostQuery query);
void addPost(AddPostDTO addDTO);
void updatePost(UpdatePostDTO updateDTO);
void deletePost(List<Long> ids);
}

View File

@@ -0,0 +1,118 @@
package com.agileboot.system.post.service.impl;
import cn.hutool.core.bean.BeanUtil;
import com.agileboot.common.core.exception.BizException;
import com.agileboot.common.core.exception.error.ErrorCode;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.post.dto.AddPostDTO;
import com.agileboot.system.post.dto.PostQuery;
import com.agileboot.system.post.dto.PostVO;
import com.agileboot.system.post.dto.UpdatePostDTO;
import com.agileboot.system.post.entity.SysPost;
import com.agileboot.system.post.mapper.SysPostMapper;
import com.agileboot.system.post.service.ISysPostService;
import com.agileboot.system.user.service.ISysUserService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class SysPostServiceImpl extends ServiceImpl<SysPostMapper, SysPost> implements ISysPostService {
@Resource
private ISysUserService sysUserService;
@Override
public PostVO getPostInfo(Long postId) {
// sysUserMapper.getPostInfo();
return null;
}
/**
* 条件查询
*
* @param query
* @return
*/
@Override
public PageR<PostVO> getPostList(PostQuery query) {
Page<SysPost> page = super.page(query.toPage(), query.toQueryWrapper());
IPage<PostVO> convert = page.convert(PostVO::new);
return new PageR<>(convert);
}
/**
* 查询满足条件的所有岗位,不分页
*
* @param query 查询条件
* @return 满足查询条件的岗位列表
* @author Kevin Zhang
*/
@Override
public List<PostVO> getPostListAll(PostQuery query) {
List<SysPost> all = super.list(query.toQueryWrapper());
List<PostVO> records = all.stream().map(PostVO::new).collect(Collectors.toList());
return records;
}
@Override
public void addPost(AddPostDTO addDTO) {
SysPost sysPost = new SysPost();
BeanUtil.copyProperties(addDTO, sysPost);
if (this.isPostNameDuplicated(null, sysPost.getPostName())) {
throw new BizException(ErrorCode.Business.POST_NAME_IS_NOT_UNIQUE, sysPost.getPostName());
}
if (this.isPostCodeDuplicated(null, sysPost.getPostCode())) {
throw new BizException(ErrorCode.Business.POST_CODE_IS_NOT_UNIQUE, sysPost.getPostCode());
}
super.save(sysPost);
}
@Override
public void updatePost(UpdatePostDTO updateDTO) {
Long postId = updateDTO.getPostId();
SysPost sysPost = super.getById(postId);
if (sysPost == null) {
throw new BizException(ErrorCode.Business.COMMON_OBJECT_NOT_FOUND, postId, "职位");
}
BeanUtil.copyProperties(updateDTO, sysPost, "postId");
if (this.isPostNameDuplicated(postId, sysPost.getPostName())) {
throw new BizException(ErrorCode.Business.POST_NAME_IS_NOT_UNIQUE, sysPost.getPostName());
}
if (this.isPostCodeDuplicated(postId, sysPost.getPostCode())) {
throw new BizException(ErrorCode.Business.POST_CODE_IS_NOT_UNIQUE, sysPost.getPostCode());
}
super.updateById(sysPost);
}
@Override
public void deletePost(List<Long> ids) {
// 检测职位是否分配给用户
sysUserService.checkAnyPostIsAssignedToUser(ids);
super.removeBatchByIds(ids);
}
// ---------------------------------------------------------------
private boolean isPostNameDuplicated(Long postId, String postName) {
LambdaQueryWrapper<SysPost> queryWrapper = Wrappers.lambdaQuery(SysPost.class)
.ne(postId != null, SysPost::getPostId, postId)
.eq(SysPost::getPostName, postName);
return baseMapper.exists(queryWrapper);
}
private boolean isPostCodeDuplicated(Long postId, String postCode) {
LambdaQueryWrapper<SysPost> queryWrapper = Wrappers.lambdaQuery(SysPost.class)
.ne(postId != null, SysPost::getPostId, postId)
.eq(SysPost::getPostCode, postCode);
return baseMapper.exists(queryWrapper);
}
}

View File

@@ -0,0 +1,148 @@
package com.agileboot.system.role.controller;
import com.agileboot.common.core.core.R;
import com.agileboot.common.core.utils.poi.CustomExcelUtil;
import com.agileboot.common.mybatis.core.page.PageR;
import com.agileboot.system.role.dto.*;
import com.agileboot.system.role.service.ISysRoleService;
import com.agileboot.system.role.vo.RoleVO;
import com.agileboot.system.user.dto.UserInfo;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* 角色信息
*/
@RestController
@RequestMapping("/system/role")
@Validated
@RequiredArgsConstructor
public class SysRoleController {
private final ISysRoleService sysRoleService;
/**
* 角色列表
*/
@GetMapping("/list")
public PageR<RoleVO> list(RoleQuery query) {
PageR<RoleVO> page = sysRoleService.getRoleList(query);
return page;
}
/**
* 角色列表导出
*/
@PostMapping("/export")
public void export(HttpServletResponse response, RoleQuery query) {
PageR<RoleVO> page = sysRoleService.getRoleList(query);
CustomExcelUtil.writeToResponse(page.getRows(), RoleVO.class, response);
}
/**
* 根据角色编号获取详细信息
*/
@GetMapping(value = "/{roleId}")
public R<RoleVO> getInfo(@PathVariable @NotNull Long roleId) {
RoleVO roleInfo = sysRoleService.getRoleInfo(roleId);
return R.ok(roleInfo);
}
/**
* 新增角色
*/
@PostMapping("/create")
public R<Void> add(@RequestBody AddRoleDTO addDTO) {
sysRoleService.addRole(addDTO);
return R.ok();
}
/**
* 移除角色
*/
@PostMapping(value = "/{roleId}")
public R<Void> remove(@PathVariable("roleId") List<Long> roleIds) {
sysRoleService.deleteRole(roleIds);
return R.ok();
}
/**
* 修改保存角色
*/
@PostMapping
public R<Void> edit(@Validated @RequestBody UpdateRoleDTO updateDTO) {
sysRoleService.updateRole(updateDTO);
return R.ok();
}
/**
* 修改保存数据权限
*/
@PostMapping("/{roleId}/dataScope")
public R<Void> dataScope(@PathVariable("roleId") Long roleId,
@RequestBody UpdateDataScopeDTO updateDTO) {
updateDTO.setRoleId(roleId);
sysRoleService.updateDataScope(updateDTO);
return R.ok();
}
/**
* 角色状态修改
*/
@PostMapping("/{roleId}/status")
public R<Void> changeStatus(@PathVariable("roleId") Long roleId,
@RequestBody UpdateStatusDTO updateDTO) {
updateDTO.setRoleId(roleId);
sysRoleService.updateStatus(updateDTO);
return R.ok();
}
/**
* 查询已分配用户角色列表
*/
@GetMapping("/{roleId}/allocated/list")
public PageR<UserInfo> allocatedUserList(@PathVariable("roleId") Long roleId,
AllocatedRoleQuery query) {
query.setRoleId(roleId);
PageR<UserInfo> page = sysRoleService.getAllocatedUserList(query);
return page;
}
/**
* 查询未分配用户角色列表
*/
@GetMapping("/{roleId}/unallocated/list")
public PageR<UserInfo> unallocatedUserList(@PathVariable("roleId") Long roleId,
UnallocatedRoleQuery query) {
query.setRoleId(roleId);
PageR<UserInfo> page = sysRoleService.getUnallocatedUserList(query);
return page;
}
/**
* 批量取消授权用户
*/
@PostMapping("/users/{userIds}/grant/bulk")
public R<Void> deleteRoleOfUserByBulk(@PathVariable("userIds") List<Long> userIds) {
sysRoleService.deleteRoleOfUserByUserIds(userIds);
return R.ok();
}
/**
* 批量选择用户授权
*/
@PostMapping("/{roleId}/users/{userIds}/grant/bulk")
public R<Void> addRoleForUserByBulk(@PathVariable("roleId") Long roleId,
@PathVariable("userIds") List<Long> userIds) {
sysRoleService.addRoleOfUserByByUserIds(roleId, userIds);
return R.ok();
}
}

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